Skip to content

Commit 5144f94

Browse files
dfa1claude
andcommitted
test(writer): cover ChunkImpl put/finish/adapt paths
ChunkImpl was ~32% covered. Add ChunkImplTest exercising put (unknown/duplicate/ chaining), finish (missing/complete), null + unadapted-dtype passthrough, every primitive ptype (plain array, boxed→NullableData on nullable, boxed-reject on non-nullable, type mismatch, F16), Utf8 (null handling + wrong type) and Bool (boxed nullable + reject + wrong type). 100% line coverage. Also inline the boxed-primitive ternaries onto single lines (cleaner, and avoids a jacoco mis-attribution of the always-throwing reject arm). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8babe10 commit 5144f94

2 files changed

Lines changed: 234 additions & 12 deletions

File tree

writer/src/main/java/io/github/dfa1/vortex/writer/ChunkImpl.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,38 +62,32 @@ private static Object adaptPrimitive(String column, DType.Primitive dtype, Objec
6262
return switch (ptype) {
6363
case I8, U8 -> switch (value) {
6464
case byte[] a -> a;
65-
case Byte[] a -> nullable ? boxedToNullableBytes(a)
66-
: rejectNullable(column, ptype);
65+
case Byte[] a -> nullable ? boxedToNullableBytes(a) : rejectNullable(column, ptype);
6766
default -> throw typeMismatch(column, ptype, value);
6867
};
6968
case I16, U16 -> switch (value) {
7069
case short[] a -> a;
71-
case Short[] a -> nullable ? boxedToNullableShorts(a)
72-
: rejectNullable(column, ptype);
70+
case Short[] a -> nullable ? boxedToNullableShorts(a) : rejectNullable(column, ptype);
7371
default -> throw typeMismatch(column, ptype, value);
7472
};
7573
case I32, U32 -> switch (value) {
7674
case int[] a -> a;
77-
case Integer[] a -> nullable ? boxedToNullableInts(a)
78-
: rejectNullable(column, ptype);
75+
case Integer[] a -> nullable ? boxedToNullableInts(a) : rejectNullable(column, ptype);
7976
default -> throw typeMismatch(column, ptype, value);
8077
};
8178
case I64, U64 -> switch (value) {
8279
case long[] a -> a;
83-
case Long[] a -> nullable ? boxedToNullableLongs(a)
84-
: rejectNullable(column, ptype);
80+
case Long[] a -> nullable ? boxedToNullableLongs(a) : rejectNullable(column, ptype);
8581
default -> throw typeMismatch(column, ptype, value);
8682
};
8783
case F32 -> switch (value) {
8884
case float[] a -> a;
89-
case Float[] a -> nullable ? boxedToNullableFloats(a)
90-
: rejectNullable(column, ptype);
85+
case Float[] a -> nullable ? boxedToNullableFloats(a) : rejectNullable(column, ptype);
9186
default -> throw typeMismatch(column, ptype, value);
9287
};
9388
case F64 -> switch (value) {
9489
case double[] a -> a;
95-
case Double[] a -> nullable ? boxedToNullableDoubles(a)
96-
: rejectNullable(column, ptype);
90+
case Double[] a -> nullable ? boxedToNullableDoubles(a) : rejectNullable(column, ptype);
9791
default -> throw typeMismatch(column, ptype, value);
9892
};
9993
case F16 -> switch (value) {
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package io.github.dfa1.vortex.writer;
2+
3+
import io.github.dfa1.vortex.core.DType;
4+
import io.github.dfa1.vortex.core.PType;
5+
import io.github.dfa1.vortex.writer.encode.NullableData;
6+
import org.junit.jupiter.api.Nested;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
14+
15+
class ChunkImplTest {
16+
17+
private static DType.Struct schema(DType dtype) {
18+
return new DType.Struct(List.of("c"), List.of(dtype), false);
19+
}
20+
21+
private static DType prim(PType ptype, boolean nullable) {
22+
return new DType.Primitive(ptype, nullable);
23+
}
24+
25+
private static Object putGet(DType dtype, Object value) {
26+
ChunkImpl sut = new ChunkImpl(schema(dtype));
27+
sut.put("c", value);
28+
return sut.finish().get("c");
29+
}
30+
31+
@Nested
32+
class PutAndFinish {
33+
34+
@Test
35+
void unknownColumnRejected() {
36+
// Given
37+
ChunkImpl sut = new ChunkImpl(schema(prim(PType.I32, false)));
38+
39+
// When / Then
40+
assertThatThrownBy(() -> sut.put("nope", new int[]{1}))
41+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("unknown column");
42+
}
43+
44+
@Test
45+
void duplicatePutRejected() {
46+
// Given
47+
ChunkImpl sut = new ChunkImpl(schema(prim(PType.I32, false)));
48+
sut.put("c", new int[]{1});
49+
50+
// When / Then
51+
assertThatThrownBy(() -> sut.put("c", new int[]{2}))
52+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("duplicate");
53+
}
54+
55+
@Test
56+
void putReturnsSelfForChaining() {
57+
// Given
58+
ChunkImpl sut = new ChunkImpl(schema(prim(PType.I32, false)));
59+
60+
// When
61+
Chunk result = sut.put("c", new int[]{1});
62+
63+
// Then
64+
assertThat(result).isSameAs(sut);
65+
}
66+
67+
@Test
68+
void finishRejectsMissingColumn() {
69+
// Given — schema has two columns, only one put
70+
ChunkImpl sut = new ChunkImpl(new DType.Struct(
71+
List.of("a", "b"), List.of(prim(PType.I32, false), prim(PType.I32, false)), false));
72+
sut.put("a", new int[]{1});
73+
74+
// When / Then
75+
assertThatThrownBy(sut::finish)
76+
.isInstanceOf(IllegalStateException.class).hasMessageContaining("missing column");
77+
}
78+
79+
@Test
80+
void finishReturnsAllColumns() {
81+
// Given
82+
ChunkImpl sut = new ChunkImpl(schema(prim(PType.I32, false)));
83+
int[] col = {1, 2, 3};
84+
sut.put("c", col);
85+
86+
// When
87+
Map<String, Object> result = sut.finish();
88+
89+
// Then
90+
assertThat(result).containsEntry("c", col);
91+
}
92+
93+
@Test
94+
void nullValueRejected() {
95+
// When / Then
96+
assertThatThrownBy(() -> putGet(prim(PType.I32, false), null))
97+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("null array");
98+
}
99+
100+
@Test
101+
void unadaptedDtypePassesThrough() {
102+
// Given — a List dtype is not specially adapted; its carrier passes through unchanged
103+
DType listDtype = new DType.List(prim(PType.I32, false), false);
104+
Object carrier = new Object();
105+
106+
// When
107+
Object result = putGet(listDtype, carrier);
108+
109+
// Then
110+
assertThat(result).isSameAs(carrier);
111+
}
112+
}
113+
114+
@Nested
115+
class Primitive {
116+
117+
@Test
118+
void plainArraysAcceptedForEveryPType() {
119+
// Given / When / Then — the matching primitive array passes through
120+
assertThat(putGet(prim(PType.I8, false), new byte[]{1})).isInstanceOf(byte[].class);
121+
assertThat(putGet(prim(PType.I16, false), new short[]{1})).isInstanceOf(short[].class);
122+
assertThat(putGet(prim(PType.I32, false), new int[]{1})).isInstanceOf(int[].class);
123+
assertThat(putGet(prim(PType.I64, false), new long[]{1})).isInstanceOf(long[].class);
124+
assertThat(putGet(prim(PType.F32, false), new float[]{1})).isInstanceOf(float[].class);
125+
assertThat(putGet(prim(PType.F64, false), new double[]{1})).isInstanceOf(double[].class);
126+
assertThat(putGet(prim(PType.F16, false), new short[]{1})).isInstanceOf(short[].class);
127+
}
128+
129+
@Test
130+
void boxedArraysConvertToNullableDataOnNullableColumns() {
131+
// Given / When / Then — null slots become invalid in the NullableData carrier
132+
assertValidity(putGet(prim(PType.I8, true), new Byte[]{1, null}));
133+
assertValidity(putGet(prim(PType.I16, true), new Short[]{1, null}));
134+
assertValidity(putGet(prim(PType.I32, true), new Integer[]{1, null}));
135+
assertValidity(putGet(prim(PType.I64, true), new Long[]{1L, null}));
136+
assertValidity(putGet(prim(PType.F32, true), new Float[]{1f, null}));
137+
assertValidity(putGet(prim(PType.F64, true), new Double[]{1.0, null}));
138+
}
139+
140+
@Test
141+
void boxedArraysRejectedOnNonNullableColumns() {
142+
// Each must hit rejectNullable (the "rejects boxed array" message), not the
143+
// generic typeMismatch — asserting the message keeps these on the boxed arm.
144+
assertRejectsBoxed(prim(PType.I8, false), new Byte[]{1});
145+
assertRejectsBoxed(prim(PType.I16, false), new Short[]{1});
146+
assertRejectsBoxed(prim(PType.I32, false), new Integer[]{1});
147+
assertRejectsBoxed(prim(PType.I64, false), new Long[]{1L});
148+
assertRejectsBoxed(prim(PType.F32, false), new Float[]{1f});
149+
assertRejectsBoxed(prim(PType.F64, false), new Double[]{1.0});
150+
}
151+
152+
private void assertRejectsBoxed(DType dtype, Object boxed) {
153+
assertThatThrownBy(() -> putGet(dtype, boxed))
154+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("rejects boxed array");
155+
}
156+
157+
@Test
158+
void wrongTypeRejectedForEveryPType() {
159+
for (PType p : List.of(PType.I8, PType.I16, PType.I32, PType.I64, PType.F32, PType.F64, PType.F16)) {
160+
assertThatThrownBy(() -> putGet(prim(p, false), "not an array"))
161+
.as("ptype %s", p)
162+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("expects");
163+
}
164+
}
165+
166+
private void assertValidity(Object result) {
167+
assertThat(result).isInstanceOf(NullableData.class);
168+
assertThat(((NullableData) result).validity()).containsExactly(true, false);
169+
}
170+
}
171+
172+
@Nested
173+
class Utf8 {
174+
175+
@Test
176+
void stringArrayAccepted() {
177+
assertThat(putGet(new DType.Utf8(false), new String[]{"a", "b"})).isInstanceOf(String[].class);
178+
}
179+
180+
@Test
181+
void nullableAllowsNullElements() {
182+
assertThat(putGet(new DType.Utf8(true), new String[]{"a", null})).isInstanceOf(String[].class);
183+
}
184+
185+
@Test
186+
void nonNullableRejectsNullElement() {
187+
assertThatThrownBy(() -> putGet(new DType.Utf8(false), new String[]{"a", null}))
188+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("null at row 1");
189+
}
190+
191+
@Test
192+
void wrongTypeRejected() {
193+
assertThatThrownBy(() -> putGet(new DType.Utf8(false), new int[]{1}))
194+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("expects String[]");
195+
}
196+
}
197+
198+
@Nested
199+
class Bool {
200+
201+
@Test
202+
void boolArrayAccepted() {
203+
assertThat(putGet(new DType.Bool(false), new boolean[]{true, false})).isInstanceOf(boolean[].class);
204+
}
205+
206+
@Test
207+
void boxedConvertsToNullableDataOnNullableColumn() {
208+
// Given / When
209+
Object result = putGet(new DType.Bool(true), new Boolean[]{true, null, false});
210+
211+
// Then
212+
assertThat(result).isInstanceOf(NullableData.class);
213+
assertThat(((NullableData) result).validity()).containsExactly(true, false, true);
214+
}
215+
216+
@Test
217+
void boxedRejectedOnNonNullableColumn() {
218+
assertThatThrownBy(() -> putGet(new DType.Bool(false), new Boolean[]{true}))
219+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("rejects Boolean[]");
220+
}
221+
222+
@Test
223+
void wrongTypeRejected() {
224+
assertThatThrownBy(() -> putGet(new DType.Bool(false), new int[]{1}))
225+
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("expects boolean[]");
226+
}
227+
}
228+
}

0 commit comments

Comments
 (0)