Skip to content

Commit a3012d4

Browse files
dfa1claude
andcommitted
test(reader): cover ZigZagEncodingDecoder decode + broadcast paths
Was 46% line coverage with no dedicated test. Adds round-trips for I8/I16/I32/I64 (incl. MIN/MAX bounds), the single-value broadcast path per width, accepts/encodingId, and the non-primitive-dtype throw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d3d245a commit a3012d4

1 file changed

Lines changed: 236 additions & 0 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package io.github.dfa1.vortex.reader.decode;
2+
3+
import io.github.dfa1.vortex.core.DType;
4+
import io.github.dfa1.vortex.core.PType;
5+
import io.github.dfa1.vortex.core.VortexException;
6+
import io.github.dfa1.vortex.encoding.EncodingId;
7+
import io.github.dfa1.vortex.encoding.TestSegments;
8+
import io.github.dfa1.vortex.reader.ReadRegistry;
9+
import io.github.dfa1.vortex.reader.array.Array;
10+
import io.github.dfa1.vortex.reader.array.ByteArray;
11+
import io.github.dfa1.vortex.reader.array.IntArray;
12+
import io.github.dfa1.vortex.reader.array.LongArray;
13+
import io.github.dfa1.vortex.reader.array.ShortArray;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.EnumSource;
17+
18+
import java.lang.foreign.Arena;
19+
import java.lang.foreign.MemorySegment;
20+
import java.lang.foreign.ValueLayout;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
24+
25+
class ZigZagEncodingDecoderTest {
26+
27+
private static final ZigZagEncodingDecoder SUT = new ZigZagEncodingDecoder();
28+
private static final ReadRegistry REGISTRY = TestRegistry.ofDecoders(SUT, new PrimitiveEncodingDecoder());
29+
30+
// --- zigzag encode helpers (mirror of the decoder's (u >>> 1) ^ -(u & 1)) ---
31+
32+
private static MemorySegment encodedBytes(byte... signed) {
33+
MemorySegment seg = Arena.global().allocate(signed.length);
34+
for (int i = 0; i < signed.length; i++) {
35+
seg.set(ValueLayout.JAVA_BYTE, i, (byte) ((signed[i] << 1) ^ (signed[i] >> 7)));
36+
}
37+
return seg;
38+
}
39+
40+
private static MemorySegment encodedShorts(short... signed) {
41+
short[] u = new short[signed.length];
42+
for (int i = 0; i < signed.length; i++) {
43+
u[i] = (short) ((signed[i] << 1) ^ (signed[i] >> 15));
44+
}
45+
return TestSegments.leShorts(u);
46+
}
47+
48+
private static MemorySegment encodedInts(int... signed) {
49+
int[] u = new int[signed.length];
50+
for (int i = 0; i < signed.length; i++) {
51+
u[i] = (signed[i] << 1) ^ (signed[i] >> 31);
52+
}
53+
return TestSegments.leInts(u);
54+
}
55+
56+
private static MemorySegment encodedLongs(long... signed) {
57+
long[] u = new long[signed.length];
58+
for (int i = 0; i < signed.length; i++) {
59+
u[i] = (signed[i] << 1) ^ (signed[i] >> 63);
60+
}
61+
return TestSegments.leLongs(u);
62+
}
63+
64+
private static Array decode(PType ptype, long n, MemorySegment encoded) {
65+
DType dtype = new DType.Primitive(ptype, false);
66+
ArrayNode child = ArrayNode.of(EncodingId.VORTEX_PRIMITIVE, null, new ArrayNode[0], new int[]{0});
67+
ArrayNode node = ArrayNode.of(EncodingId.VORTEX_ZIGZAG, null, new ArrayNode[]{child}, new int[]{});
68+
DecodeContext ctx = new DecodeContext(node, dtype, n, new MemorySegment[]{encoded}, REGISTRY, Arena.ofAuto());
69+
return SUT.decode(ctx);
70+
}
71+
72+
@Test
73+
void encodingId_isZigzag() {
74+
// Given / When / Then
75+
assertThat(SUT.encodingId()).isEqualTo(EncodingId.VORTEX_ZIGZAG);
76+
}
77+
78+
@ParameterizedTest
79+
@EnumSource(value = PType.class, names = {"I8", "I16", "I32", "I64"})
80+
void accepts_signedIntegers(PType ptype) {
81+
// Given / When / Then
82+
assertThat(SUT.accepts(new DType.Primitive(ptype, false))).isTrue();
83+
}
84+
85+
@ParameterizedTest
86+
@EnumSource(value = PType.class, names = {"U8", "U16", "U32", "U64", "F16", "F32", "F64"})
87+
void accepts_rejectsNonSigned(PType ptype) {
88+
// Given / When / Then
89+
assertThat(SUT.accepts(new DType.Primitive(ptype, false))).isFalse();
90+
}
91+
92+
@Test
93+
void accepts_rejectsNonPrimitive() {
94+
// Given / When / Then
95+
assertThat(SUT.accepts(new DType.Bool(false))).isFalse();
96+
}
97+
98+
@Test
99+
void decode_i8_roundTrip() {
100+
// Given
101+
byte[] signed = {0, -1, 1, Byte.MIN_VALUE, Byte.MAX_VALUE, -42};
102+
103+
// When
104+
Array result = decode(PType.I8, signed.length, encodedBytes(signed));
105+
106+
// Then
107+
assertThat(result).isInstanceOf(ByteArray.class);
108+
ByteArray bytes = (ByteArray) result;
109+
for (int i = 0; i < signed.length; i++) {
110+
assertThat(bytes.getByte(i)).as("index %d", i).isEqualTo(signed[i]);
111+
}
112+
}
113+
114+
@Test
115+
void decode_i16_roundTrip() {
116+
// Given
117+
short[] signed = {0, -1, 1, Short.MIN_VALUE, Short.MAX_VALUE, -1000};
118+
119+
// When
120+
Array result = decode(PType.I16, signed.length, encodedShorts(signed));
121+
122+
// Then
123+
assertThat(result).isInstanceOf(ShortArray.class);
124+
ShortArray shorts = (ShortArray) result;
125+
for (int i = 0; i < signed.length; i++) {
126+
assertThat(shorts.getShort(i)).as("index %d", i).isEqualTo(signed[i]);
127+
}
128+
}
129+
130+
@Test
131+
void decode_i32_roundTrip() {
132+
// Given
133+
int[] signed = {0, -1, 1, Integer.MIN_VALUE, Integer.MAX_VALUE, -123456};
134+
135+
// When
136+
Array result = decode(PType.I32, signed.length, encodedInts(signed));
137+
138+
// Then
139+
assertThat(result).isInstanceOf(IntArray.class);
140+
IntArray ints = (IntArray) result;
141+
for (int i = 0; i < signed.length; i++) {
142+
assertThat(ints.getInt(i)).as("index %d", i).isEqualTo(signed[i]);
143+
}
144+
}
145+
146+
@Test
147+
void decode_i64_roundTrip() {
148+
// Given
149+
long[] signed = {0, -1, 1, Long.MIN_VALUE, Long.MAX_VALUE, -9_000_000_000L};
150+
151+
// When
152+
Array result = decode(PType.I64, signed.length, encodedLongs(signed));
153+
154+
// Then
155+
assertThat(result).isInstanceOf(LongArray.class);
156+
LongArray longs = (LongArray) result;
157+
for (int i = 0; i < signed.length; i++) {
158+
assertThat(longs.getLong(i)).as("index %d", i).isEqualTo(signed[i]);
159+
}
160+
}
161+
162+
// --- broadcast path: child holds a single encoded value, rowCount > 1 ---
163+
164+
@Test
165+
void decode_i8_broadcastsSingleValue() {
166+
// Given a one-element child segment but four logical rows
167+
long n = 4;
168+
169+
// When
170+
Array result = decode(PType.I8, n, encodedBytes((byte) -42));
171+
172+
// Then every row decodes to the lone value (zip-bomb-safe constant)
173+
ByteArray bytes = (ByteArray) result;
174+
for (long i = 0; i < n; i++) {
175+
assertThat(bytes.getByte(i)).as("index %d", i).isEqualTo((byte) -42);
176+
}
177+
}
178+
179+
@Test
180+
void decode_i16_broadcastsSingleValue() {
181+
// Given
182+
long n = 3;
183+
184+
// When
185+
Array result = decode(PType.I16, n, encodedShorts((short) -1000));
186+
187+
// Then
188+
ShortArray shorts = (ShortArray) result;
189+
for (long i = 0; i < n; i++) {
190+
assertThat(shorts.getShort(i)).as("index %d", i).isEqualTo((short) -1000);
191+
}
192+
}
193+
194+
@Test
195+
void decode_i32_broadcastsSingleValue() {
196+
// Given
197+
long n = 3;
198+
199+
// When
200+
Array result = decode(PType.I32, n, encodedInts(-123456));
201+
202+
// Then
203+
IntArray ints = (IntArray) result;
204+
for (long i = 0; i < n; i++) {
205+
assertThat(ints.getInt(i)).as("index %d", i).isEqualTo(-123456);
206+
}
207+
}
208+
209+
@Test
210+
void decode_i64_broadcastsSingleValue() {
211+
// Given
212+
long n = 3;
213+
214+
// When
215+
Array result = decode(PType.I64, n, encodedLongs(-9_000_000_000L));
216+
217+
// Then
218+
LongArray longs = (LongArray) result;
219+
for (long i = 0; i < n; i++) {
220+
assertThat(longs.getLong(i)).as("index %d", i).isEqualTo(-9_000_000_000L);
221+
}
222+
}
223+
224+
@Test
225+
void decode_nonPrimitiveDtype_throws() {
226+
// Given a non-primitive logical type on the context
227+
ArrayNode node = ArrayNode.of(EncodingId.VORTEX_ZIGZAG, null, new ArrayNode[0], new int[]{});
228+
DecodeContext ctx = new DecodeContext(node, new DType.Bool(false), 1,
229+
new MemorySegment[0], REGISTRY, Arena.ofAuto());
230+
231+
// When / Then
232+
assertThatThrownBy(() -> SUT.decode(ctx))
233+
.isInstanceOf(VortexException.class)
234+
.hasMessageContaining("expected primitive dtype");
235+
}
236+
}

0 commit comments

Comments
 (0)