Skip to content

Commit 0f938eb

Browse files
dfa1claude
andcommitted
test(reader): full coverage for LazyRunEnd*Array families + RunEndArrays
Add Byte/Short/Bool dispatch (were 0–40%) plus Int forEach/fold, varying the run-ends array type (Byte/Short/Int/Long) to cover RunEndArrays.readRunEnd, and exercise the walkRuns edges: zero-length run (duplicate run-end) and length extending past the last run-end. Covers the unsupported-run-ends default arm too. 100% line + branch across all five records and the shared helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8cd5318 commit 0f938eb

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

reader/src/test/java/io/github/dfa1/vortex/reader/decode/LazyRunEndArrayTest.java

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
package io.github.dfa1.vortex.reader.decode;
22

3+
import io.github.dfa1.vortex.core.VortexException;
34
import io.github.dfa1.vortex.reader.array.Array;
5+
import io.github.dfa1.vortex.reader.array.BoolArray;
6+
import io.github.dfa1.vortex.reader.array.ByteArray;
47
import io.github.dfa1.vortex.reader.array.IntArray;
8+
import io.github.dfa1.vortex.reader.array.LazyRunEndBoolArray;
9+
import io.github.dfa1.vortex.reader.array.LazyRunEndByteArray;
510
import io.github.dfa1.vortex.reader.array.LazyRunEndIntArray;
611
import io.github.dfa1.vortex.reader.array.LazyRunEndLongArray;
12+
import io.github.dfa1.vortex.reader.array.LazyRunEndShortArray;
713
import io.github.dfa1.vortex.reader.array.LongArray;
14+
import io.github.dfa1.vortex.reader.array.ShortArray;
815
import org.junit.jupiter.api.Nested;
916
import org.junit.jupiter.api.Test;
1017

1118
import java.util.ArrayList;
1219

20+
import static io.github.dfa1.vortex.encoding.DTypes.BOOL;
21+
import static io.github.dfa1.vortex.encoding.DTypes.I8;
22+
import static io.github.dfa1.vortex.encoding.DTypes.I16;
1323
import static io.github.dfa1.vortex.encoding.DTypes.I32;
1424
import static io.github.dfa1.vortex.encoding.DTypes.I64;
25+
import static io.github.dfa1.vortex.reader.array.TestArrays.bools;
26+
import static io.github.dfa1.vortex.reader.array.TestArrays.bytes;
27+
import static io.github.dfa1.vortex.reader.array.TestArrays.doubles;
1528
import static io.github.dfa1.vortex.reader.array.TestArrays.ints;
1629
import static io.github.dfa1.vortex.reader.array.TestArrays.longs;
30+
import static io.github.dfa1.vortex.reader.array.TestArrays.shorts;
1731
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1833

1934
/// Unit tests for the lazy run-end records nested in [RunEndEncodingDecoder].
2035
/// Covers scalar dispatch via binary-search-on-runEnds, forEach run-walking,
@@ -83,6 +98,36 @@ void offsetSkipsLeadingRuns() {
8398
assertThat(sut.getLong(4)).isEqualTo(3L);
8499
}
85100

