From 38e36b00f20f04efa30b4019635a3fdd40cb69da Mon Sep 17 00:00:00 2001 From: Davide Angelocola Date: Sat, 20 Jun 2026 08:35:04 +0200 Subject: [PATCH] test(reader): cover DecimalEncodingDecoder (was untested) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DecimalEncodingDecoderTest: accepts/encodingId, every DecimalType width (1/2/4/8/16/32 bytes), unknown values_type, buffer-too-small, and the missing/empty/invalid metadata guards. 100% line + branch. values_type is encoded as an explicit field-1 varint so the proto3 default (0) is present on the wire (DecimalMetadata(0).encode() omits it → reads as empty). Co-Authored-By: Claude Opus 4.8 --- .../decode/DecimalEncodingDecoderTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 reader/src/test/java/io/github/dfa1/vortex/reader/decode/DecimalEncodingDecoderTest.java diff --git a/reader/src/test/java/io/github/dfa1/vortex/reader/decode/DecimalEncodingDecoderTest.java b/reader/src/test/java/io/github/dfa1/vortex/reader/decode/DecimalEncodingDecoderTest.java new file mode 100644 index 00000000..5a272486 --- /dev/null +++ b/reader/src/test/java/io/github/dfa1/vortex/reader/decode/DecimalEncodingDecoderTest.java @@ -0,0 +1,115 @@ +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.core.VortexException; +import io.github.dfa1.vortex.encoding.EncodingId; +import io.github.dfa1.vortex.reader.ReadRegistry; +import io.github.dfa1.vortex.reader.array.Array; +import io.github.dfa1.vortex.reader.array.LazyDecimalArray; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DecimalEncodingDecoderTest { + + private static final DecimalEncodingDecoder SUT = new DecimalEncodingDecoder(); + private static final DType DECIMAL = new DType.Decimal((byte) 10, (byte) 2, false); + + private static Array decode(int valuesType, int rowCount, int bufferBytes) { + // Encode values_type explicitly (field 1, varint) so even the proto3 default (0) is + // present on the wire — DecimalMetadata(0).encode() would omit it and read as empty. + ByteBuffer meta = ByteBuffer.wrap(new byte[]{0x08, (byte) valuesType}); + return decode(meta, rowCount, bufferBytes); + } + + private static Array decode(ByteBuffer meta, int rowCount, int bufferBytes) { + MemorySegment buf = MemorySegment.ofArray(new byte[bufferBytes]); + ArrayNode node = ArrayNode.of(EncodingId.VORTEX_DECIMAL, meta, new ArrayNode[0], new int[]{0}); + DecodeContext ctx = new DecodeContext(node, DECIMAL, rowCount, + new MemorySegment[]{buf}, ReadRegistry.empty(), Arena.ofAuto()); + return SUT.decode(ctx); + } + + @Test + void acceptsDecimalRejectsOthers() { + // Given / When / Then + assertThat(SUT.accepts(DECIMAL)).isTrue(); + assertThat(SUT.accepts(new DType.Primitive(PType.I32, false))).isFalse(); + } + + @Test + void encodingId() { + assertThat(SUT.encodingId()).isEqualTo(EncodingId.VORTEX_DECIMAL); + } + + @Nested + class Decode { + + @ParameterizedTest(name = "valuesType={0} -> {1} bytes") + @CsvSource({"0,1", "1,2", "2,4", "3,8", "4,16", "5,32"}) + void everyDecimalTypeWidth(int valuesType, int byteWidth) { + // Given — buffer sized exactly for the declared width × rows + int rows = 2; + + // When + Array result = decode(valuesType, rows, rows * byteWidth); + + // Then + assertThat(result).isInstanceOf(LazyDecimalArray.class); + assertThat(result.length()).isEqualTo(rows); + } + + @Test + void unknownValuesType_throws() { + // When / Then + assertThatThrownBy(() -> decode(6, 1, 64)) + .isInstanceOf(VortexException.class) + .hasMessageContaining("unknown DecimalType"); + } + + @Test + void bufferTooSmall_throws() { + // Given — values_type 3 = 8 bytes/row × 2 rows = 16 needed, only 8 provided + // When / Then + assertThatThrownBy(() -> decode(3, 2, 8)) + .isInstanceOf(VortexException.class) + .hasMessageContaining("buffer too small"); + } + + @Test + void missingMetadata_throws() { + // When / Then — null metadata + assertThatThrownBy(() -> decode((ByteBuffer) null, 1, 8)) + .isInstanceOf(VortexException.class) + .hasMessageContaining("missing metadata"); + } + + @Test + void emptyMetadata_throws() { + // When / Then — present but zero remaining + assertThatThrownBy(() -> decode(ByteBuffer.allocate(0), 1, 8)) + .isInstanceOf(VortexException.class) + .hasMessageContaining("missing metadata"); + } + + @Test + void invalidMetadata_throws() { + // Given — a truncated varint that proto decode rejects + ByteBuffer bad = ByteBuffer.wrap(new byte[]{0x08, (byte) 0x80}); + + // When / Then + assertThatThrownBy(() -> decode(bad, 1, 8)) + .isInstanceOf(VortexException.class) + .hasMessageContaining("invalid metadata"); + } + } +}