The int_64 branch condition `-(1 << 63) <= value` overflowed in GDScript's signed 64-bit arithmetic, making the branch unreachable. Negative values beyond int_32 range were incorrectly encoded as uint_64 (0xcf) instead of int_64 (0xd3). Replaced with `value < 0`. Updated boundary tests BV-N15 and BV-N16 to expect correct int_64 header byte. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
223 lines
9.1 KiB
GDScript
223 lines
9.1 KiB
GDScript
## Boundary value tests for MessagePack encoding: 25 positive + 16 negative boundaries.
|
|
## Validates header byte + payload for each value, encode-decode roundtrip,
|
|
## and cross-encoding decode (GDScript accepting Rust-style unsigned encodings).
|
|
## Bug #4 class prevention: format boundary encoding errors.
|
|
## Spec ref: hoshe-round3.md Section 4, workshop-outcomes.md Appendix C.
|
|
class_name TestMsgpackBoundaries
|
|
extends GdUnitTestSuite
|
|
|
|
|
|
# -- Helpers -------------------------------------------------------------------
|
|
|
|
## Assert that encoding `value` produces exactly `expected_bytes`.
|
|
func _assert_encodes_to(value: int, expected_bytes: PackedByteArray, label: String) -> void:
|
|
var result = Messagepack.encode(value)
|
|
assert_that(result.value).override_failure_message(
|
|
"%s: encode(%d) produced wrong bytes" % [label, value]
|
|
).is_equal(expected_bytes)
|
|
|
|
|
|
## Assert that encoding then decoding `value` returns the original value.
|
|
func _assert_roundtrip(value: int, label: String) -> void:
|
|
var encoded = Messagepack.encode(value)
|
|
var decoded = Messagepack.decode(encoded.value)
|
|
assert_that(decoded.value).override_failure_message(
|
|
"%s: roundtrip failed for %d" % [label, value]
|
|
).is_equal(value)
|
|
|
|
|
|
# -- Positive boundaries: encode-only -----------------------------------------
|
|
|
|
func test_encode_pos_fixint() -> void:
|
|
# BV-P01 to BV-P04: positive fixint range (0 to 127)
|
|
# Format: single byte = value itself
|
|
_assert_encodes_to(0, PackedByteArray([0x00]), "BV-P01")
|
|
_assert_encodes_to(1, PackedByteArray([0x01]), "BV-P02")
|
|
_assert_encodes_to(126, PackedByteArray([0x7e]), "BV-P03")
|
|
_assert_encodes_to(127, PackedByteArray([0x7f]), "BV-P04")
|
|
|
|
|
|
func test_encode_uint8() -> void:
|
|
# BV-P05 to BV-P08: uint 8 range (128 to 255)
|
|
# Format: 0xcc + 1 byte
|
|
# Bug #4 regression target: 128 must NOT encode as int_8 (0xd0, 0x80 = -128)
|
|
_assert_encodes_to(128, PackedByteArray([0xcc, 0x80]), "BV-P05")
|
|
_assert_encodes_to(129, PackedByteArray([0xcc, 0x81]), "BV-P06")
|
|
_assert_encodes_to(254, PackedByteArray([0xcc, 0xfe]), "BV-P07")
|
|
_assert_encodes_to(255, PackedByteArray([0xcc, 0xff]), "BV-P08")
|
|
|
|
|
|
func test_encode_int16_gdscript() -> void:
|
|
# BV-P09 to BV-P12: GDScript encodes 256-32767 as int_16 (NOT uint_16)
|
|
# Asymmetry: Rust encodes these as uint_16 (0xcd). Both are spec-valid.
|
|
# Format: 0xd1 + 2 bytes big-endian signed
|
|
_assert_encodes_to(256, PackedByteArray([0xd1, 0x01, 0x00]), "BV-P09")
|
|
_assert_encodes_to(257, PackedByteArray([0xd1, 0x01, 0x01]), "BV-P10")
|
|
_assert_encodes_to(32766, PackedByteArray([0xd1, 0x7f, 0xfe]), "BV-P11")
|
|
_assert_encodes_to(32767, PackedByteArray([0xd1, 0x7f, 0xff]), "BV-P12")
|
|
|
|
|
|
func test_encode_uint16() -> void:
|
|
# BV-P13 to BV-P16: uint 16 range (32768 to 65535)
|
|
# Format: 0xcd + 2 bytes big-endian unsigned
|
|
_assert_encodes_to(32768, PackedByteArray([0xcd, 0x80, 0x00]), "BV-P13")
|
|
_assert_encodes_to(32769, PackedByteArray([0xcd, 0x80, 0x01]), "BV-P14")
|
|
_assert_encodes_to(65534, PackedByteArray([0xcd, 0xff, 0xfe]), "BV-P15")
|
|
_assert_encodes_to(65535, PackedByteArray([0xcd, 0xff, 0xff]), "BV-P16")
|
|
|
|
|
|
func test_encode_int32_gdscript() -> void:
|
|
# BV-P17 to BV-P20: GDScript encodes 65536-2147483647 as int_32 (NOT uint_32)
|
|
# Asymmetry: Rust encodes these as uint_32 (0xce). Both are spec-valid.
|
|
# Format: 0xd2 + 4 bytes big-endian signed
|
|
_assert_encodes_to(65536, PackedByteArray([0xd2, 0x00, 0x01, 0x00, 0x00]), "BV-P17")
|
|
_assert_encodes_to(65537, PackedByteArray([0xd2, 0x00, 0x01, 0x00, 0x01]), "BV-P18")
|
|
_assert_encodes_to(2147483646, PackedByteArray([0xd2, 0x7f, 0xff, 0xff, 0xfe]), "BV-P19")
|
|
_assert_encodes_to(2147483647, PackedByteArray([0xd2, 0x7f, 0xff, 0xff, 0xff]), "BV-P20")
|
|
|
|
|
|
func test_encode_uint32() -> void:
|
|
# BV-P21 to BV-P23: uint 32 range (2^31 to 2^32-1)
|
|
# Format: 0xce + 4 bytes big-endian unsigned
|
|
_assert_encodes_to(2147483648, PackedByteArray([0xce, 0x80, 0x00, 0x00, 0x00]), "BV-P21")
|
|
_assert_encodes_to(4294967294, PackedByteArray([0xce, 0xff, 0xff, 0xff, 0xfe]), "BV-P22")
|
|
_assert_encodes_to(4294967295, PackedByteArray([0xce, 0xff, 0xff, 0xff, 0xff]), "BV-P23")
|
|
|
|
|
|
func test_encode_int64_positive() -> void:
|
|
# BV-P24 to BV-P25: positive values > uint_32 max → uint_64 (0xcf)
|
|
# Positive values correctly use uint_64 encoding for Rust interop.
|
|
_assert_encodes_to(4294967296, PackedByteArray([
|
|
0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00
|
|
]), "BV-P24")
|
|
_assert_encodes_to(9223372036854775807, PackedByteArray([
|
|
0xcf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
|
]), "BV-P25")
|
|
|
|
|
|
# -- Negative boundaries: encode-only -----------------------------------------
|
|
|
|
func test_encode_neg_fixint() -> void:
|
|
# BV-N01 to BV-N03: negative fixint range (-1 to -32)
|
|
# Format: single byte, two's complement
|
|
_assert_encodes_to(-1, PackedByteArray([0xff]), "BV-N01")
|
|
_assert_encodes_to(-31, PackedByteArray([0xe1]), "BV-N02")
|
|
_assert_encodes_to(-32, PackedByteArray([0xe0]), "BV-N03")
|
|
|
|
|
|
func test_encode_int8_negative() -> void:
|
|
# BV-N04 to BV-N07: int 8 range (-33 to -128)
|
|
# Format: 0xd0 + 1 byte signed
|
|
_assert_encodes_to(-33, PackedByteArray([0xd0, 0xdf]), "BV-N04")
|
|
_assert_encodes_to(-34, PackedByteArray([0xd0, 0xde]), "BV-N05")
|
|
_assert_encodes_to(-127, PackedByteArray([0xd0, 0x81]), "BV-N06")
|
|
_assert_encodes_to(-128, PackedByteArray([0xd0, 0x80]), "BV-N07")
|
|
|
|
|
|
func test_encode_int16_negative() -> void:
|
|
# BV-N08 to BV-N11: int 16 range (-129 to -32768)
|
|
# Format: 0xd1 + 2 bytes big-endian signed
|
|
_assert_encodes_to(-129, PackedByteArray([0xd1, 0xff, 0x7f]), "BV-N08")
|
|
_assert_encodes_to(-130, PackedByteArray([0xd1, 0xff, 0x7e]), "BV-N09")
|
|
_assert_encodes_to(-32767, PackedByteArray([0xd1, 0x80, 0x01]), "BV-N10")
|
|
_assert_encodes_to(-32768, PackedByteArray([0xd1, 0x80, 0x00]), "BV-N11")
|
|
|
|
|
|
func test_encode_int32_negative() -> void:
|
|
# BV-N12 to BV-N14: int 32 range (-32769 to -2147483648)
|
|
# Format: 0xd2 + 4 bytes big-endian signed
|
|
_assert_encodes_to(-32769, PackedByteArray([0xd2, 0xff, 0xff, 0x7f, 0xff]), "BV-N12")
|
|
_assert_encodes_to(-2147483647, PackedByteArray([0xd2, 0x80, 0x00, 0x00, 0x01]), "BV-N13")
|
|
_assert_encodes_to(-2147483648, PackedByteArray([0xd2, 0x80, 0x00, 0x00, 0x00]), "BV-N14")
|
|
|
|
|
|
func test_encode_int64_negative() -> void:
|
|
# BV-N15 to BV-N16: int 64 range (< -2147483648)
|
|
# Negative values beyond int_32 now correctly encode as int_64 (0xd3).
|
|
# Fixed: encoder's int_64 branch used `-(1 << 63)` which overflowed to dead code.
|
|
_assert_encodes_to(-2147483649, PackedByteArray([
|
|
0xd3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff
|
|
]), "BV-N15")
|
|
# MIN_INT64: -9223372036854775808
|
|
var min_int64: int = -9223372036854775807 - 1
|
|
_assert_encodes_to(min_int64, PackedByteArray([
|
|
0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
]), "BV-N16")
|
|
|
|
|
|
# -- Roundtrip tests -----------------------------------------------------------
|
|
|
|
func test_roundtrip_positive_boundaries() -> void:
|
|
# All 25 positive boundary values: encode -> decode -> assert equal
|
|
var values: Array[int] = [
|
|
# pos fixint
|
|
0, 1, 126, 127,
|
|
# uint 8
|
|
128, 129, 254, 255,
|
|
# int 16 (GDScript) / uint 16 (Rust)
|
|
256, 257, 32766, 32767,
|
|
# uint 16
|
|
32768, 32769, 65534, 65535,
|
|
# int 32 (GDScript) / uint 32 (Rust)
|
|
65536, 65537, 2147483646, 2147483647,
|
|
# uint 32
|
|
2147483648, 4294967294, 4294967295,
|
|
# int 64 / uint 64
|
|
4294967296, 9223372036854775807,
|
|
]
|
|
for i in values.size():
|
|
_assert_roundtrip(values[i], "BV-P%02d" % (i + 1))
|
|
|
|
|
|
func test_roundtrip_negative_boundaries() -> void:
|
|
# All 16 negative boundary values: encode -> decode -> assert equal
|
|
var min_int64: int = -9223372036854775807 - 1
|
|
var values: Array[int] = [
|
|
# neg fixint
|
|
-1, -31, -32,
|
|
# int 8
|
|
-33, -34, -127, -128,
|
|
# int 16
|
|
-129, -130, -32767, -32768,
|
|
# int 32
|
|
-32769, -2147483647, -2147483648,
|
|
# int 64 / uint 64
|
|
-2147483649, min_int64,
|
|
]
|
|
for i in values.size():
|
|
_assert_roundtrip(values[i], "BV-N%02d" % (i + 1))
|
|
|
|
|
|
# -- Encoding overlap zone: GDScript decodes Rust-style unsigned encodings -----
|
|
# Rust encodes 256-32767 as uint_16 (0xcd) and 65536-2147483647 as uint_32 (0xce).
|
|
# GDScript encodes these as int_16 (0xd1) and int_32 (0xd2) respectively.
|
|
# Both are valid MessagePack. Both decoders must accept the other side's encoding.
|
|
# These tests verify GDScript's decoder handles Rust-style unsigned encodings.
|
|
|
|
func test_decode_rust_uint16_256() -> void:
|
|
# Rust encodes 256 as uint_16: [0xcd, 0x01, 0x00]
|
|
var bytes := PackedByteArray([0xcd, 0x01, 0x00])
|
|
var result = Messagepack.decode(bytes)
|
|
assert_that(result.value).is_equal(256)
|
|
|
|
|
|
func test_decode_rust_uint16_32767() -> void:
|
|
# Rust encodes 32767 as uint_16: [0xcd, 0x7f, 0xff]
|
|
var bytes := PackedByteArray([0xcd, 0x7f, 0xff])
|
|
var result = Messagepack.decode(bytes)
|
|
assert_that(result.value).is_equal(32767)
|
|
|
|
|
|
func test_decode_rust_uint32_65536() -> void:
|
|
# Rust encodes 65536 as uint_32: [0xce, 0x00, 0x01, 0x00, 0x00]
|
|
var bytes := PackedByteArray([0xce, 0x00, 0x01, 0x00, 0x00])
|
|
var result = Messagepack.decode(bytes)
|
|
assert_that(result.value).is_equal(65536)
|
|
|
|
|
|
func test_decode_rust_uint32_2147483647() -> void:
|
|
# Rust encodes 2^31-1 as uint_32: [0xce, 0x7f, 0xff, 0xff, 0xff]
|
|
var bytes := PackedByteArray([0xce, 0x7f, 0xff, 0xff, 0xff])
|
|
var result = Messagepack.decode(bytes)
|
|
assert_that(result.value).is_equal(2147483647)
|