101+
@Test
102+
void forEachSkipsZeroLengthRun() {
103+
// Given duplicate run-end → run 1 has zero length (count == 0 branch)
104+
LongArray values = longs(1L, 99L, 3L);
105+
Array runEnds = ints(2, 2, 5);
106+
var sut = new LazyRunEndLongArray(I64, 5L, values, runEnds, 0L);
107+
108+
// When
109+
var seen = new ArrayList<Long>();
110+
sut.forEachLong(seen::add);
111+
112+
// Then — the zero-length run's value (99) is never emitted
113+
assertThat(seen).containsExactly(1L, 1L, 3L, 3L, 3L);
114+
}
115+
116+
@Test
117+
void forEachStopsWhenRunsExhaustedBeforeLength() {
118+
// Given length extends past the last run-end → loop exits on run < numRuns
119+
LongArray values = longs(1L, 2L);
120+
Array runEnds = ints(2, 5);
121+
var sut = new LazyRunEndLongArray(I64, 7L, values, runEnds, 0L);
122+
123+
// When
124+
var seen = new ArrayList<Long>();
125+
sut.forEachLong(seen::add);
126+
127+
// Then — only the 5 covered rows are emitted
128+
assertThat(seen).containsExactly(1L, 1L, 2L, 2L, 2L);
129+
}
130+
86131
@Test
87132
void offsetForEachStartsAtOffset() {
88133
// Given runs [0..3)=1,[3..5)=2,[5..8)=3 with offset 3
@@ -114,5 +159,139 @@ void getIntMapsThroughRuns() {
114159
assertThat(sut.getInt(3)).isEqualTo(200);
115160
assertThat(sut.getInt(4)).isEqualTo(200);
116161
}
162+
163+
@Test
164+
void forEachIntAndFoldWalkRuns() {
165+
// Given runs: [0..2)=1, [2..5)=4
166+
IntArray values = ints(1, 4);
167+
Array runEnds = ints(2, 5);
168+
var sut = new LazyRunEndIntArray(I32, 5L, values, runEnds, 0L);
169+
170+
// When / Then
171+
var seen = new ArrayList<Integer>();
172+
sut.forEachInt(seen::add);
173+
assertThat(seen).containsExactly(1, 1, 4, 4, 4);
174+
assertThat(sut.fold(0, Integer::sum)).isEqualTo(14); // 2*1 + 3*4
175+
}
176+
177+
@Test
178+
void forEachSkipsZeroLengthRunAndStopsBeyondRuns() {
179+
// Given a zero-length run (duplicate end) then a length past the last run-end
180+
IntArray values = ints(1, 99, 3);
181+
Array dupEnds = ints(2, 2, 5);
182+
var dup = new LazyRunEndIntArray(I32, 5L, values, dupEnds, 0L);
183+
184+
// When
185+
var seen = new ArrayList<Integer>();
186+
dup.forEachInt(seen::add);
187+
188+
// Then — zero-length run (99) skipped
189+
assertThat(seen).containsExactly(1, 1, 3, 3, 3);
190+
191+
// When — length exceeds last run-end → loop exits on run < numRuns
192+
var over = new LazyRunEndIntArray(I32, 7L, ints(1, 2), ints(2, 5), 0L);
193+
var seen2 = new ArrayList<Integer>();
194+
over.forEachInt(seen2::add);
195+
196+
// Then
197+
assertThat(seen2).containsExactly(1, 1, 2, 2, 2);
198+
}
199+
}
200+
201+
@Nested
202+
class ByteDispatch {
203+
204+
@Test
205+
void getByteGetIntFold_withByteRunEnds() {
206+
// Given runs: [0..2)=1, [2..3)=2, [3..6)=3; run-ends as a ByteArray
207+
ByteArray values = bytes((byte) 1, (byte) 2, (byte) 3);
208+
Array runEnds = bytes((byte) 2, (byte) 3, (byte) 6);
209+
var sut = new LazyRunEndByteArray(I8, 6L, values, runEnds, 0L);
210+
211+
// When / Then
212+
assertThat(sut.getByte(0)).isEqualTo((byte) 1);
213+
assertThat(sut.getByte(2)).isEqualTo((byte) 2);
214+
assertThat(sut.getByte(5)).isEqualTo((byte) 3);
215+
assertThat(sut.getInt(5)).isEqualTo(3);
216+
assertThat(sut.fold(0L, Long::sum)).isEqualTo(13L); // 2*1 + 1*2 + 3*3
217+
}
218+
}
219+
220+
@Nested
221+
class ShortDispatch {
222+
223+
@Test
224+
void getShortGetIntFold_withShortRunEnds() {
225+
// Given runs: [0..2)=10, [2..5)=20; run-ends as a ShortArray
226+
ShortArray values = shorts((short) 10, (short) 20);
227+
Array runEnds = shorts((short) 2, (short) 5);
228+
var sut = new LazyRunEndShortArray(I16, 5L, values, runEnds, 0L);
229+
230+
// When / Then
231+
assertThat(sut.getShort(0)).isEqualTo((short) 10);
232+
assertThat(sut.getShort(4)).isEqualTo((short) 20);
233+
assertThat(sut.getInt(4)).isEqualTo(20);
234+
assertThat(sut.fold(0L, Long::sum)).isEqualTo(80L); // 2*10 + 3*20
235+
}
236+
}
237+
238+
@Nested
239+
class BoolDispatch {
240+
241+
@Test
242+
void getBooleanAndForEach_withLongRunEnds() {
243+
// Given runs: [0..2)=true, [2..5)=false; run-ends as a LongArray
244+
BoolArray values = bools(true, false);
245+
Array runEnds = longs(2L, 5L);
246+
var sut = new LazyRunEndBoolArray(BOOL, 5L, values, runEnds, 0L);
247+
248+
// When
249+
var seen = new ArrayList<Boolean>();
250+
sut.forEachBoolean(seen::add);
251+
252+
// Then
253+
assertThat(sut.getBoolean(0)).isTrue();
254+
assertThat(sut.getBoolean(2)).isFalse();
255+
assertThat(seen).containsExactly(true, true, false, false, false);
256+
}
257+
258+
@Test
259+
void forEachSkipsZeroLengthRunAndStopsBeyondRuns() {
260+
// Given — duplicate run-end yields a zero-length run (RunEndArrays.walkRuns count == 0)
261+
var dup = new LazyRunEndBoolArray(BOOL, 5L, bools(true, false, true),
262+
longs(2L, 2L, 5L), 0L);
263+
264+
// When
265+
var seen = new ArrayList<Boolean>();
266+
dup.forEachBoolean(seen::add);
267+
268+
// Then — the zero-length run is skipped
269+
assertThat(seen).containsExactly(true, true, true, true, true);
270+
271+
// When — length past the last run-end → walkRuns exits on run < numRuns
272+
var over = new LazyRunEndBoolArray(BOOL, 7L, bools(true, false), longs(2L, 5L), 0L);
273+
var seen2 = new ArrayList<Boolean>();
274+
over.forEachBoolean(seen2::add);
275+
276+
// Then
277+
assertThat(seen2).containsExactly(true, true, false, false, false);
278+
}
279+
}
280+
281+
@Nested
282+
class InvalidRunEnds {
283+
284+
@Test
285+
void unsupportedRunEndsType_throws() {
286+
// Given run-ends backed by a DoubleArray — not a supported run-ends type
287+
LongArray values = longs(1L, 2L);
288+
Array badRunEnds = doubles(2.0, 5.0);
289+
var sut = new LazyRunEndLongArray(I64, 5L, values, badRunEnds, 0L);
290+
291+
// When / Then — the binary search reads run-ends and hits the default arm
292+
assertThatThrownBy(() -> sut.getLong(0))
293+
.isInstanceOf(VortexException.class)
294+
.hasMessageContaining("run-ends");
295+
}
117296
}
118297
}

0 commit comments

Comments
 (0)