Skip to content

Commit 9e8db49

Browse files
committed
test: add comprehensive size assertion and encoding distinction tests for PackedFormat and PackedUtils
1 parent 2729aa0 commit 9e8db49

2 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/test/kotlin/com/eignex/kencode/PackedFormatTest.kt

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,126 @@ class PackedFormatTest {
359359
}
360360
}
361361

362+
// --- Size assertions ---
363+
364+
@Test
365+
fun `class with no booleans or nullables has no bitmask header`() {
366+
// 4 (Int) + 8 (Long) + 1 (VarInt len) + 13 (String bytes) = 26
367+
val bytes = PackedFormat.encodeToByteArray(
368+
NoBooleansNoNulls.serializer(),
369+
NoBooleansNoNulls(42, 42L, "No flags here")
370+
)
371+
assertEquals(26, bytes.size)
372+
}
373+
374+
@Test
375+
fun `boolean fields pack into single bitmask byte`() {
376+
// 2 booleans → 1 bitmask byte (VarLong), 2 ints → 4 bytes each = 9 total
377+
val bytes = PackedFormat.encodeToByteArray(
378+
SimpleIntsAndBooleans.serializer(),
379+
SimpleIntsAndBooleans(0, 0, false, false)
380+
)
381+
assertEquals(9, bytes.size)
382+
}
383+
384+
@Test
385+
fun `empty string encodes as single zero byte`() {
386+
val bytes = PackedFormat.encodeToByteArray(String.serializer(), "")
387+
assertEquals(1, bytes.size)
388+
assertEquals(0, bytes[0])
389+
}
390+
391+
@Test
392+
fun `empty list encodes as single zero byte`() {
393+
val bytes = PackedFormat.encodeToByteArray(ListSerializer(Int.serializer()), emptyList())
394+
assertEquals(1, bytes.size)
395+
assertEquals(0, bytes[0])
396+
}
397+
398+
@Test
399+
fun `empty map roundtrip`() {
400+
assertPackedRoundtrip(MapHolder.serializer(), MapHolder(emptyMap(), null))
401+
assertPackedRoundtrip(MapHolder.serializer(), MapHolder(emptyMap(), emptyMap()))
402+
}
403+
404+
// --- VarInt / VarUInt encoding distinction ---
405+
406+
@Test
407+
fun `VarUInt does not apply zigzag so negative inputs encode large`() {
408+
// @VarUInt(-1 as Int) = 0xFFFFFFFF unsigned = 5 bytes
409+
// @VarUInt(-1L as Long) = 0xFFFFFFFFFFFFFFFF unsigned = 10 bytes
410+
val uintNegative = VarIntVarUIntPayload(0, 0L, -1, -1L, 0, 0)
411+
val bytes = PackedFormat.encodeToByteArray(VarIntVarUIntPayload.serializer(), uintNegative)
412+
// VarInt(0)=1, VarLong(0)=1, VarInt(0xFFFFFFFF)=5, VarLong(0xFFFFFFFF…)=10, Int=4, Long=8 → 29
413+
assertEquals(29, bytes.size)
414+
415+
// @VarInt(-1) = zigzag(−1) = 1 = 1 byte
416+
val intNegative = VarIntVarUIntPayload(-1, -1L, 0, 0L, 0, 0)
417+
val bytes2 = PackedFormat.encodeToByteArray(VarIntVarUIntPayload.serializer(), intNegative)
418+
// VarInt(1)=1, VarLong(1)=1, VarInt(0)=1, VarLong(0)=1, Int=4, Long=8 → 16
419+
assertEquals(16, bytes2.size)
420+
421+
assertPackedRoundtrip(VarIntVarUIntPayload.serializer(), uintNegative)
422+
assertPackedRoundtrip(VarIntVarUIntPayload.serializer(), intNegative)
423+
}
424+
425+
// --- defaultVarInt / defaultZigZag interaction ---
426+
427+
@Test
428+
fun `both defaultVarInt and defaultZigZag active uses zigzag`() {
429+
val fmt = PackedFormat { defaultVarInt = true; defaultZigZag = true }
430+
val payload = UnannotatedPayload(-1, -1L)
431+
val bytes = fmt.encodeToByteArray(UnannotatedPayload.serializer(), payload)
432+
// zigzag(−1)=1 for both → 1 + 1 = 2 bytes
433+
assertEquals(2, bytes.size)
434+
assertEquals(payload, fmt.decodeFromByteArray(UnannotatedPayload.serializer(), bytes))
435+
}
436+
437+
// --- Nullability roundtrips not covered in the main table ---
438+
439+
@Test
440+
fun `deep null mid-chain roundtrip`() {
441+
assertPackedRoundtrip(DeepNested.serializer(), DeepNested("root", Level1(false, null)))
442+
}
443+
444+
// --- UTF-8 character encoding ---
445+
446+
@Test
447+
fun `3-byte UTF-8 char roundtrip`() {
448+
assertPackedRoundtrip(
449+
AllPrimitiveTypes.serializer(),
450+
AllPrimitiveTypes(0, 0L, 0, 0, 0f, 0.0, '', false, "")
451+
)
452+
}
453+
454+
// --- Truncation error coverage ---
455+
456+
@Test
457+
fun `truncated varint throws`() {
458+
// Continuation bit set but no following byte
459+
val incomplete = byteArrayOf(0x80.toByte())
460+
assertFailsWith<IllegalArgumentException> {
461+
PackedUtils.decodeVarInt(incomplete, 0)
462+
}
463+
}
464+
465+
@Test
466+
fun `truncated string bytes throws`() {
467+
// VarInt says length 5 but only 2 bytes follow
468+
val bad = byteArrayOf(5, 'h'.code.toByte(), 'i'.code.toByte())
469+
assertFailsWith<IllegalArgumentException> {
470+
PackedFormat.decodeFromByteArray(String.serializer(), bad)
471+
}
472+
}
473+
474+
@Test
475+
fun `truncated bitmask header throws`() {
476+
// NullableFieldsPayload expects a bitmask but gets empty input
477+
assertFailsWith<IllegalArgumentException> {
478+
PackedFormat.decodeFromByteArray(NullableFieldsPayload.serializer(), byteArrayOf())
479+
}
480+
}
481+
362482
@Test
363483
fun `decodeElementIndex simulates sequential decoding for classes and collections`() {
364484
// 1. Test the Class path

src/test/kotlin/com/eignex/kencode/PackedUtilsTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,7 @@ class BitPackingTest {
304304
@Test
305305
fun `flags roundtrip`() {
306306
val flags = booleanArrayOf(true, false, true, true, false)
307-
val longFlags =
308-
PackedUtils.packFlagsToLong(*flags.toTypedArray().toBooleanArray())
307+
val longFlags = PackedUtils.packFlagsToLong(flags)
309308
val unpacked = PackedUtils.unpackFlagsFromLong(longFlags, flags.size)
310309
assertContentEquals(flags, unpacked)
311310
}

0 commit comments

Comments
 (0)