diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/ArrayLimitedTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/ArrayLimitedTest.java index 531f67fa..927afac6 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/ArrayLimitedTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/ArrayLimitedTest.java @@ -1,68 +1,73 @@ package io.github.dfa1.vortex.reader.array; import io.github.dfa1.vortex.core.DType; -import io.github.dfa1.vortex.core.PType; import io.github.dfa1.vortex.core.VortexException; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; import java.util.List; +import static io.github.dfa1.vortex.encoding.DTypes.BOOL; +import static io.github.dfa1.vortex.encoding.DTypes.F32; +import static io.github.dfa1.vortex.encoding.DTypes.F64; +import static io.github.dfa1.vortex.encoding.DTypes.I16; +import static io.github.dfa1.vortex.encoding.DTypes.I32; +import static io.github.dfa1.vortex.encoding.DTypes.I64; +import static io.github.dfa1.vortex.encoding.DTypes.I8; +import static io.github.dfa1.vortex.reader.array.TestArrays.bools; +import static io.github.dfa1.vortex.reader.array.TestArrays.bytes; +import static io.github.dfa1.vortex.reader.array.TestArrays.doubles; +import static io.github.dfa1.vortex.reader.array.TestArrays.float16; +import static io.github.dfa1.vortex.reader.array.TestArrays.floats; +import static io.github.dfa1.vortex.reader.array.TestArrays.ints; +import static io.github.dfa1.vortex.reader.array.TestArrays.longs; +import static io.github.dfa1.vortex.reader.array.TestArrays.shorts; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /// Tests the [Array#limited(long)] contract: the [Array#limited(Array, long)] guard /// (no-op clamp + negative rejection) and every concrete implementation — -/// zero-copy views, composite child recursion, and the [UnknownArray] rejection. +/// zero-copy views, composite child recursion, chunked prefix retention, and the +/// [UnknownArray] rejection. class ArrayLimitedTest { - private static final DType I64 = new DType.Primitive(PType.I64, false); - @Nested class Guard { @Test void rowsEqualToLengthReturnsSameInstance() { - try (Arena arena = Arena.ofConfined()) { - // Given - LongArray sut = longs(arena, 1L, 2L, 3L); + // Given + LongArray sut = longs(1L, 2L, 3L); - // When - Array result = Array.limited(sut, 3); + // When + Array result = Array.limited(sut, 3); - // Then - assertThat(result).isSameAs(sut); - } + // Then + assertThat(result).isSameAs(sut); } @Test void rowsBiggerThanLengthReturnsSameInstance() { - try (Arena arena = Arena.ofConfined()) { - // Given — limit is min(length, rows): asking for more than exists is a no-op - LongArray sut = longs(arena, 1L, 2L, 3L); + // Given — limit is min(length, rows): asking for more than exists is a no-op + LongArray sut = longs(1L, 2L, 3L); - // When - Array result = Array.limited(sut, 99); + // When + Array result = Array.limited(sut, 99); - // Then - assertThat(result).isSameAs(sut); - } + // Then + assertThat(result).isSameAs(sut); } @Test void negativeRowsThrows() { - try (Arena arena = Arena.ofConfined()) { - // Given - LongArray sut = longs(arena, 1L); + // Given + LongArray sut = longs(1L); - // When / Then - assertThatThrownBy(() -> Array.limited(sut, -1)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(">= 0"); - } + // When / Then + assertThatThrownBy(() -> Array.limited(sut, -1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(">= 0"); } } @@ -71,34 +76,30 @@ class Primitive { @Test void cutsToFirstRowsAsView() { - try (Arena arena = Arena.ofConfined()) { - // Given - LongArray sut = longs(arena, 10L, 20L, 30L, 40L); + // Given + LongArray sut = longs(10L, 20L, 30L, 40L); - // When - Array result = sut.limited(2); + // When + Array result = sut.limited(2); - // Then - assertThat(result.length()).isEqualTo(2L); - assertThat(((LongArray) result).getLong(0)).isEqualTo(10L); - assertThat(((LongArray) result).getLong(1)).isEqualTo(20L); - } + // Then + assertThat(result.length()).isEqualTo(2L); + assertThat(((LongArray) result).getLong(0)).isEqualTo(10L); + assertThat(((LongArray) result).getLong(1)).isEqualTo(20L); } @Test void float16SlicesBuffer() { - try (Arena arena = Arena.ofConfined()) { - // Given - Float16Array sut = float16(arena, 1.0f, 2.0f, 3.0f); + // Given + Float16Array sut = float16(1.0f, 2.0f, 3.0f); - // When - Array result = sut.limited(2); + // When + Array result = sut.limited(2); - // Then - assertThat(result.length()).isEqualTo(2L); - assertThat(((Float16Array) result).getFloat(0)).isEqualTo(1.0f); - assertThat(((Float16Array) result).getFloat(1)).isEqualTo(2.0f); - } + // Then + assertThat(result.length()).isEqualTo(2L); + assertThat(((Float16Array) result).getFloat(0)).isEqualTo(1.0f); + assertThat(((Float16Array) result).getFloat(1)).isEqualTo(2.0f); } } @@ -107,107 +108,210 @@ class Composite { @Test void structLimitsEachField() { - try (Arena arena = Arena.ofConfined()) { - // Given - DType.Struct dtype = new DType.Struct(List.of("a", "b"), List.of(I64, I64), false); - StructArray sut = new StructArray(dtype, 3, - List.of(longs(arena, 1L, 2L, 3L), longs(arena, 10L, 20L, 30L))); + // Given + DType.Struct dtype = new DType.Struct(List.of("a", "b"), List.of(I64, I64), false); + StructArray sut = new StructArray(dtype, 3, + List.of(longs(1L, 2L, 3L), longs(10L, 20L, 30L))); - // When - StructArray result = (StructArray) sut.limited(2); + // When + StructArray result = (StructArray) sut.limited(2); - // Then - assertThat(result.length()).isEqualTo(2L); - assertThat(result.field(0).length()).isEqualTo(2L); - assertThat(((LongArray) result.field(1)).getLong(1)).isEqualTo(20L); - } + // Then + assertThat(result.length()).isEqualTo(2L); + assertThat(result.field(0).length()).isEqualTo(2L); + assertThat(((LongArray) result.field(1)).getLong(1)).isEqualTo(20L); } @Test void listLimitsOffsetsToRowsPlusOne() { - try (Arena arena = Arena.ofConfined()) { - // Given — 3 lists over offsets [0,2,3,5]; elements shared - DType.List dtype = new DType.List(I64, false); - LongArray elements = longs(arena, 7L, 7L, 8L, 9L, 9L); - LongArray offsets = longs(arena, 0L, 2L, 3L, 5L); - ListArray sut = new ListArray(dtype, 3, elements, offsets); - - // When - ListArray result = (ListArray) sut.limited(2); - - // Then — offsets keep rows+1 = 3 entries so list[1] bounds stay readable - assertThat(result.length()).isEqualTo(2L); - assertThat(result.offsets().length()).isEqualTo(3L); - assertThat(result.elements()).isSameAs(elements); - } + // Given — 3 lists over offsets [0,2,3,5]; elements shared + DType.List dtype = new DType.List(I64, false); + LongArray elements = longs(7L, 7L, 8L, 9L, 9L); + LongArray offsets = longs(0L, 2L, 3L, 5L); + ListArray sut = new ListArray(dtype, 3, elements, offsets); + + // When + ListArray result = (ListArray) sut.limited(2); + + // Then — offsets keep rows+1 = 3 entries so list[1] bounds stay readable + assertThat(result.length()).isEqualTo(2L); + assertThat(result.offsets().length()).isEqualTo(3L); + assertThat(result.elements()).isSameAs(elements); } @Test void listViewLimitsOffsetsAndSizes() { - try (Arena arena = Arena.ofConfined()) { - // Given - DType.List dtype = new DType.List(I64, false); - LongArray elements = longs(arena, 1L, 2L, 3L, 4L); - LongArray offsets = longs(arena, 0L, 2L, 3L); - LongArray sizes = longs(arena, 2L, 1L, 1L); - ListViewArray sut = new ListViewArray(dtype, 3, elements, offsets, sizes); - - // When - ListViewArray result = (ListViewArray) sut.limited(2); - - // Then - assertThat(result.length()).isEqualTo(2L); - assertThat(result.offsets().length()).isEqualTo(2L); - assertThat(result.sizes().length()).isEqualTo(2L); - } + // Given + DType.List dtype = new DType.List(I64, false); + LongArray elements = longs(1L, 2L, 3L, 4L); + LongArray offsets = longs(0L, 2L, 3L); + LongArray sizes = longs(2L, 1L, 1L); + ListViewArray sut = new ListViewArray(dtype, 3, elements, offsets, sizes); + + // When + ListViewArray result = (ListViewArray) sut.limited(2); + + // Then + assertThat(result.length()).isEqualTo(2L); + assertThat(result.offsets().length()).isEqualTo(2L); + assertThat(result.sizes().length()).isEqualTo(2L); } @Test void fixedSizeListLimitsElementsByWidth() { - try (Arena arena = Arena.ofConfined()) { - // Given — fixedSize 2: 3 rows -> 6 elements - DType.FixedSizeList dtype = new DType.FixedSizeList(I64, 2, false); - FixedSizeListArray sut = new FixedSizeListArray(dtype, 3, - longs(arena, 1L, 2L, 3L, 4L, 5L, 6L)); + // Given — fixedSize 2: 3 rows -> 6 elements + DType.FixedSizeList dtype = new DType.FixedSizeList(I64, 2, false); + FixedSizeListArray sut = new FixedSizeListArray(dtype, 3, + longs(1L, 2L, 3L, 4L, 5L, 6L)); - // When - FixedSizeListArray result = (FixedSizeListArray) sut.limited(2); + // When + FixedSizeListArray result = (FixedSizeListArray) sut.limited(2); - // Then — 2 rows -> 4 elements - assertThat(result.length()).isEqualTo(2L); - assertThat(result.elements().length()).isEqualTo(4L); - } + // Then — 2 rows -> 4 elements + assertThat(result.length()).isEqualTo(2L); + assertThat(result.elements().length()).isEqualTo(4L); } @Test void variantLimitsCoreAndShredded() { - try (Arena arena = Arena.ofConfined()) { - // Given - VariantArray sut = new VariantArray(I64, 3, - longs(arena, 1L, 2L, 3L), longs(arena, 4L, 5L, 6L)); + // Given + VariantArray sut = new VariantArray(I64, 3, longs(1L, 2L, 3L), longs(4L, 5L, 6L)); - // When - VariantArray result = (VariantArray) sut.limited(2); + // When + VariantArray result = (VariantArray) sut.limited(2); - // Then - assertThat(result.length()).isEqualTo(2L); - assertThat(result.coreStorage().length()).isEqualTo(2L); - assertThat(result.shredded().length()).isEqualTo(2L); - } + // Then + assertThat(result.length()).isEqualTo(2L); + assertThat(result.coreStorage().length()).isEqualTo(2L); + assertThat(result.shredded().length()).isEqualTo(2L); } @Test void variantWithNullShreddedStaysNull() { - try (Arena arena = Arena.ofConfined()) { - // Given - VariantArray sut = new VariantArray(I64, 3, longs(arena, 1L, 2L, 3L), null); + // Given + VariantArray sut = new VariantArray(I64, 3, longs(1L, 2L, 3L), null); - // When - VariantArray result = (VariantArray) sut.limited(2); + // When + VariantArray result = (VariantArray) sut.limited(2); - // Then - assertThat(result.shredded()).isNull(); - } + // Then + assertThat(result.shredded()).isNull(); + } + } + + @Nested + class Chunked { + + @Test + void limitAcrossBoundaryKeepsPrefixAndCutsBoundaryChild() { + // Given — two chunks [0,1,2][3,4]; limit 4 lands inside the second chunk + ChunkedLongArray sut = ChunkedLongArray.of(I64, 5, + List.of(longs(0L, 1L, 2L), longs(3L, 4L))); + + // When + Array result = sut.limited(4); + + // Then — first chunk kept whole, boundary chunk truncated to 1 row + assertThat(result.length()).isEqualTo(4L); + assertThat(((LongArray) result).getLong(0)).isEqualTo(0L); + assertThat(((LongArray) result).getLong(3)).isEqualTo(3L); + } + + @Test + void limitWithinFirstChunkDropsLaterChunks() { + // Given — two chunks; limit 2 falls inside the first + ChunkedLongArray sut = ChunkedLongArray.of(I64, 5, + List.of(longs(0L, 1L, 2L), longs(3L, 4L))); + + // When + Array result = sut.limited(2); + + // Then — only the (truncated) first chunk survives + assertThat(result.length()).isEqualTo(2L); + assertThat(((LongArray) result).getLong(1)).isEqualTo(1L); + } + + @Test + void intChunkedLimitsAcrossBoundary() { + // Given + ChunkedIntArray sut = ChunkedIntArray.of(I32, 4, List.of(ints(0, 1), ints(2, 3))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((IntArray) result).getInt(2)).isEqualTo(2); + } + + @Test + void doubleChunkedLimitsAcrossBoundary() { + // Given + ChunkedDoubleArray sut = ChunkedDoubleArray.of(F64, 4, + List.of(doubles(0.5, 1.5), doubles(2.5, 3.5))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((DoubleArray) result).getDouble(2)).isEqualTo(2.5); + } + + @Test + void floatChunkedLimitsAcrossBoundary() { + // Given + ChunkedFloatArray sut = ChunkedFloatArray.of(F32, 4, + List.of(floats(0.5f, 1.5f), floats(2.5f, 3.5f))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((FloatArray) result).getFloat(2)).isEqualTo(2.5f); + } + + @Test + void shortChunkedLimitsAcrossBoundary() { + // Given + ChunkedShortArray sut = ChunkedShortArray.of(I16, 4, + List.of(shorts((short) 0, (short) 1), shorts((short) 2, (short) 3))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((ShortArray) result).getShort(2)).isEqualTo((short) 2); + } + + @Test + void byteChunkedLimitsAcrossBoundary() { + // Given + ChunkedByteArray sut = ChunkedByteArray.of(I8, 4, + List.of(bytes((byte) 0, (byte) 1), bytes((byte) 2, (byte) 3))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((ByteArray) result).getByte(2)).isEqualTo((byte) 2); + } + + @Test + void boolChunkedLimitsAcrossBoundary() { + // Given + ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 4, + List.of(bools(true, false), bools(true, true))); + + // When + Array result = sut.limited(3); + + // Then + assertThat(result.length()).isEqualTo(3L); + assertThat(((BoolArray) result).getBoolean(2)).isTrue(); } } @@ -226,20 +330,4 @@ void unknownArrayThrows() { .hasMessageContaining("vortex.mystery"); } } - - private static LongArray longs(Arena arena, long... vs) { - MemorySegment seg = arena.allocate(vs.length * 8L, 8); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_LONG, i, vs[i]); - } - return new MaterializedLongArray(I64, vs.length, seg.asReadOnly()); - } - - private static Float16Array float16(Arena arena, float... vs) { - MemorySegment seg = arena.allocate(vs.length * 2L, 2); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_SHORT, i, Float.floatToFloat16(vs[i])); - } - return new MaterializedFloat16Array(new DType.Primitive(PType.F16, false), vs.length, seg.asReadOnly()); - } } diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/ChunkedRecordSmokeTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/ChunkedRecordSmokeTest.java index 8e4eb337..f5f5548c 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/ChunkedRecordSmokeTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/ChunkedRecordSmokeTest.java @@ -1,26 +1,31 @@ package io.github.dfa1.vortex.reader.array; -import io.github.dfa1.vortex.core.DType; -import io.github.dfa1.vortex.core.PType; import io.github.dfa1.vortex.core.VortexException; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; import java.util.ArrayList; import java.util.List; +import static io.github.dfa1.vortex.encoding.DTypes.BOOL; +import static io.github.dfa1.vortex.encoding.DTypes.F32; +import static io.github.dfa1.vortex.encoding.DTypes.F64; +import static io.github.dfa1.vortex.encoding.DTypes.I16; +import static io.github.dfa1.vortex.encoding.DTypes.I32; +import static io.github.dfa1.vortex.encoding.DTypes.I64; +import static io.github.dfa1.vortex.encoding.DTypes.I8; +import static io.github.dfa1.vortex.reader.array.TestArrays.bools; +import static io.github.dfa1.vortex.reader.array.TestArrays.bytes; +import static io.github.dfa1.vortex.reader.array.TestArrays.doubles; +import static io.github.dfa1.vortex.reader.array.TestArrays.floats; +import static io.github.dfa1.vortex.reader.array.TestArrays.ints; +import static io.github.dfa1.vortex.reader.array.TestArrays.longs; +import static io.github.dfa1.vortex.reader.array.TestArrays.shorts; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class ChunkedRecordSmokeTest { - private static final DType I64 = new DType.Primitive(PType.I64, false); - private static final DType I32 = new DType.Primitive(PType.I32, false); - private static final DType F64 = new DType.Primitive(PType.F64, false); - private static final DType F32 = new DType.Primitive(PType.F32, false); - @Nested class FindChunk { @@ -75,65 +80,57 @@ void emptyChunkListRejected() { @Test void rowMismatchRejected() { - try (Arena arena = Arena.ofConfined()) { - // Given - LongArray c0 = longChunk(arena, 1L, 2L, 3L); - LongArray c1 = longChunk(arena, 4L, 5L); - - // When / Then - assertThatThrownBy(() -> ChunkedLongArray.of(I64, 99, List.of(c0, c1))) - .isInstanceOf(VortexException.class); - } + // Given + LongArray c0 = longs(1L, 2L, 3L); + LongArray c1 = longs(4L, 5L); + + // When / Then + assertThatThrownBy(() -> ChunkedLongArray.of(I64, 99, List.of(c0, c1))) + .isInstanceOf(VortexException.class); } @Test void getLongDispatchesAcrossChunks() { - try (Arena arena = Arena.ofConfined()) { - // Given two chunks: [10,11,12] and [20,21,22,23] - LongArray c0 = longChunk(arena, 10L, 11L, 12L); - LongArray c1 = longChunk(arena, 20L, 21L, 22L, 23L); - ChunkedLongArray sut = ChunkedLongArray.of(I64, 7, List.of(c0, c1)); - - // When / Then - assertThat(sut.getLong(0)).isEqualTo(10L); - assertThat(sut.getLong(2)).isEqualTo(12L); - assertThat(sut.getLong(3)).isEqualTo(20L); - assertThat(sut.getLong(6)).isEqualTo(23L); - } + // Given two chunks: [10,11,12] and [20,21,22,23] + LongArray c0 = longs(10L, 11L, 12L); + LongArray c1 = longs(20L, 21L, 22L, 23L); + ChunkedLongArray sut = ChunkedLongArray.of(I64, 7, List.of(c0, c1)); + + // When / Then + assertThat(sut.getLong(0)).isEqualTo(10L); + assertThat(sut.getLong(2)).isEqualTo(12L); + assertThat(sut.getLong(3)).isEqualTo(20L); + assertThat(sut.getLong(6)).isEqualTo(23L); } @Test void nestedChunkedFlattens() { - try (Arena arena = Arena.ofConfined()) { - // Given a nested ChunkedLongArray as one chunk - LongArray leaf0 = longChunk(arena, 1L, 2L); - LongArray leaf1 = longChunk(arena, 3L); - ChunkedLongArray nested = ChunkedLongArray.of(I64, 3, List.of(leaf0, leaf1)); - LongArray leaf2 = longChunk(arena, 4L, 5L); - - // When - ChunkedLongArray sut = ChunkedLongArray.of(I64, 5, List.of(nested, leaf2)); - - // Then chunks were flattened — 3 children, not 2 - assertThat(sut.children()).hasSize(3); - assertThat(sut.getLong(4)).isEqualTo(5L); - } + // Given a nested ChunkedLongArray as one chunk + LongArray leaf0 = longs(1L, 2L); + LongArray leaf1 = longs(3L); + ChunkedLongArray nested = ChunkedLongArray.of(I64, 3, List.of(leaf0, leaf1)); + LongArray leaf2 = longs(4L, 5L); + + // When + ChunkedLongArray sut = ChunkedLongArray.of(I64, 5, List.of(nested, leaf2)); + + // Then chunks were flattened — 3 children, not 2 + assertThat(sut.children()).hasSize(3); + assertThat(sut.getLong(4)).isEqualTo(5L); } @Test void foldIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - // Given - LongArray c0 = longChunk(arena, 1L, 2L); - LongArray c1 = longChunk(arena, 3L, 4L); - ChunkedLongArray sut = ChunkedLongArray.of(I64, 4, List.of(c0, c1)); - - // When - long result = sut.fold(0L, Long::sum); - - // Then - assertThat(result).isEqualTo(10L); - } + // Given + LongArray c0 = longs(1L, 2L); + LongArray c1 = longs(3L, 4L); + ChunkedLongArray sut = ChunkedLongArray.of(I64, 4, List.of(c0, c1)); + + // When + long result = sut.fold(0L, Long::sum); + + // Then + assertThat(result).isEqualTo(10L); } } @@ -142,105 +139,89 @@ class CrossTypePrimitives { @Test void chunkedDoubleSeesValues() { - try (Arena arena = Arena.ofConfined()) { - // Given - DoubleArray c0 = doubleChunk(arena, 1.5, 2.5); - DoubleArray c1 = doubleChunk(arena, 3.5); - ChunkedDoubleArray sut = ChunkedDoubleArray.of(F64, 3, List.of(c0, c1)); - - // When / Then - assertThat(sut.getDouble(2)).isEqualTo(3.5); - } + // Given + DoubleArray c0 = doubles(1.5, 2.5); + DoubleArray c1 = doubles(3.5); + ChunkedDoubleArray sut = ChunkedDoubleArray.of(F64, 3, List.of(c0, c1)); + + // When / Then + assertThat(sut.getDouble(2)).isEqualTo(3.5); } @Test void chunkedIntSeesValues() { - try (Arena arena = Arena.ofConfined()) { - // Given - IntArray c0 = intChunk(arena, 1, 2); - IntArray c1 = intChunk(arena, 3, 4); - ChunkedIntArray sut = ChunkedIntArray.of(I32, 4, List.of(c0, c1)); - - // When / Then - assertThat(sut.getInt(3)).isEqualTo(4); - } + // Given + IntArray c0 = ints(1, 2); + IntArray c1 = ints(3, 4); + ChunkedIntArray sut = ChunkedIntArray.of(I32, 4, List.of(c0, c1)); + + // When / Then + assertThat(sut.getInt(3)).isEqualTo(4); } @Test void chunkedFloatSeesValues() { - try (Arena arena = Arena.ofConfined()) { - // Given - FloatArray c0 = floatChunk(arena, 1.5f, 2.5f); - FloatArray c1 = floatChunk(arena, 3.5f); - ChunkedFloatArray sut = ChunkedFloatArray.of(F32, 3, List.of(c0, c1)); - - // When / Then - assertThat(sut.getFloat(0)).isEqualTo(1.5f); - assertThat(sut.getFloat(2)).isEqualTo(3.5f); - } + // Given + FloatArray c0 = floats(1.5f, 2.5f); + FloatArray c1 = floats(3.5f); + ChunkedFloatArray sut = ChunkedFloatArray.of(F32, 3, List.of(c0, c1)); + + // When / Then + assertThat(sut.getFloat(0)).isEqualTo(1.5f); + assertThat(sut.getFloat(2)).isEqualTo(3.5f); } @Test void wrongTypeRejected() { - try (Arena arena = Arena.ofConfined()) { - // Given — stuffing a LongArray into a Double container is a bug - LongArray longChunk = longChunk(arena, 1L); - - // When / Then - assertThatThrownBy(() -> ChunkedDoubleArray.of(F64, 1, List.of(longChunk))) - .isInstanceOf(VortexException.class); - } + // Given — stuffing a LongArray into a Double container is a bug + LongArray longChunk = longs(1L); + + // When / Then + assertThatThrownBy(() -> ChunkedDoubleArray.of(F64, 1, List.of(longChunk))) + .isInstanceOf(VortexException.class); } } @Nested class ChunkedShort { - private static final DType I16 = new DType.Primitive(PType.I16, false); - @Test void getShortDispatchesAcrossChunks() { - try (Arena arena = Arena.ofConfined()) { - // Given - ShortArray c0 = shortChunk(arena, (short) 10, (short) 11); - ShortArray c1 = shortChunk(arena, (short) 20, (short) 21); - ChunkedShortArray sut = ChunkedShortArray.of(I16, 4, List.of(c0, c1)); - - // When / Then - assertThat(sut.getShort(0)).isEqualTo((short) 10); - assertThat(sut.getShort(2)).isEqualTo((short) 20); - assertThat(sut.getShort(3)).isEqualTo((short) 21); - } + // Given + ShortArray c0 = shorts((short) 10, (short) 11); + ShortArray c1 = shorts((short) 20, (short) 21); + ChunkedShortArray sut = ChunkedShortArray.of(I16, 4, List.of(c0, c1)); + + // When / Then + assertThat(sut.getShort(0)).isEqualTo((short) 10); + assertThat(sut.getShort(2)).isEqualTo((short) 20); + assertThat(sut.getShort(3)).isEqualTo((short) 21); } @Test void getIntWidens() { - try (Arena arena = Arena.ofConfined()) { - // Given - ShortArray c0 = shortChunk(arena, (short) -1, (short) 2); - ShortArray c1 = shortChunk(arena, (short) 3); - ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); - - // When / Then — I16 is signed, so sign-extends - assertThat(sut.getInt(0)).isEqualTo(-1); - assertThat(sut.getInt(2)).isEqualTo(3); - } + // Given + ShortArray c0 = shorts((short) -1, (short) 2); + ShortArray c1 = shorts((short) 3); + ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); + + // When / Then — I16 is signed, so sign-extends + assertThat(sut.getInt(0)).isEqualTo(-1); + assertThat(sut.getInt(2)).isEqualTo(3); } @Test void foldIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - // Given - ShortArray c0 = shortChunk(arena, (short) 1, (short) 2); - ShortArray c1 = shortChunk(arena, (short) 3); - ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); - - // When - long result = sut.fold(0L, Long::sum); - - // Then - assertThat(result).isEqualTo(6L); - } + // Given + ShortArray c0 = shorts((short) 1, (short) 2); + ShortArray c1 = shorts((short) 3); + ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); + + // When + long result = sut.fold(0L, Long::sum); + + // Then + assertThat(result).isEqualTo(6L); } @Test @@ -252,194 +233,130 @@ void emptyRejected() { @Test void forEachShortIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - // Given - ShortArray c0 = shortChunk(arena, (short) 1, (short) 2); - ShortArray c1 = shortChunk(arena, (short) 3); - ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); - - // When - var seen = new ArrayList(); - sut.forEachShort(seen::add); - - // Then - assertThat(seen).containsExactly((short) 1, (short) 2, (short) 3); - } + // Given + ShortArray c0 = shorts((short) 1, (short) 2); + ShortArray c1 = shorts((short) 3); + ChunkedShortArray sut = ChunkedShortArray.of(I16, 3, List.of(c0, c1)); + + // When + var seen = new ArrayList(); + sut.forEachShort(seen::add); + + // Then + assertThat(seen).containsExactly((short) 1, (short) 2, (short) 3); } } @Nested class ChunkedByte { - private static final DType I8 = new DType.Primitive(PType.I8, false); - @Test void getByteDispatchesAcrossChunks() { - try (Arena arena = Arena.ofConfined()) { - ByteArray c0 = byteChunk(arena, (byte) 1, (byte) 2); - ByteArray c1 = byteChunk(arena, (byte) 3, (byte) 4); - ChunkedByteArray sut = ChunkedByteArray.of(I8, 4, List.of(c0, c1)); - - assertThat(sut.getByte(0)).isEqualTo((byte) 1); - assertThat(sut.getByte(2)).isEqualTo((byte) 3); - assertThat(sut.getByte(3)).isEqualTo((byte) 4); - } + // Given + ByteArray c0 = bytes((byte) 1, (byte) 2); + ByteArray c1 = bytes((byte) 3, (byte) 4); + ChunkedByteArray sut = ChunkedByteArray.of(I8, 4, List.of(c0, c1)); + + // When / Then + assertThat(sut.getByte(0)).isEqualTo((byte) 1); + assertThat(sut.getByte(2)).isEqualTo((byte) 3); + assertThat(sut.getByte(3)).isEqualTo((byte) 4); } @Test void foldIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - ByteArray c0 = byteChunk(arena, (byte) 10, (byte) 20); - ByteArray c1 = byteChunk(arena, (byte) 30); - ChunkedByteArray sut = ChunkedByteArray.of(I8, 3, List.of(c0, c1)); + // Given + ByteArray c0 = bytes((byte) 10, (byte) 20); + ByteArray c1 = bytes((byte) 30); + ChunkedByteArray sut = ChunkedByteArray.of(I8, 3, List.of(c0, c1)); - long sum = sut.fold(0L, Long::sum); + // When + long result = sut.fold(0L, Long::sum); - assertThat(sum).isEqualTo(60L); - } + // Then + assertThat(result).isEqualTo(60L); } @Test void rowMismatchRejected() { - try (Arena arena = Arena.ofConfined()) { - ByteArray c0 = byteChunk(arena, (byte) 1, (byte) 2); - assertThatThrownBy(() -> ChunkedByteArray.of(I8, 99, List.of(c0))) - .isInstanceOf(VortexException.class); - } + // Given + ByteArray c0 = bytes((byte) 1, (byte) 2); + + // When / Then + assertThatThrownBy(() -> ChunkedByteArray.of(I8, 99, List.of(c0))) + .isInstanceOf(VortexException.class); } @Test void forEachByteIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - ByteArray c0 = byteChunk(arena, (byte) 10, (byte) 20); - ByteArray c1 = byteChunk(arena, (byte) 30); - ChunkedByteArray sut = ChunkedByteArray.of(I8, 3, List.of(c0, c1)); + // Given + ByteArray c0 = bytes((byte) 10, (byte) 20); + ByteArray c1 = bytes((byte) 30); + ChunkedByteArray sut = ChunkedByteArray.of(I8, 3, List.of(c0, c1)); - var seen = new ArrayList(); - sut.forEachByte(seen::add); + // When + var seen = new ArrayList(); + sut.forEachByte(seen::add); - assertThat(seen).containsExactly((byte) 10, (byte) 20, (byte) 30); - } + // Then + assertThat(seen).containsExactly((byte) 10, (byte) 20, (byte) 30); } } @Nested class ChunkedBool { - private static final DType BOOL = new DType.Bool(false); - @Test void getBooleanDispatchesAcrossChunks() { - try (Arena arena = Arena.ofConfined()) { - // chunk 0: true, false, true (3 bits, fits 1 byte) - BoolArray c0 = boolChunk(arena, true, false, true); - // chunk 1: false, true (2 bits) - BoolArray c1 = boolChunk(arena, false, true); - ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 5, List.of(c0, c1)); - - assertThat(sut.getBoolean(0)).isTrue(); - assertThat(sut.getBoolean(1)).isFalse(); - assertThat(sut.getBoolean(2)).isTrue(); - assertThat(sut.getBoolean(3)).isFalse(); - assertThat(sut.getBoolean(4)).isTrue(); - } + // Given chunk 0: true,false,true (3 bits); chunk 1: false,true (2 bits) + BoolArray c0 = bools(true, false, true); + BoolArray c1 = bools(false, true); + ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 5, List.of(c0, c1)); + + // When / Then + assertThat(sut.getBoolean(0)).isTrue(); + assertThat(sut.getBoolean(1)).isFalse(); + assertThat(sut.getBoolean(2)).isTrue(); + assertThat(sut.getBoolean(3)).isFalse(); + assertThat(sut.getBoolean(4)).isTrue(); } @Test void nestedFlattens() { - try (Arena arena = Arena.ofConfined()) { - BoolArray leaf0 = boolChunk(arena, true); - BoolArray leaf1 = boolChunk(arena, false); - ChunkedBoolArray nested = ChunkedBoolArray.of(BOOL, 2, List.of(leaf0, leaf1)); - BoolArray leaf2 = boolChunk(arena, true); + // Given + BoolArray leaf0 = bools(true); + BoolArray leaf1 = bools(false); + ChunkedBoolArray nested = ChunkedBoolArray.of(BOOL, 2, List.of(leaf0, leaf1)); + BoolArray leaf2 = bools(true); - ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 3, List.of(nested, leaf2)); + // When + ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 3, List.of(nested, leaf2)); - assertThat(sut.children()).hasSize(3); - assertThat(sut.getBoolean(2)).isTrue(); - } + // Then + assertThat(sut.children()).hasSize(3); + assertThat(sut.getBoolean(2)).isTrue(); } @Test void emptyRejected() { + // Given / When / Then assertThatThrownBy(() -> ChunkedBoolArray.of(BOOL, 0, List.of())) .isInstanceOf(VortexException.class); } @Test void forEachBooleanIteratesChildren() { - try (Arena arena = Arena.ofConfined()) { - BoolArray c0 = boolChunk(arena, true, false); - BoolArray c1 = boolChunk(arena, true); - ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 3, List.of(c0, c1)); - - var seen = new ArrayList(); - sut.forEachBoolean(seen::add); - - assertThat(seen).containsExactly(true, false, true); - } - } - } - - private static LongArray longChunk(Arena arena, long... values) { - MemorySegment seg = arena.allocate(values.length * 8L); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(java.lang.foreign.ValueLayout.JAVA_LONG, i, values[i]); - } - return new MaterializedLongArray(I64, values.length, seg.asReadOnly()); - } - - private static IntArray intChunk(Arena arena, int... values) { - MemorySegment seg = arena.allocate(values.length * 4L); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(java.lang.foreign.ValueLayout.JAVA_INT, i, values[i]); - } - return new MaterializedIntArray(I32, values.length, seg.asReadOnly()); - } - - private static DoubleArray doubleChunk(Arena arena, double... values) { - MemorySegment seg = arena.allocate(values.length * 8L); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(java.lang.foreign.ValueLayout.JAVA_DOUBLE, i, values[i]); - } - return new MaterializedDoubleArray(F64, values.length, seg.asReadOnly()); - } - - private static FloatArray floatChunk(Arena arena, float... values) { - MemorySegment seg = arena.allocate(values.length * 4L); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(java.lang.foreign.ValueLayout.JAVA_FLOAT, i, values[i]); - } - return new MaterializedFloatArray(F32, values.length, seg.asReadOnly()); - } - - private static ShortArray shortChunk(Arena arena, short... values) { - MemorySegment seg = arena.allocate(values.length * 2L); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(java.lang.foreign.ValueLayout.JAVA_SHORT, i, values[i]); - } - return new MaterializedShortArray(new DType.Primitive(PType.I16, false), values.length, seg.asReadOnly()); - } + // Given + BoolArray c0 = bools(true, false); + BoolArray c1 = bools(true); + ChunkedBoolArray sut = ChunkedBoolArray.of(BOOL, 3, List.of(c0, c1)); - private static ByteArray byteChunk(Arena arena, byte... values) { - MemorySegment seg = arena.allocate(values.length); - for (int i = 0; i < values.length; i++) { - seg.set(java.lang.foreign.ValueLayout.JAVA_BYTE, i, values[i]); - } - return new MaterializedByteArray(new DType.Primitive(PType.I8, false), values.length, seg.asReadOnly()); - } + // When + var seen = new ArrayList(); + sut.forEachBoolean(seen::add); - private static BoolArray boolChunk(Arena arena, boolean... values) { - int nBytes = (values.length + 7) / 8; - MemorySegment seg = arena.allocate(nBytes); - for (int i = 0; i < values.length; i++) { - if (values[i]) { - int byteIdx = i >>> 3; - int bitIdx = i & 7; - byte cur = seg.get(java.lang.foreign.ValueLayout.JAVA_BYTE, byteIdx); - seg.set(java.lang.foreign.ValueLayout.JAVA_BYTE, byteIdx, (byte) (cur | (1 << bitIdx))); - } + // Then + assertThat(seen).containsExactly(true, false, true); } - return new MaterializedBoolArray(new DType.Bool(false), values.length, seg.asReadOnly()); } } diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/DoubleArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/DoubleArrayTest.java index 3608500c..3fefc5e4 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/DoubleArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/DoubleArrayTest.java @@ -21,12 +21,7 @@ class DoubleArrayTest { ValueLayout.JAVA_DOUBLE_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); private static DoubleArray of(double... values) { - MemorySegment seg = Arena.ofAuto().allocate((long) values.length * 8, 8); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(LE_DOUBLE, i, values[i]); - } - DType dtype = new DType.Primitive(PType.F64, false); - return new MaterializedDoubleArray(dtype, values.length, seg); + return TestArrays.doubles(values); } @Nested diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/IntArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/IntArrayTest.java index 4dcb0910..8fb3cf8b 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/IntArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/IntArrayTest.java @@ -21,12 +21,7 @@ class IntArrayTest { ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); private static IntArray of(int... values) { - MemorySegment seg = Arena.ofAuto().allocate((long) values.length * 4, 4); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(LE_INT, i, values[i]); - } - DType dtype = new DType.Primitive(PType.I32, false); - return new MaterializedIntArray(dtype, values.length, seg); + return TestArrays.ints(values); } @Nested diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazyDateTimePartsLongArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazyDateTimePartsLongArrayTest.java index 91409601..149bed4e 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazyDateTimePartsLongArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazyDateTimePartsLongArrayTest.java @@ -1,13 +1,10 @@ package io.github.dfa1.vortex.reader.array; -import io.github.dfa1.vortex.core.DType; -import io.github.dfa1.vortex.core.PType; import org.junit.jupiter.api.Test; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - +import static io.github.dfa1.vortex.encoding.DTypes.I64; +import static io.github.dfa1.vortex.reader.array.TestArrays.ints; +import static io.github.dfa1.vortex.reader.array.TestArrays.longs; import static org.assertj.core.api.Assertions.assertThat; /// Unit tests for {@link LazyDateTimePartsLongArray}. Verifies the @@ -16,80 +13,56 @@ /// that lets each child use whatever signed-integer ptype the encoder picked. class LazyDateTimePartsLongArrayTest { - private static final DType I64 = new DType.Primitive(PType.I64, false); - private static final DType I32 = new DType.Primitive(PType.I32, false); - // Decoder constructs records carrying the parent Extension dtype; use I64 here - // as a stand-in since the record never inspects dtype semantics directly. - @Test void millisecondsReassembly() { - // Given 2 rows of arbitrary (days, seconds_in_day, subseconds) for ms unit. - // unitsPerSecond = 1_000; unitsPerDay = 86_400_000. - // Row 0: days=20_000 -> 2024-12-31 area, seconds=12345, subseconds=678 -> 1_728_012_345_678 - try (Arena arena = Arena.ofConfined()) { - LongArray days = longArray(arena, 20_000L, 0L); - LongArray seconds = longArray(arena, 12_345L, 0L); - LongArray subseconds = longArray(arena, 678L, 0L); - long unitsPerSecond = 1_000L; - long unitsPerDay = 86_400L * unitsPerSecond; - - var sut = new LazyDateTimePartsLongArray(I64, 2, - days, seconds, subseconds, unitsPerDay, unitsPerSecond); - - assertThat(sut.getLong(0)).isEqualTo( - 20_000L * unitsPerDay + 12_345L * unitsPerSecond + 678L); - assertThat(sut.getLong(1)).isZero(); - } + // Given 2 rows of (days, seconds_in_day, subseconds) for ms unit: + // unitsPerSecond = 1_000; unitsPerDay = 86_400_000; row 0 -> 1_728_012_345_678 + LongArray days = longs(20_000L, 0L); + LongArray seconds = longs(12_345L, 0L); + LongArray subseconds = longs(678L, 0L); + long unitsPerSecond = 1_000L; + long unitsPerDay = 86_400L * unitsPerSecond; + + // When + var sut = new LazyDateTimePartsLongArray(I64, 2, + days, seconds, subseconds, unitsPerDay, unitsPerSecond); + + // Then + assertThat(sut.getLong(0)).isEqualTo( + 20_000L * unitsPerDay + 12_345L * unitsPerSecond + 678L); + assertThat(sut.getLong(1)).isZero(); } @Test void widensFromNarrowerChildPtypes() { - // Days as I32, seconds as I32, subseconds as I64 — encoder is free to pick. - try (Arena arena = Arena.ofConfined()) { - IntArray days = intArray(arena, 1); - IntArray seconds = intArray(arena, 2); - LongArray subseconds = longArray(arena, 3L); - long ups = 1_000_000_000L; // nanos - long upd = 86_400L * ups; - - var sut = new LazyDateTimePartsLongArray(I64, 1, - days, seconds, subseconds, upd, ups); - - assertThat(sut.getLong(0)).isEqualTo(1L * upd + 2L * ups + 3L); - } + // Given days/seconds as I32, subseconds as I64 — encoder is free to pick widths + IntArray days = ints(1); + IntArray seconds = ints(2); + LongArray subseconds = longs(3L); + long ups = 1_000_000_000L; // nanos + long upd = 86_400L * ups; + + // When + var sut = new LazyDateTimePartsLongArray(I64, 1, days, seconds, subseconds, upd, ups); + + // Then + assertThat(sut.getLong(0)).isEqualTo(1L * upd + 2L * ups + 3L); } @Test void foldSumsAllRows() { - try (Arena arena = Arena.ofConfined()) { - LongArray days = longArray(arena, 1L, 2L, 3L); - LongArray seconds = longArray(arena, 0L, 0L, 0L); - LongArray subseconds = longArray(arena, 0L, 0L, 0L); - long ups = 1L; - long upd = 86_400L; - - var sut = new LazyDateTimePartsLongArray(I64, 3, - days, seconds, subseconds, upd, ups); - - long sum = sut.fold(0L, Long::sum); - // 1*86400 + 2*86400 + 3*86400 = 6*86400 - assertThat(sum).isEqualTo(6L * upd); - } - } - - private static LongArray longArray(Arena arena, long... vs) { - MemorySegment seg = arena.allocate(vs.length * 8L, 8); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_LONG, i, vs[i]); - } - return new MaterializedLongArray(I64, vs.length, seg.asReadOnly()); - } - - private static IntArray intArray(Arena arena, int... vs) { - MemorySegment seg = arena.allocate(vs.length * 4L, 4); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_INT, i, vs[i]); - } - return new MaterializedIntArray(I32, vs.length, seg.asReadOnly()); + // Given + LongArray days = longs(1L, 2L, 3L); + LongArray seconds = longs(0L, 0L, 0L); + LongArray subseconds = longs(0L, 0L, 0L); + long ups = 1L; + long upd = 86_400L; + var sut = new LazyDateTimePartsLongArray(I64, 3, days, seconds, subseconds, upd, ups); + + // When + long result = sut.fold(0L, Long::sum); + + // Then — 1*86400 + 2*86400 + 3*86400 = 6*86400 + assertThat(result).isEqualTo(6L * upd); } } diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazySparseArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazySparseArrayTest.java index 01790c10..5bbf364a 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazySparseArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LazySparseArrayTest.java @@ -1,7 +1,5 @@ package io.github.dfa1.vortex.reader.array; -import io.github.dfa1.vortex.core.DType; -import io.github.dfa1.vortex.core.PType; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -10,94 +8,99 @@ import java.lang.foreign.ValueLayout; import java.util.ArrayList; +import static io.github.dfa1.vortex.encoding.DTypes.F32; +import static io.github.dfa1.vortex.encoding.DTypes.F64; +import static io.github.dfa1.vortex.encoding.DTypes.I16; +import static io.github.dfa1.vortex.encoding.DTypes.I32; +import static io.github.dfa1.vortex.encoding.DTypes.I64; +import static io.github.dfa1.vortex.encoding.DTypes.I8; +import static io.github.dfa1.vortex.encoding.DTypes.U16; +import static io.github.dfa1.vortex.encoding.DTypes.U8; +import static io.github.dfa1.vortex.reader.array.TestArrays.bytes; +import static io.github.dfa1.vortex.reader.array.TestArrays.doubles; +import static io.github.dfa1.vortex.reader.array.TestArrays.floats; +import static io.github.dfa1.vortex.reader.array.TestArrays.ints; +import static io.github.dfa1.vortex.reader.array.TestArrays.longs; +import static io.github.dfa1.vortex.reader.array.TestArrays.shorts; import static org.assertj.core.api.Assertions.assertThat; /// Unit tests for the lazy Sparse records. Covers fill vs patch dispatch, ordered /// forEach iteration, fold reduction, and offset slicing semantics. class LazySparseArrayTest { - private static final DType I64 = new DType.Primitive(PType.I64, false); - private static final DType I32 = new DType.Primitive(PType.I32, false); - private static final DType F64 = new DType.Primitive(PType.F64, false); - private static final DType F32 = new DType.Primitive(PType.F32, false); - private static final DType I8 = new DType.Primitive(PType.I8, false); - private static final DType U8 = new DType.Primitive(PType.U8, false); - private static final DType I16 = new DType.Primitive(PType.I16, false); - private static final DType U16 = new DType.Primitive(PType.U16, false); - @Nested class Long { @Test void unpatchedPositionsReturnFill() { - try (Arena arena = Arena.ofConfined()) { - // length=5, fill=99, patches at index 1 → 7, index 3 → 11 - LongArray values = longArray(arena, 7L, 11L); - Array indices = intArray(arena, 1, 3); - var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); - - assertThat(sut.getLong(0)).isEqualTo(99L); - assertThat(sut.getLong(1)).isEqualTo(7L); - assertThat(sut.getLong(2)).isEqualTo(99L); - assertThat(sut.getLong(3)).isEqualTo(11L); - assertThat(sut.getLong(4)).isEqualTo(99L); - } + // Given — length=5, fill=99, patches at index 1 -> 7, index 3 -> 11 + LongArray values = longs(7L, 11L); + Array indices = ints(1, 3); + var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); + + // When / Then + assertThat(sut.getLong(0)).isEqualTo(99L); + assertThat(sut.getLong(1)).isEqualTo(7L); + assertThat(sut.getLong(2)).isEqualTo(99L); + assertThat(sut.getLong(3)).isEqualTo(11L); + assertThat(sut.getLong(4)).isEqualTo(99L); } @Test void forEachEmitsInOrder() { - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 7L, 11L); - Array indices = intArray(arena, 1, 3); - var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); + // Given + LongArray values = longs(7L, 11L); + Array indices = ints(1, 3); + var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); - var seen = new ArrayList(); - sut.forEachLong(seen::add); + // When + var seen = new ArrayList(); + sut.forEachLong(seen::add); - assertThat(seen).containsExactly(99L, 7L, 99L, 11L, 99L); - } + // Then + assertThat(seen).containsExactly(99L, 7L, 99L, 11L, 99L); } @Test void foldSumsFillAndPatches() { - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 7L, 11L); - Array indices = intArray(arena, 1, 3); - var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); + // Given + LongArray values = longs(7L, 11L); + Array indices = ints(1, 3); + var sut = new LazySparseLongArray(I64, 5, 99L, values, indices, 0L); - long sum = sut.fold(0L, java.lang.Long::sum); + // When + long result = sut.fold(0L, java.lang.Long::sum); - // 99 + 7 + 99 + 11 + 99 = 315 - assertThat(sum).isEqualTo(315L); - } + // Then — 99 + 7 + 99 + 11 + 99 = 315 + assertThat(result).isEqualTo(315L); } @Test void offsetSkipsLeadingPatches() { - try (Arena arena = Arena.ofConfined()) { - // length=3 covering abs [4..7), fill=1, patches at abs 4 and 6 - LongArray values = longArray(arena, 10L, 11L, 12L); - Array indices = intArray(arena, 1, 4, 6); - var sut = new LazySparseLongArray(I64, 3, 1L, values, indices, 4L); - - assertThat(sut.getLong(0)).isEqualTo(11L); - assertThat(sut.getLong(1)).isEqualTo(1L); - assertThat(sut.getLong(2)).isEqualTo(12L); - } + // Given — length=3 covering abs [4..7), fill=1, patches at abs 4 and 6 + LongArray values = longs(10L, 11L, 12L); + Array indices = ints(1, 4, 6); + var sut = new LazySparseLongArray(I64, 3, 1L, values, indices, 4L); + + // When / Then + assertThat(sut.getLong(0)).isEqualTo(11L); + assertThat(sut.getLong(1)).isEqualTo(1L); + assertThat(sut.getLong(2)).isEqualTo(12L); } @Test void noPatchesIsAllFill() { - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena); - Array indices = intArray(arena); - var sut = new LazySparseLongArray(I64, 3, 42L, values, indices, 0L); + // Given + LongArray values = longs(); + Array indices = ints(); + var sut = new LazySparseLongArray(I64, 3, 42L, values, indices, 0L); - var seen = new ArrayList(); - sut.forEachLong(seen::add); + // When + var seen = new ArrayList(); + sut.forEachLong(seen::add); - assertThat(seen).containsExactly(42L, 42L, 42L); - } + // Then + assertThat(seen).containsExactly(42L, 42L, 42L); } } @@ -106,28 +109,28 @@ class IntAndDouble { @Test void intPatchDispatches() { - try (Arena arena = Arena.ofConfined()) { - IntArray values = intArray(arena, 100, 200); - Array indices = intArray(arena, 0, 2); - var sut = new LazySparseIntArray(I32, 3, 5, values, indices, 0L); - - assertThat(sut.getInt(0)).isEqualTo(100); - assertThat(sut.getInt(1)).isEqualTo(5); - assertThat(sut.getInt(2)).isEqualTo(200); - } + // Given + IntArray values = ints(100, 200); + Array indices = ints(0, 2); + var sut = new LazySparseIntArray(I32, 3, 5, values, indices, 0L); + + // When / Then + assertThat(sut.getInt(0)).isEqualTo(100); + assertThat(sut.getInt(1)).isEqualTo(5); + assertThat(sut.getInt(2)).isEqualTo(200); } @Test void doublePatchDispatches() { - try (Arena arena = Arena.ofConfined()) { - DoubleArray values = doubleArray(arena, 1.5, 2.5); - Array indices = intArray(arena, 0, 2); - var sut = new LazySparseDoubleArray(F64, 3, 0.0, values, indices, 0L); - - assertThat(sut.getDouble(0)).isEqualTo(1.5); - assertThat(sut.getDouble(1)).isEqualTo(0.0); - assertThat(sut.getDouble(2)).isEqualTo(2.5); - } + // Given + DoubleArray values = doubles(1.5, 2.5); + Array indices = ints(0, 2); + var sut = new LazySparseDoubleArray(F64, 3, 0.0, values, indices, 0L); + + // When / Then + assertThat(sut.getDouble(0)).isEqualTo(1.5); + assertThat(sut.getDouble(1)).isEqualTo(0.0); + assertThat(sut.getDouble(2)).isEqualTo(2.5); } } @@ -140,43 +143,37 @@ class ByteAndShort { @Test void bytePatchAndFillDispatch() { - try (Arena arena = Arena.ofConfined()) { - // Given — patches at 0->7 and 2->11, fill 5 - ByteArray values = byteArray(arena, (byte) 7, (byte) 11); - Array indices = intArray(arena, 0, 2); - var sut = new LazySparseByteArray(I8, 3, (byte) 5, 5, values, indices, 0L); - - // When / Then - assertThat(sut.getByte(0)).isEqualTo((byte) 7); - assertThat(sut.getInt(1)).isEqualTo(5); - assertThat(sut.getInt(2)).isEqualTo(11); - } + // Given — patches at 0->7 and 2->11, fill 5 + ByteArray values = bytes((byte) 7, (byte) 11); + Array indices = ints(0, 2); + var sut = new LazySparseByteArray(I8, 3, (byte) 5, 5, values, indices, 0L); + + // When / Then + assertThat(sut.getByte(0)).isEqualTo((byte) 7); + assertThat(sut.getInt(1)).isEqualTo(5); + assertThat(sut.getInt(2)).isEqualTo(11); } @Test void byteGetIntWidensUnsignedFill() { - try (Arena arena = Arena.ofConfined()) { - // Given — U8 fill 0xFF -> fillInt 255 - ByteArray values = byteArray(arena, (byte) 1); - Array indices = intArray(arena, 0); - var sut = new LazySparseByteArray(U8, 3, (byte) 0xFF, 255, values, indices, 0L); - - // When / Then — unpatched position reports 255 not -1 - assertThat(sut.getInt(1)).isEqualTo(255); - } + // Given — U8 fill 0xFF -> fillInt 255 + ByteArray values = bytes((byte) 1); + Array indices = ints(0); + var sut = new LazySparseByteArray(U8, 3, (byte) 0xFF, 255, values, indices, 0L); + + // When / Then — unpatched position reports 255 not -1 + assertThat(sut.getInt(1)).isEqualTo(255); } @Test void byteFoldSumsThroughIntPath() { - try (Arena arena = Arena.ofConfined()) { - // Given — length 5, fill 10, patches 1->7 and 3->11 - ByteArray values = byteArray(arena, (byte) 7, (byte) 11); - Array indices = intArray(arena, 1, 3); - var sut = new LazySparseByteArray(I8, 5, (byte) 10, 10, values, indices, 0L); - - // When / Then — 10+7+10+11+10 = 48 - assertThat(sut.fold(0L, java.lang.Long::sum)).isEqualTo(48L); - } + // Given — length 5, fill 10, patches 1->7 and 3->11 + ByteArray values = bytes((byte) 7, (byte) 11); + Array indices = ints(1, 3); + var sut = new LazySparseByteArray(I8, 5, (byte) 10, 10, values, indices, 0L); + + // When / Then — 10+7+10+11+10 = 48 + assertThat(sut.fold(0L, java.lang.Long::sum)).isEqualTo(48L); } @Test @@ -191,32 +188,28 @@ void byteNullPatchesIsAllFill() { @Test void shortPatchAndFoldDispatch() { - try (Arena arena = Arena.ofConfined()) { - // Given — length 5, fill 1, patches 1->100 and 3->200 - ShortArray values = shortArray(arena, (short) 100, (short) 200); - Array indices = intArray(arena, 1, 3); - var sut = new LazySparseShortArray(I16, 5, (short) 1, 1, values, indices, 0L); - - // When / Then — fill + patches; fold 1+100+1+200+1 = 303 - assertThat(sut.getInt(0)).isEqualTo(1); - assertThat(sut.getInt(1)).isEqualTo(100); - assertThat(sut.fold(0L, java.lang.Long::sum)).isEqualTo(303L); - } + // Given — length 5, fill 1, patches 1->100 and 3->200 + ShortArray values = shorts((short) 100, (short) 200); + Array indices = ints(1, 3); + var sut = new LazySparseShortArray(I16, 5, (short) 1, 1, values, indices, 0L); + + // When / Then — fill + patches; fold 1+100+1+200+1 = 303 + assertThat(sut.getInt(0)).isEqualTo(1); + assertThat(sut.getInt(1)).isEqualTo(100); + assertThat(sut.fold(0L, java.lang.Long::sum)).isEqualTo(303L); } @Test void shortGetIntWidensUnsigned() { - try (Arena arena = Arena.ofConfined()) { - // Given — widening flows through patchValues.getInt, so the patch array must be U16 - MemorySegment seg = arena.allocate(2L, 2); - seg.setAtIndex(ValueLayout.JAVA_SHORT, 0, (short) 0xFFFF); - ShortArray values = new MaterializedShortArray(U16, 1, seg.asReadOnly()); - Array indices = intArray(arena, 0); - var sut = new LazySparseShortArray(U16, 2, (short) 0, 0, values, indices, 0L); - - // When / Then - assertThat(sut.getInt(0)).isEqualTo(65535); - } + // Given — widening flows through patchValues.getInt, so the patch array must be U16 + MemorySegment seg = Arena.ofAuto().allocate(2L, 2); + seg.setAtIndex(ValueLayout.JAVA_SHORT, 0, (short) 0xFFFF); + ShortArray values = new MaterializedShortArray(U16, 1, seg.asReadOnly()); + Array indices = ints(0); + var sut = new LazySparseShortArray(U16, 2, (short) 0, 0, values, indices, 0L); + + // When / Then + assertThat(sut.getInt(0)).isEqualTo(65535); } } @@ -225,109 +218,52 @@ class Float { @Test void patchAndFillDispatch() { - try (Arena arena = Arena.ofConfined()) { - FloatArray values = floatArray(arena, 1.5f, 2.5f); - Array indices = intArray(arena, 0, 2); - var sut = new LazySparseFloatArray(F32, 3, 9.0f, values, indices, 0L); - - assertThat(sut.getFloat(0)).isEqualTo(1.5f); - assertThat(sut.getFloat(1)).isEqualTo(9.0f); - assertThat(sut.getFloat(2)).isEqualTo(2.5f); - } + // Given + FloatArray values = floats(1.5f, 2.5f); + Array indices = ints(0, 2); + var sut = new LazySparseFloatArray(F32, 3, 9.0f, values, indices, 0L); + + // When / Then + assertThat(sut.getFloat(0)).isEqualTo(1.5f); + assertThat(sut.getFloat(1)).isEqualTo(9.0f); + assertThat(sut.getFloat(2)).isEqualTo(2.5f); } @Test void foldSumsFillAndPatches() { - try (Arena arena = Arena.ofConfined()) { - FloatArray values = floatArray(arena, 1.5f, 2.5f); - Array indices = intArray(arena, 1, 3); - // length=5, fill=10, patches at index 1 and 3 - var sut = new LazySparseFloatArray(F32, 5, 10.0f, values, indices, 0L); + // Given — length=5, fill=10, patches at index 1 and 3 + FloatArray values = floats(1.5f, 2.5f); + Array indices = ints(1, 3); + var sut = new LazySparseFloatArray(F32, 5, 10.0f, values, indices, 0L); - double sum = sut.fold(0.0, java.lang.Double::sum); + // When + double result = sut.fold(0.0, java.lang.Double::sum); - // 10 + 1.5 + 10 + 2.5 + 10 = 34 - assertThat(sum).isEqualTo(34.0); - } + // Then — 10 + 1.5 + 10 + 2.5 + 10 = 34 + assertThat(result).isEqualTo(34.0); } @Test void offsetSkipsLeadingPatches() { - try (Arena arena = Arena.ofConfined()) { - // length=3 covering abs [4..7), fill=1, patches at abs 4 and 6 - FloatArray values = floatArray(arena, 10.0f, 11.0f, 12.0f); - Array indices = intArray(arena, 1, 4, 6); - var sut = new LazySparseFloatArray(F32, 3, 1.0f, values, indices, 4L); - - assertThat(sut.getFloat(0)).isEqualTo(11.0f); - assertThat(sut.getFloat(1)).isEqualTo(1.0f); - assertThat(sut.getFloat(2)).isEqualTo(12.0f); - } + // Given — length=3 covering abs [4..7), fill=1, patches at abs 4 and 6 + FloatArray values = floats(10.0f, 11.0f, 12.0f); + Array indices = ints(1, 4, 6); + var sut = new LazySparseFloatArray(F32, 3, 1.0f, values, indices, 4L); + + // When / Then + assertThat(sut.getFloat(0)).isEqualTo(11.0f); + assertThat(sut.getFloat(1)).isEqualTo(1.0f); + assertThat(sut.getFloat(2)).isEqualTo(12.0f); } @Test void nullPatchesIsAllFill() { - // patchValues == null is the no-patch fast path: every position returns fill + // Given — patchValues == null is the no-patch fast path var sut = new LazySparseFloatArray(F32, 3, 42.0f, null, null, 0L); + // When / Then — every position returns the fill assertThat(sut.getFloat(0)).isEqualTo(42.0f); assertThat(sut.fold(0.0, java.lang.Double::sum)).isEqualTo(126.0); } } - - private static LongArray longArray(Arena arena, long... vs) { - if (vs.length == 0) { - return new MaterializedLongArray(I64, 0, - arena.allocate(1L, 8).asReadOnly().asSlice(0, 0)); - } - MemorySegment seg = arena.allocate(vs.length * 8L, 8); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_LONG, i, vs[i]); - } - return new MaterializedLongArray(I64, vs.length, seg.asReadOnly()); - } - - private static IntArray intArray(Arena arena, int... vs) { - if (vs.length == 0) { - return new MaterializedIntArray(I32, 0, - arena.allocate(1L, 4).asReadOnly().asSlice(0, 0)); - } - MemorySegment seg = arena.allocate(vs.length * 4L, 4); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_INT, i, vs[i]); - } - return new MaterializedIntArray(I32, vs.length, seg.asReadOnly()); - } - - private static DoubleArray doubleArray(Arena arena, double... vs) { - MemorySegment seg = arena.allocate(vs.length * 8L, 8); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_DOUBLE, i, vs[i]); - } - return new MaterializedDoubleArray(F64, vs.length, seg.asReadOnly()); - } - - private static FloatArray floatArray(Arena arena, float... vs) { - MemorySegment seg = arena.allocate(vs.length * 4L, 4); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_FLOAT, i, vs[i]); - } - return new MaterializedFloatArray(F32, vs.length, seg.asReadOnly()); - } - - private static ByteArray byteArray(Arena arena, byte... vs) { - MemorySegment seg = arena.allocate(vs.length, 1); - for (int i = 0; i < vs.length; i++) { - seg.set(ValueLayout.JAVA_BYTE, i, vs[i]); - } - return new MaterializedByteArray(I8, vs.length, seg.asReadOnly()); - } - - private static ShortArray shortArray(Arena arena, short... vs) { - MemorySegment seg = arena.allocate(vs.length * 2L, 2); - for (int i = 0; i < vs.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_SHORT, i, vs[i]); - } - return new MaterializedShortArray(I16, vs.length, seg.asReadOnly()); - } } diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LongArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LongArrayTest.java index 05c62f70..0f7aaf87 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/array/LongArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/LongArrayTest.java @@ -21,12 +21,7 @@ class LongArrayTest { ValueLayout.JAVA_LONG_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); private static LongArray of(long... values) { - MemorySegment seg = Arena.ofAuto().allocate((long) values.length * 8, 8); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(LE_LONG, i, values[i]); - } - DType dtype = new DType.Primitive(PType.I64, false); - return new MaterializedLongArray(dtype, values.length, seg); + return TestArrays.longs(values); } @Nested diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/array/TestArrays.java b/reader/src/test/java/io/github/dfa1/vortex/reader/array/TestArrays.java new file mode 100644 index 00000000..c05b9753 --- /dev/null +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/array/TestArrays.java @@ -0,0 +1,120 @@ +package io.github.dfa1.vortex.reader.array; + +import io.github.dfa1.vortex.encoding.DTypes; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +/// Shared builders for in-memory `Materialized*Array` instances in reader tests. +/// +/// Replaces the per-file copy-pasted segment-allocation helpers. Each builder +/// allocates a little-endian, read-only segment from an auto [Arena] (GC-managed — +/// test data is short-lived, so callers need no try-with-resources) with the +/// default signed dtype (from [DTypes]) for its width. Tests that need an unsigned +/// dtype (U8/U16) for widening behaviour build the array inline instead. +public final class TestArrays { + + private TestArrays() { + } + + /// Builds an I64-typed [LongArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized long array + public static LongArray longs(long... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 8L, 8); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_LONG, i, vs[i]); + } + return new MaterializedLongArray(DTypes.I64, vs.length, seg.asReadOnly()); + } + + /// Builds an I32-typed [IntArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized int array + public static IntArray ints(int... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 4L, 4); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_INT, i, vs[i]); + } + return new MaterializedIntArray(DTypes.I32, vs.length, seg.asReadOnly()); + } + + /// Builds an F64-typed [DoubleArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized double array + public static DoubleArray doubles(double... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 8L, 8); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_DOUBLE, i, vs[i]); + } + return new MaterializedDoubleArray(DTypes.F64, vs.length, seg.asReadOnly()); + } + + /// Builds an F32-typed [FloatArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized float array + public static FloatArray floats(float... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 4L, 4); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_FLOAT, i, vs[i]); + } + return new MaterializedFloatArray(DTypes.F32, vs.length, seg.asReadOnly()); + } + + /// Builds an I16-typed [ShortArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized short array + public static ShortArray shorts(short... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 2L, 2); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_SHORT, i, vs[i]); + } + return new MaterializedShortArray(DTypes.I16, vs.length, seg.asReadOnly()); + } + + /// Builds an I8-typed [ByteArray] from the given values. + /// + /// @param vs element values + /// @return read-only materialized byte array + public static ByteArray bytes(byte... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length, 1); + for (int i = 0; i < vs.length; i++) { + seg.set(ValueLayout.JAVA_BYTE, i, vs[i]); + } + return new MaterializedByteArray(DTypes.I8, vs.length, seg.asReadOnly()); + } + + /// Builds a Bool [BoolArray] from the given values (LSB-first bit packing). + /// + /// @param vs element values + /// @return read-only materialized bool array + public static BoolArray bools(boolean... vs) { + MemorySegment seg = Arena.ofAuto().allocate((vs.length + 7) / 8, 1); + for (int i = 0; i < vs.length; i++) { + if (vs[i]) { + long byteIdx = i >>> 3; + byte cur = seg.get(ValueLayout.JAVA_BYTE, byteIdx); + seg.set(ValueLayout.JAVA_BYTE, byteIdx, (byte) (cur | (1 << (i & 7)))); + } + } + return new MaterializedBoolArray(DTypes.BOOL, vs.length, seg.asReadOnly()); + } + + /// Builds an F16-typed [Float16Array] from the given float values. + /// + /// @param vs element values (converted to half precision) + /// @return read-only materialized float16 array + public static Float16Array float16(float... vs) { + MemorySegment seg = Arena.ofAuto().allocate(vs.length * 2L, 2); + for (int i = 0; i < vs.length; i++) { + seg.setAtIndex(ValueLayout.JAVA_SHORT, i, Float.floatToFloat16(vs[i])); + } + return new MaterializedFloat16Array(DTypes.F16, vs.length, seg.asReadOnly()); + } +} diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/decode/LazyRunEndArrayTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/decode/LazyRunEndArrayTest.java index 86d859ea..7ab6534a 100644 --- a/reader/src/test/java/io/github/dfa1/vortex/reader/decode/LazyRunEndArrayTest.java +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/decode/LazyRunEndArrayTest.java @@ -1,20 +1,19 @@ package io.github.dfa1.vortex.reader.decode; -import io.github.dfa1.vortex.core.DType; -import io.github.dfa1.vortex.core.PType; import io.github.dfa1.vortex.reader.array.Array; import io.github.dfa1.vortex.reader.array.IntArray; +import io.github.dfa1.vortex.reader.array.LazyRunEndIntArray; +import io.github.dfa1.vortex.reader.array.LazyRunEndLongArray; import io.github.dfa1.vortex.reader.array.LongArray; -import io.github.dfa1.vortex.reader.array.MaterializedIntArray; -import io.github.dfa1.vortex.reader.array.MaterializedLongArray; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; import java.util.ArrayList; +import static io.github.dfa1.vortex.encoding.DTypes.I32; +import static io.github.dfa1.vortex.encoding.DTypes.I64; +import static io.github.dfa1.vortex.reader.array.TestArrays.ints; +import static io.github.dfa1.vortex.reader.array.TestArrays.longs; import static org.assertj.core.api.Assertions.assertThat; /// Unit tests for the lazy run-end records nested in [RunEndEncodingDecoder]. @@ -22,93 +21,81 @@ /// fold reduction, and the offset slicing semantics. class LazyRunEndArrayTest { - private static final DType I64 = new DType.Primitive(PType.I64, false); - private static final DType I32 = new DType.Primitive(PType.I32, false); - @Nested class LongDispatch { @Test void getLongMapsThroughRuns() { // Given runs: [0..3)=10, [3..5)=20, [5..8)=30 - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 10L, 20L, 30L); - Array runEnds = intArray(arena, 3, 5, 8); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndLongArray(I64, 8L, values, runEnds, 0L); - - // When/Then - assertThat(sut.getLong(0)).isEqualTo(10L); - assertThat(sut.getLong(2)).isEqualTo(10L); - assertThat(sut.getLong(3)).isEqualTo(20L); - assertThat(sut.getLong(4)).isEqualTo(20L); - assertThat(sut.getLong(5)).isEqualTo(30L); - assertThat(sut.getLong(7)).isEqualTo(30L); - } + LongArray values = longs(10L, 20L, 30L); + Array runEnds = ints(3, 5, 8); + var sut = new LazyRunEndLongArray(I64, 8L, values, runEnds, 0L); + + // When / Then + assertThat(sut.getLong(0)).isEqualTo(10L); + assertThat(sut.getLong(2)).isEqualTo(10L); + assertThat(sut.getLong(3)).isEqualTo(20L); + assertThat(sut.getLong(4)).isEqualTo(20L); + assertThat(sut.getLong(5)).isEqualTo(30L); + assertThat(sut.getLong(7)).isEqualTo(30L); } @Test void forEachLongWalksRuns() { // Given runs: [0..2)=1, [2..3)=2, [3..6)=3 - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 1L, 2L, 3L); - Array runEnds = intArray(arena, 2, 3, 6); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndLongArray(I64, 6L, values, runEnds, 0L); - - // When - var seen = new ArrayList(); - sut.forEachLong(seen::add); - - // Then - assertThat(seen).containsExactly(1L, 1L, 2L, 3L, 3L, 3L); - } + LongArray values = longs(1L, 2L, 3L); + Array runEnds = ints(2, 3, 6); + var sut = new LazyRunEndLongArray(I64, 6L, values, runEnds, 0L); + + // When + var seen = new ArrayList(); + sut.forEachLong(seen::add); + + // Then + assertThat(seen).containsExactly(1L, 1L, 2L, 3L, 3L, 3L); } @Test void foldSumsCorrectly() { // Given runs: [0..2)=5, [2..5)=10 - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 5L, 10L); - Array runEnds = intArray(arena, 2, 5); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndLongArray(I64, 5L, values, runEnds, 0L); + LongArray values = longs(5L, 10L); + Array runEnds = ints(2, 5); + var sut = new LazyRunEndLongArray(I64, 5L, values, runEnds, 0L); - // When - long sum = sut.fold(0L, Long::sum); + // When + long result = sut.fold(0L, Long::sum); - // Then 2*5 + 3*10 = 40 - assertThat(sum).isEqualTo(40L); - } + // Then — 2*5 + 3*10 = 40 + assertThat(result).isEqualTo(40L); } @Test void offsetSkipsLeadingRuns() { - // Given runs: [0..3)=1, [3..5)=2, [5..8)=3 - // With offset=3, logical row 0 should map to absolute 3 → value 2 - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 1L, 2L, 3L); - Array runEnds = intArray(arena, 3, 5, 8); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndLongArray(I64, 5L, values, runEnds, 3L); - - // When/Then - assertThat(sut.getLong(0)).isEqualTo(2L); - assertThat(sut.getLong(1)).isEqualTo(2L); - assertThat(sut.getLong(2)).isEqualTo(3L); - assertThat(sut.getLong(4)).isEqualTo(3L); - } + // Given runs: [0..3)=1, [3..5)=2, [5..8)=3; offset=3 maps logical 0 -> abs 3 -> value 2 + LongArray values = longs(1L, 2L, 3L); + Array runEnds = ints(3, 5, 8); + var sut = new LazyRunEndLongArray(I64, 5L, values, runEnds, 3L); + + // When / Then + assertThat(sut.getLong(0)).isEqualTo(2L); + assertThat(sut.getLong(1)).isEqualTo(2L); + assertThat(sut.getLong(2)).isEqualTo(3L); + assertThat(sut.getLong(4)).isEqualTo(3L); } @Test void offsetForEachStartsAtOffset() { - try (Arena arena = Arena.ofConfined()) { - LongArray values = longArray(arena, 1L, 2L, 3L); - Array runEnds = intArray(arena, 3, 5, 8); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndLongArray(I64, 5L, values, runEnds, 3L); + // Given runs [0..3)=1,[3..5)=2,[5..8)=3 with offset 3 + LongArray values = longs(1L, 2L, 3L); + Array runEnds = ints(3, 5, 8); + var sut = new LazyRunEndLongArray(I64, 5L, values, runEnds, 3L); - var seen = new ArrayList(); - sut.forEachLong(seen::add); + // When + var seen = new ArrayList(); + sut.forEachLong(seen::add); - // logical [0..5) over runs starting at abs=3: 2,2,3,3,3 - assertThat(seen).containsExactly(2L, 2L, 3L, 3L, 3L); - } + // Then — logical [0..5) over runs starting at abs=3: 2,2,3,3,3 + assertThat(seen).containsExactly(2L, 2L, 3L, 3L, 3L); } } @@ -117,31 +104,15 @@ class IntDispatch { @Test void getIntMapsThroughRuns() { - try (Arena arena = Arena.ofConfined()) { - IntArray values = intArray(arena, 100, 200); - Array runEnds = intArray(arena, 3, 5); - var sut = new io.github.dfa1.vortex.reader.array.LazyRunEndIntArray(I32, 5L, values, runEnds, 0L); - - assertThat(sut.getInt(0)).isEqualTo(100); - assertThat(sut.getInt(3)).isEqualTo(200); - assertThat(sut.getInt(4)).isEqualTo(200); - } - } - } - - private static LongArray longArray(Arena arena, long... values) { - MemorySegment seg = arena.allocate(values.length * 8L, 8); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_LONG, i, values[i]); - } - return new MaterializedLongArray(I64, values.length, seg.asReadOnly()); - } - - private static IntArray intArray(Arena arena, int... values) { - MemorySegment seg = arena.allocate(values.length * 4L, 4); - for (int i = 0; i < values.length; i++) { - seg.setAtIndex(ValueLayout.JAVA_INT, i, values[i]); + // Given runs: [0..3)=100, [3..5)=200 + IntArray values = ints(100, 200); + Array runEnds = ints(3, 5); + var sut = new LazyRunEndIntArray(I32, 5L, values, runEnds, 0L); + + // When / Then + assertThat(sut.getInt(0)).isEqualTo(100); + assertThat(sut.getInt(3)).isEqualTo(200); + assertThat(sut.getInt(4)).isEqualTo(200); } - return new MaterializedIntArray(I32, values.length, seg.asReadOnly()); } }