From 005b4ac8dffb593d1c173d9634361d3505b6dae1 Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Sat, 20 Jun 2026 14:14:03 +0200 Subject: [PATCH] test(writer): bit-width sweep property for bitpacked encode/decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../encode/BitpackedEncodingEncoderTest.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/writer/src/test/java/io/github/dfa1/vortex/writer/encode/BitpackedEncodingEncoderTest.java b/writer/src/test/java/io/github/dfa1/vortex/writer/encode/BitpackedEncodingEncoderTest.java index dafe10cd..afd8038a 100644 --- a/writer/src/test/java/io/github/dfa1/vortex/writer/encode/BitpackedEncodingEncoderTest.java +++ b/writer/src/test/java/io/github/dfa1/vortex/writer/encode/BitpackedEncodingEncoderTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.lang.foreign.Arena; +import java.util.Random; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -83,4 +84,62 @@ void encode_i32_metadata_bitWidth_isNonZero() throws Exception { assertThat(meta.bit_width()).isGreaterThan(0); } + + /// Property: lossless pack/unpack at **every** bit width. Each array forces a specific + /// width by including its max value `2^W - 1`, fills the rest with random values masked to + /// W bits, and checks the boundary widths (1, 7, 8, 31/32, 63/64) explicitly. The random + /// arrays' values don't deterministically exercise every width, so this pins them all. + @ParameterizedTest(name = "u32 width={0}") + @MethodSource("u32Widths") + void encodeDecode_u32_everyBitWidth(int width, int[] data) { + EncodeResult encoded = ENCODER.encode(DTypes.U32, data, EncodeTestHelper.testCtx()); + DecodeContext ctx = DecodeTestHelper.toDecodeContext(encoded, data.length, DTypes.U32, REGISTRY); + Array result = DECODER.decode(ctx); + + var seg = result.materialize(Arena.ofAuto()); + for (int i = 0; i < data.length; i++) { + assertThat(seg.get(PTypeIO.LE_INT, (long) i * 4)).as("width %d index %d", width, i).isEqualTo(data[i]); + } + } + + @ParameterizedTest(name = "u64 width={0}") + @MethodSource("u64Widths") + void encodeDecode_u64_everyBitWidth(int width, long[] data) { + EncodeResult encoded = ENCODER.encode(DTypes.U64, data, EncodeTestHelper.testCtx()); + DecodeContext ctx = DecodeTestHelper.toDecodeContext(encoded, data.length, DTypes.U64, REGISTRY); + Array result = DECODER.decode(ctx); + + var seg = result.materialize(Arena.ofAuto()); + for (int i = 0; i < data.length; i++) { + assertThat(seg.get(PTypeIO.LE_LONG, (long) i * 8)).as("width %d index %d", width, i).isEqualTo(data[i]); + } + } + + static Stream u32Widths() { + Random rng = new Random(0xB17BACEDL); + return Stream.iterate(1, w -> w <= 32, w -> w + 1).map(w -> { + int mask = w == 32 ? -1 : (1 << w) - 1; // -1 == 0xFFFFFFFF for the full width + int[] a = new int[40]; + a[0] = 0; + a[1] = mask; // 2^w - 1 — forces the encoder to pick width w + for (int i = 2; i < a.length; i++) { + a[i] = rng.nextInt() & mask; + } + return Arguments.of(w, a); + }); + } + + static Stream u64Widths() { + Random rng = new Random(0xB17BACE2L); + return Stream.iterate(1, w -> w <= 64, w -> w + 1).map(w -> { + long mask = w == 64 ? -1L : (1L << w) - 1L; + long[] a = new long[40]; + a[0] = 0L; + a[1] = mask; + for (int i = 2; i < a.length; i++) { + a[i] = rng.nextLong() & mask; + } + return Arguments.of(w, a); + }); + } }