Skip to content

Commit aede11d

Browse files
dfa1claude
andcommitted
test(writer): bit-width sweep property for bitpacked encode/decode
Pin and round-trip every bit width 1..32 (u32) and 1..64 (u64): each case forces its width by including 2^W-1 and fills the rest with random W-bit values, covering the boundaries (1, 8, 31/32, 63/64) the existing curated arrays didn't hit deterministically. encode→decode must be lossless for all 96 widths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a2cf344 commit aede11d

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

writer/src/test/java/io/github/dfa1/vortex/writer/encode/BitpackedEncodingEncoderTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.junit.jupiter.params.provider.MethodSource;
1616

1717
import java.lang.foreign.Arena;
18+
import java.util.Random;
1819
import java.util.stream.Stream;
1920

2021
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,4 +84,62 @@ void encode_i32_metadata_bitWidth_isNonZero() throws Exception {
8384

8485
assertThat(meta.bit_width()).isGreaterThan(0);
8586
}
87+
88+
/// Property: lossless pack/unpack at **every** bit width. Each array forces a specific
89+
/// width by including its max value `2^W - 1`, fills the rest with random values masked to
90+
/// W bits, and checks the boundary widths (1, 7, 8, 31/32, 63/64) explicitly. The random
91+
/// arrays' values don't deterministically exercise every width, so this pins them all.
92+
@ParameterizedTest(name = "u32 width={0}")
93+
@MethodSource("u32Widths")
94+
void encodeDecode_u32_everyBitWidth(int width, int[] data) {
95+
EncodeResult encoded = ENCODER.encode(DTypes.U32, data, EncodeTestHelper.testCtx());
96+
DecodeContext ctx = DecodeTestHelper.toDecodeContext(encoded, data.length, DTypes.U32, REGISTRY);
97+
Array result = DECODER.decode(ctx);
98+
99+
var seg = result.materialize(Arena.ofAuto());
100+
for (int i = 0; i < data.length; i++) {
101+
assertThat(seg.get(PTypeIO.LE_INT, (long) i * 4)).as("width %d index %d", width, i).isEqualTo(data[i]);
102+
}
103+
}
104+
105+
@ParameterizedTest(name = "u64 width={0}")
106+
@MethodSource("u64Widths")
107+
void encodeDecode_u64_everyBitWidth(int width, long[] data) {
108+
EncodeResult encoded = ENCODER.encode(DTypes.U64, data, EncodeTestHelper.testCtx());
109+
DecodeContext ctx = DecodeTestHelper.toDecodeContext(encoded, data.length, DTypes.U64, REGISTRY);
110+
Array result = DECODER.decode(ctx);
111+
112+
var seg = result.materialize(Arena.ofAuto());
113+
for (int i = 0; i < data.length; i++) {
114+
assertThat(seg.get(PTypeIO.LE_LONG, (long) i * 8)).as("width %d index %d", width, i).isEqualTo(data[i]);
115+
}
116+
}
117+
118+
static Stream<Arguments> u32Widths() {
119+
Random rng = new Random(0xB17BACEDL);
120+
return Stream.iterate(1, w -> w <= 32, w -> w + 1).map(w -> {
121+
int mask = w == 32 ? -1 : (1 << w) - 1; // -1 == 0xFFFFFFFF for the full width
122+
int[] a = new int[40];
123+
a[0] = 0;
124+
a[1] = mask; // 2^w - 1 — forces the encoder to pick width w
125+
for (int i = 2; i < a.length; i++) {
126+
a[i] = rng.nextInt() & mask;
127+
}
128+
return Arguments.of(w, a);
129+
});
130+
}
131+
132+
static Stream<Arguments> u64Widths() {
133+
Random rng = new Random(0xB17BACE2L);
134+
return Stream.iterate(1, w -> w <= 64, w -> w + 1).map(w -> {
135+
long mask = w == 64 ? -1L : (1L << w) - 1L;
136+
long[] a = new long[40];
137+
a[0] = 0L;
138+
a[1] = mask;
139+
for (int i = 2; i < a.length; i++) {
140+
a[i] = rng.nextLong() & mask;
141+
}
142+
return Arguments.of(w, a);
143+
});
144+
}
86145
}

0 commit comments

Comments
 (0)