Skip to content

Commit c9386ed

Browse files
dfa1claude
andcommitted
test(reader): cover VariantEncodingDecoder.dtypeFromProto branches
Was 36% line coverage — only the primitive arm of the proto-to-core DType translation was exercised. Adds a DtypeFromProto nested class covering null/bool/decimal/utf8/binary/struct/list/fixed_size_list/ extension (with and without metadata bytes)/variant, plus the unsupported-empty-proto throw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3e5a992 commit c9386ed

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

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

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212

1313
import io.github.dfa1.vortex.proto.Primitive;
1414
import io.github.dfa1.vortex.proto.VariantMetadata;
15+
import org.junit.jupiter.api.Nested;
1516
import org.junit.jupiter.api.Test;
1617

1718
import java.lang.foreign.Arena;
1819
import java.lang.foreign.MemorySegment;
1920
import java.nio.ByteBuffer;
21+
import java.util.List;
2022

2123
import static org.assertj.core.api.Assertions.assertThat;
2224
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -145,4 +147,166 @@ void decode_wrongChildCount_throws() {
145147
assertThatThrownBy(() -> SUT.decode(ctx))
146148
.hasMessageContaining("expected 1 or 2 children");
147149
}
150+
151+
/// Exercises every branch of [VariantEncodingDecoder#dtypeFromProto] — the
152+
/// proto-to-core DType translation that backs shredded-variant decoding.
153+
@Nested
154+
class DtypeFromProto {
155+
156+
private static io.github.dfa1.vortex.proto.DType prim(io.github.dfa1.vortex.proto.PType pt, boolean nullable) {
157+
return io.github.dfa1.vortex.proto.DType.ofPrimitive(new Primitive(pt, nullable));
158+
}
159+
160+
@Test
161+
void nullType() {
162+
// Given / When
163+
DType result = VariantEncodingDecoder.dtypeFromProto(
164+
io.github.dfa1.vortex.proto.DType.ofNull(new io.github.dfa1.vortex.proto.Null()));
165+
166+
// Then null is always nullable
167+
assertThat(result).isEqualTo(new DType.Null(true));
168+
}
169+
170+
@Test
171+
void bool() {
172+
// Given / When
173+
DType result = VariantEncodingDecoder.dtypeFromProto(
174+
io.github.dfa1.vortex.proto.DType.ofBool(new io.github.dfa1.vortex.proto.Bool(true)));
175+
176+
// Then
177+
assertThat(result).isEqualTo(new DType.Bool(true));
178+
}
179+
180+
@Test
181+
void primitive() {
182+
// Given / When
183+
DType result = VariantEncodingDecoder.dtypeFromProto(prim(io.github.dfa1.vortex.proto.PType.I64, false));
184+
185+
// Then
186+
assertThat(result).isEqualTo(new DType.Primitive(PType.I64, false));
187+
}
188+
189+
@Test
190+
void decimal() {
191+
// Given / When
192+
DType result = VariantEncodingDecoder.dtypeFromProto(
193+
io.github.dfa1.vortex.proto.DType.ofDecimal(new io.github.dfa1.vortex.proto.Decimal(10, 2, false)));
194+
195+
// Then precision/scale narrow to byte
196+
assertThat(result).isEqualTo(new DType.Decimal((byte) 10, (byte) 2, false));
197+
}
198+
199+
@Test
200+
void utf8() {
201+
// Given / When
202+
DType result = VariantEncodingDecoder.dtypeFromProto(
203+
io.github.dfa1.vortex.proto.DType.ofUtf8(new io.github.dfa1.vortex.proto.Utf8(true)));
204+
205+
// Then
206+
assertThat(result).isEqualTo(new DType.Utf8(true));
207+
}
208+
209+
@Test
210+
void binary() {
211+
// Given / When
212+
DType result = VariantEncodingDecoder.dtypeFromProto(
213+
io.github.dfa1.vortex.proto.DType.ofBinary(new io.github.dfa1.vortex.proto.Binary(false)));
214+
215+
// Then
216+
assertThat(result).isEqualTo(new DType.Binary(false));
217+
}
218+
219+
@Test
220+
void struct() {
221+
// Given a two-field struct with mixed child types
222+
var proto = io.github.dfa1.vortex.proto.DType.ofStruct(new io.github.dfa1.vortex.proto.Struct(
223+
List.of("a", "b"),
224+
List.of(prim(io.github.dfa1.vortex.proto.PType.I32, false),
225+
io.github.dfa1.vortex.proto.DType.ofUtf8(new io.github.dfa1.vortex.proto.Utf8(true))),
226+
false));
227+
228+
// When children are translated recursively
229+
DType result = VariantEncodingDecoder.dtypeFromProto(proto);
230+
231+
// Then
232+
assertThat(result).isEqualTo(new DType.Struct(
233+
List.of("a", "b"),
234+
List.of(new DType.Primitive(PType.I32, false), new DType.Utf8(true)),
235+
false));
236+
}
237+
238+
@Test
239+
void list() {
240+
// Given / When element type is translated recursively
241+
DType result = VariantEncodingDecoder.dtypeFromProto(
242+
io.github.dfa1.vortex.proto.DType.ofList(new io.github.dfa1.vortex.proto.List(
243+
prim(io.github.dfa1.vortex.proto.PType.I32, false), true)));
244+
245+
// Then
246+
assertThat(result).isEqualTo(new DType.List(new DType.Primitive(PType.I32, false), true));
247+
}
248+
249+
@Test
250+
void fixedSizeList() {
251+
// Given / When
252+
DType result = VariantEncodingDecoder.dtypeFromProto(
253+
io.github.dfa1.vortex.proto.DType.ofFixedSizeList(new io.github.dfa1.vortex.proto.FixedSizeList(
254+
prim(io.github.dfa1.vortex.proto.PType.F64, false), 4, false)));
255+
256+
// Then size is carried through
257+
assertThat(result).isEqualTo(
258+
new DType.FixedSizeList(new DType.Primitive(PType.F64, false), 4, false));
259+
}
260+
261+
@Test
262+
void extension_withMetadata() {
263+
// Given an extension with non-null metadata bytes
264+
var proto = io.github.dfa1.vortex.proto.DType.ofExtension(new io.github.dfa1.vortex.proto.Extension(
265+
"ip.address", prim(io.github.dfa1.vortex.proto.PType.I32, false), new byte[]{1, 2, 3}));
266+
267+
// When
268+
DType result = VariantEncodingDecoder.dtypeFromProto(proto);
269+
270+
// Then id, storage dtype, and metadata bytes are preserved
271+
assertThat(result).isInstanceOf(DType.Extension.class);
272+
DType.Extension ext = (DType.Extension) result;
273+
assertThat(ext.extensionId()).isEqualTo("ip.address");
274+
assertThat(ext.storageDType()).isEqualTo(new DType.Primitive(PType.I32, false));
275+
assertThat(ext.metadata().remaining()).isEqualTo(3);
276+
}
277+
278+
@Test
279+
void extension_nullMetadata_becomesEmptyBuffer() {
280+
// Given null metadata — must not NPE, maps to an empty read-only buffer
281+
var proto = io.github.dfa1.vortex.proto.DType.ofExtension(new io.github.dfa1.vortex.proto.Extension(
282+
"uuid", prim(io.github.dfa1.vortex.proto.PType.I64, false), null));
283+
284+
// When
285+
DType.Extension result = (DType.Extension) VariantEncodingDecoder.dtypeFromProto(proto);
286+
287+
// Then
288+
assertThat(result.metadata().remaining()).isZero();
289+
}
290+
291+
@Test
292+
void variant() {
293+
// Given / When
294+
DType result = VariantEncodingDecoder.dtypeFromProto(
295+
io.github.dfa1.vortex.proto.DType.ofVariant(new io.github.dfa1.vortex.proto.Variant(false)));
296+
297+
// Then
298+
assertThat(result).isEqualTo(new DType.Variant(false));
299+
}
300+
301+
@Test
302+
void noFieldSet_throws() {
303+
// Given a proto DType with no oneof arm populated
304+
var empty = new io.github.dfa1.vortex.proto.DType(
305+
null, null, null, null, null, null, null, null, null, null, null, null);
306+
307+
// When / Then
308+
assertThatThrownBy(() -> VariantEncodingDecoder.dtypeFromProto(empty))
309+
.hasMessageContaining("unsupported proto DType");
310+
}
311+
}
148312
}

0 commit comments

Comments
 (0)