Skip to content

Commit bcf0423

Browse files
dfa1claude
andcommitted
test(reader): cover ExtensionStorage + CompressionScheme (were untested)
- ExtensionStorageTest: epochInteger across every integer array type + masked valid/null + unsupported, readUnit (tag/null/empty metadata), instantFromRaw for all units incl negative floor math and the Days rejection, checkBounds. - CompressionSchemeTest: of() for every code + unknown-code rejection. Both at 100% line + branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2ca96f8 commit bcf0423

2 files changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.dfa1.vortex.reader;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.params.ParameterizedTest;
5+
import org.junit.jupiter.params.provider.CsvSource;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
10+
class CompressionSchemeTest {
11+
12+
@ParameterizedTest(name = "code {0} -> {1}")
13+
@CsvSource({"0,NONE", "1,LZ4", "2,ZLIB", "3,ZSTD"})
14+
void ofMapsEveryCode(int code, CompressionScheme expected) {
15+
// When / Then
16+
assertThat(CompressionScheme.of(code)).isEqualTo(expected);
17+
assertThat(expected.code).isEqualTo(code);
18+
}
19+
20+
@Test
21+
void unknownCodeRejected() {
22+
// When / Then
23+
assertThatThrownBy(() -> CompressionScheme.of(99))
24+
.isInstanceOf(IllegalArgumentException.class)
25+
.hasMessageContaining("unknown compression code");
26+
}
27+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package io.github.dfa1.vortex.reader.extension;
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.TimeUnit;
7+
import io.github.dfa1.vortex.reader.array.DoubleArray;
8+
import io.github.dfa1.vortex.reader.array.MaskedArray;
9+
import org.junit.jupiter.api.Nested;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.nio.ByteBuffer;
13+
import java.time.Instant;
14+
15+
import static io.github.dfa1.vortex.reader.array.TestArrays.bools;
16+
import static io.github.dfa1.vortex.reader.array.TestArrays.bytes;
17+
import static io.github.dfa1.vortex.reader.array.TestArrays.doubles;
18+
import static io.github.dfa1.vortex.reader.array.TestArrays.ints;
19+
import static io.github.dfa1.vortex.reader.array.TestArrays.longs;
20+
import static io.github.dfa1.vortex.reader.array.TestArrays.shorts;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
24+
class ExtensionStorageTest {
25+
26+
@Nested
27+
class EpochInteger {
28+
29+
@Test
30+
void readsEveryIntegerArrayType() {
31+
// Given / When / Then
32+
assertThat(ExtensionStorage.epochInteger(bytes((byte) -5), 0)).isEqualTo(-5L);
33+
assertThat(ExtensionStorage.epochInteger(shorts((short) 1234), 0)).isEqualTo(1234L);
34+
assertThat(ExtensionStorage.epochInteger(ints(70000), 0)).isEqualTo(70000L);
35+
assertThat(ExtensionStorage.epochInteger(longs(9_000_000_000L), 0)).isEqualTo(9_000_000_000L);
36+
}
37+
38+
@Test
39+
void recursesThroughValidMaskedCell() {
40+
// Given
41+
MaskedArray masked = new MaskedArray(longs(10L, 20L), bools(true, true));
42+
43+
// When / Then
44+
assertThat(ExtensionStorage.epochInteger(masked, 1)).isEqualTo(20L);
45+
}
46+
47+
@Test
48+
void nullMaskedCell_throws() {
49+
// Given
50+
MaskedArray masked = new MaskedArray(longs(10L, 20L), bools(true, false));
51+
52+
// When / Then
53+
assertThatThrownBy(() -> ExtensionStorage.epochInteger(masked, 1))
54+
.isInstanceOf(VortexException.class).hasMessageContaining("null cell");
55+
}
56+
57+
@Test
58+
void unsupportedStorage_throws() {
59+
// Given
60+
DoubleArray bad = doubles(1.0);
61+
62+
// When / Then
63+
assertThatThrownBy(() -> ExtensionStorage.epochInteger(bad, 0))
64+
.isInstanceOf(VortexException.class).hasMessageContaining("unsupported storage type");
65+
}
66+
}
67+
68+
@Nested
69+
class ReadUnit {
70+
71+
@Test
72+
void readsTagByte() {
73+
// Given — tag 3 = Seconds (ordinal)
74+
DType.Extension ext = ext(ByteBuffer.wrap(new byte[]{3}));
75+
76+
// When / Then
77+
assertThat(ExtensionStorage.readUnit(ext)).isEqualTo(TimeUnit.Seconds);
78+
}
79+
80+
@Test
81+
void nullMetadata_throws() {
82+
assertThatThrownBy(() -> ExtensionStorage.readUnit(ext(null)))
83+
.isInstanceOf(VortexException.class).hasMessageContaining("missing TimeUnit");
84+
}
85+
86+
@Test
87+
void emptyMetadata_throws() {
88+
assertThatThrownBy(() -> ExtensionStorage.readUnit(ext(ByteBuffer.allocate(0))))
89+
.isInstanceOf(VortexException.class).hasMessageContaining("missing TimeUnit");
90+
}
91+
92+
private DType.Extension ext(ByteBuffer meta) {
93+
return new DType.Extension("vortex.timestamp", new DType.Primitive(PType.I64, false), meta, false);
94+
}
95+
}
96+
97+
@Nested
98+
class InstantFromRaw {
99+
100+
@Test
101+
void everyTimeUnitExceptDays() {
102+
// Given / When / Then
103+
assertThat(ExtensionStorage.instantFromRaw(5L, TimeUnit.Seconds))
104+
.isEqualTo(Instant.ofEpochSecond(5));
105+
assertThat(ExtensionStorage.instantFromRaw(5000L, TimeUnit.Milliseconds))
106+
.isEqualTo(Instant.ofEpochMilli(5000));
107+
assertThat(ExtensionStorage.instantFromRaw(1_500_000L, TimeUnit.Microseconds))
108+
.isEqualTo(Instant.ofEpochSecond(1, 500_000_000L));
109+
assertThat(ExtensionStorage.instantFromRaw(1_500_000_000L, TimeUnit.Nanoseconds))
110+
.isEqualTo(Instant.ofEpochSecond(1, 500_000_000L));
111+
}
112+
113+
@Test
114+
void negativeMicrosFloorsCorrectly() {
115+
// Given / When / Then — floorDiv/floorMod path for negative epoch
116+
assertThat(ExtensionStorage.instantFromRaw(-1L, TimeUnit.Microseconds))
117+
.isEqualTo(Instant.ofEpochSecond(-1, 999_999_000L));
118+
assertThat(ExtensionStorage.instantFromRaw(-1L, TimeUnit.Nanoseconds))
119+
.isEqualTo(Instant.ofEpochSecond(-1, 999_999_999L));
120+
}
121+
122+
@Test
123+
void daysRejected() {
124+
assertThatThrownBy(() -> ExtensionStorage.instantFromRaw(1L, TimeUnit.Days))
125+
.isInstanceOf(VortexException.class).hasMessageContaining("Days");
126+
}
127+
}
128+
129+
@Nested
130+
class CheckBounds {
131+
132+
@Test
133+
void validIndexPasses() {
134+
ExtensionStorage.checkBounds(0, 3);
135+
ExtensionStorage.checkBounds(2, 3);
136+
}
137+
138+
@Test
139+
void outOfBoundsThrows() {
140+
assertThatThrownBy(() -> ExtensionStorage.checkBounds(3, 3))
141+
.isInstanceOf(IndexOutOfBoundsException.class);
142+
assertThatThrownBy(() -> ExtensionStorage.checkBounds(-1, 3))
143+
.isInstanceOf(IndexOutOfBoundsException.class);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)