|
2 | 2 |
|
3 | 3 | import io.github.dfa1.vortex.core.DType; |
4 | 4 | import io.github.dfa1.vortex.core.PType; |
| 5 | +import io.github.dfa1.vortex.core.VortexException; |
5 | 6 | import io.github.dfa1.vortex.encoding.DTypes; |
6 | 7 | import io.github.dfa1.vortex.encoding.EncodingId; |
7 | 8 | import io.github.dfa1.vortex.reader.ReadRegistry; |
8 | 9 | import io.github.dfa1.vortex.reader.array.Array; |
| 10 | +import io.github.dfa1.vortex.reader.array.ByteArray; |
9 | 11 | import io.github.dfa1.vortex.reader.array.IntArray; |
10 | 12 | import io.github.dfa1.vortex.reader.array.LongArray; |
| 13 | +import io.github.dfa1.vortex.reader.array.ShortArray; |
11 | 14 | import io.github.dfa1.vortex.reader.decode.ArrayNode; |
12 | 15 | import io.github.dfa1.vortex.reader.decode.DecodeContext; |
13 | 16 | import io.github.dfa1.vortex.reader.decode.PatchedEncodingDecoder; |
|
16 | 19 | import org.junit.jupiter.api.Nested; |
17 | 20 | import org.junit.jupiter.api.Test; |
18 | 21 | import org.junit.jupiter.params.ParameterizedTest; |
| 22 | +import org.junit.jupiter.params.provider.EnumSource; |
19 | 23 | import org.junit.jupiter.params.provider.ValueSource; |
20 | 24 |
|
21 | 25 | import java.lang.foreign.Arena; |
22 | 26 | import java.lang.foreign.MemorySegment; |
23 | 27 | import java.util.List; |
24 | 28 |
|
25 | 29 | import static org.assertj.core.api.Assertions.assertThat; |
| 30 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; |
26 | 31 |
|
27 | 32 | class PatchedEncodingEncoderTest { |
28 | 33 |
|
@@ -119,6 +124,47 @@ void encode_roundTrip_i32_multipleChunks() { |
119 | 124 | } |
120 | 125 | } |
121 | 126 |
|
| 127 | + @ParameterizedTest |
| 128 | + @EnumSource(value = PType.class, names = {"I8", "U8", "I16", "U16", "I32", "U32", "I64", "U64"}) |
| 129 | + void encode_roundTrip_allIntegerPTypes(PType ptype) { |
| 130 | + // Given: 100 small values + 1 outlier — exercises toLongs/PTypeIO.set for every width |
| 131 | + int n = 100; |
| 132 | + DType dtype = dtypeOf(ptype); |
| 133 | + Object data = buildData(ptype, n); |
| 134 | + |
| 135 | + // When |
| 136 | + EncodeResult result = SUT.encode(dtype, data, EncodeTestHelper.testCtx()); |
| 137 | + Array decoded = decode(result, dtype, n); |
| 138 | + |
| 139 | + // Then |
| 140 | + for (int i = 0; i < n; i++) { |
| 141 | + assertThat(readLong(decoded, i, ptype)).as("ptype %s index %d", ptype, i) |
| 142 | + .isEqualTo(expectedAt(ptype, i, n)); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + @Test |
| 147 | + void encode_throws_onUnsupportedPType() { |
| 148 | + // Given: a primitive float dtype slips past the instanceof check but has no integer mapping |
| 149 | + double[] data = {1.0, 2.0, 3.0}; |
| 150 | + |
| 151 | + // When / Then |
| 152 | + assertThatThrownBy(() -> SUT.encode(DTypes.F64, data, EncodeTestHelper.testCtx())) |
| 153 | + .isInstanceOf(VortexException.class) |
| 154 | + .hasMessageContaining("unsupported ptype"); |
| 155 | + } |
| 156 | + |
| 157 | + @Test |
| 158 | + void encode_throws_onNonPrimitiveDtype() { |
| 159 | + // Given |
| 160 | + long[] data = {1L, 2L, 3L}; |
| 161 | + |
| 162 | + // When / Then: non-primitive dtype is a programmer error on the encode path |
| 163 | + assertThatThrownBy(() -> SUT.encode(DTypes.UTF8, data, EncodeTestHelper.testCtx())) |
| 164 | + .isInstanceOf(VortexException.class) |
| 165 | + .hasMessageContaining("expected primitive"); |
| 166 | + } |
| 167 | + |
122 | 168 | @ParameterizedTest |
123 | 169 | @ValueSource(ints = {10, 100, 1023, 1024, 1025, 2048}) |
124 | 170 | void encode_roundTrip_i64_variousLengths(int n) { |
@@ -212,5 +258,122 @@ void encodeCascade_notApplicable_whenEmpty() { |
212 | 258 | // Then |
213 | 259 | assertThat(step.applicable()).isFalse(); |
214 | 260 | } |
| 261 | + |
| 262 | + @Test |
| 263 | + void encodeCascade_notApplicable_whenNonPrimitiveDtype() { |
| 264 | + // Given |
| 265 | + long[] data = {1L, 2L, 3L}; |
| 266 | + |
| 267 | + // When |
| 268 | + CascadeStep step = SUT.encodeCascade(DTypes.UTF8, data, EncodeTestHelper.testCtx()); |
| 269 | + |
| 270 | + // Then |
| 271 | + assertThat(step.applicable()).isFalse(); |
| 272 | + } |
| 273 | + |
| 274 | + @Test |
| 275 | + void encodeCascade_notApplicable_whenNoBitWidthGain() { |
| 276 | + // Given: every value needs the full 64 bits, so no target width beats the raw cost |
| 277 | + long[] data = new long[100]; |
| 278 | + for (int i = 0; i < data.length; i++) { |
| 279 | + data[i] = Long.MIN_VALUE | i; // bit 63 set → width 64 for all |
| 280 | + } |
| 281 | + |
| 282 | + // When |
| 283 | + CascadeStep step = SUT.encodeCascade(DTypes.I64, data, EncodeTestHelper.testCtx()); |
| 284 | + |
| 285 | + // Then |
| 286 | + assertThat(step.applicable()).isFalse(); |
| 287 | + } |
| 288 | + |
| 289 | + @ParameterizedTest |
| 290 | + @EnumSource(value = PType.class, names = {"I8", "U8", "I16", "U16", "I32", "U32"}) |
| 291 | + void encodeCascade_applicable_smallIntegerPTypes(PType ptype) { |
| 292 | + // Given: 100 small values + 1 outlier — exercises fromLongs for every narrow width |
| 293 | + int n = 100; |
| 294 | + DType dtype = dtypeOf(ptype); |
| 295 | + Object data = buildData(ptype, n); |
| 296 | + |
| 297 | + // When |
| 298 | + CascadeStep step = SUT.encodeCascade(dtype, data, EncodeTestHelper.testCtx()); |
| 299 | + |
| 300 | + // Then |
| 301 | + assertThat(step.applicable()).isTrue(); |
| 302 | + assertThat(step.openChildren()).hasSize(4); |
| 303 | + assertThat(step.openChildren().get(0).childDtype()).isEqualTo(dtype); |
| 304 | + assertThat(step.openChildren().get(3).childDtype()).isEqualTo(dtype); |
| 305 | + } |
| 306 | + } |
| 307 | + |
| 308 | + private static DType dtypeOf(PType ptype) { |
| 309 | + return new DType.Primitive(ptype, false); |
| 310 | + } |
| 311 | + |
| 312 | + /// Builds an array of the given primitive type: small values everywhere except a |
| 313 | + /// single high outlier at the midpoint, which forces a patch. |
| 314 | + private static Object buildData(PType ptype, int n) { |
| 315 | + return switch (ptype) { |
| 316 | + case I8, U8 -> { |
| 317 | + byte[] a = new byte[n]; |
| 318 | + for (int i = 0; i < n; i++) { |
| 319 | + a[i] = (byte) (i & 3); |
| 320 | + } |
| 321 | + a[n / 2] = (byte) (ptype == PType.U8 ? 0xC8 : 0x7F); |
| 322 | + yield a; |
| 323 | + } |
| 324 | + case I16, U16 -> { |
| 325 | + short[] a = new short[n]; |
| 326 | + for (int i = 0; i < n; i++) { |
| 327 | + a[i] = (short) (i & 3); |
| 328 | + } |
| 329 | + a[n / 2] = (short) (ptype == PType.U16 ? 0xFFFF : 0x7FFF); |
| 330 | + yield a; |
| 331 | + } |
| 332 | + case I32, U32 -> { |
| 333 | + int[] a = new int[n]; |
| 334 | + for (int i = 0; i < n; i++) { |
| 335 | + a[i] = i & 3; |
| 336 | + } |
| 337 | + a[n / 2] = ptype == PType.U32 ? 0xFFFF_FFFF : 0x7FFF_FFFF; |
| 338 | + yield a; |
| 339 | + } |
| 340 | + case I64, U64 -> { |
| 341 | + long[] a = new long[n]; |
| 342 | + for (int i = 0; i < n; i++) { |
| 343 | + a[i] = i & 3; |
| 344 | + } |
| 345 | + a[n / 2] = ptype == PType.U64 ? -1L : Long.MAX_VALUE; |
| 346 | + yield a; |
| 347 | + } |
| 348 | + default -> throw new IllegalArgumentException("unsupported: " + ptype); |
| 349 | + }; |
| 350 | + } |
| 351 | + |
| 352 | + /// Expected raw element value for [#buildData], used to compare against the decoded array. |
| 353 | + private static long expectedAt(PType ptype, int i, int n) { |
| 354 | + if (i != n / 2) { |
| 355 | + return i & 3; |
| 356 | + } |
| 357 | + return switch (ptype) { |
| 358 | + case I8 -> 0x7F; |
| 359 | + case U8 -> (byte) 0xC8; |
| 360 | + case I16 -> 0x7FFF; |
| 361 | + case U16 -> (short) 0xFFFF; |
| 362 | + case I32 -> 0x7FFF_FFFF; |
| 363 | + case U32 -> 0xFFFF_FFFF; |
| 364 | + case I64 -> Long.MAX_VALUE; |
| 365 | + case U64 -> -1L; |
| 366 | + default -> throw new IllegalArgumentException("unsupported: " + ptype); |
| 367 | + }; |
| 368 | + } |
| 369 | + |
| 370 | + private static long readLong(Array array, int i, PType ptype) { |
| 371 | + return switch (ptype) { |
| 372 | + case I8, U8 -> ((ByteArray) array).getByte(i); |
| 373 | + case I16, U16 -> ((ShortArray) array).getShort(i); |
| 374 | + case I32, U32 -> ((IntArray) array).getInt(i); |
| 375 | + case I64, U64 -> ((LongArray) array).getLong(i); |
| 376 | + default -> throw new IllegalArgumentException("unsupported: " + ptype); |
| 377 | + }; |
215 | 378 | } |
216 | 379 | } |
0 commit comments