Warp/test/buffer.spec.luau
2026-02-11 22:09:37 +07:00

969 lines
No EOL
29 KiB
Text

--!nocheck
return function()
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local WarpModule = ReplicatedStorage:WaitForChild("Warp")
local Warp = require(WarpModule)
local function roundTrip<T>(Buffer: any, value: T): T
local buf, refs = Buffer.write(value)
local decoded = Buffer.read(buf, refs)
return decoded
end
local function approxEqual(a: number, b: number, epsilon: number?): boolean
local eps = epsilon or 1e-4
return math.abs(a - b) < eps
end
describe("Warp.Buffer", function()
local Buffer = Warp.Buffer
-- primitives
it("round-trips tagged primitives (write/read)", function()
expect(roundTrip(Buffer, true)).to.equal(true)
expect(roundTrip(Buffer, false)).to.equal(false)
expect(roundTrip(Buffer, 0)).to.equal(0)
expect(roundTrip(Buffer, 127)).to.equal(127)
expect(roundTrip(Buffer, 128)).to.equal(128)
expect(roundTrip(Buffer, -1)).to.equal(-1)
expect(roundTrip(Buffer, -32)).to.equal(-32)
expect(roundTrip(Buffer, -33)).to.equal(-33)
expect(roundTrip(Buffer, 255)).to.equal(255)
expect(roundTrip(Buffer, 256)).to.equal(256)
expect(roundTrip(Buffer, "hi")).to.equal("hi")
expect(roundTrip(Buffer, string.rep("a", 40))).to.equal(string.rep("a", 40))
end)
it("round-trips nil", function()
local result = roundTrip(Buffer, nil)
expect(result).to.equal(nil)
end)
-- numbers
it("round-trips positive fixint (0-127)", function()
expect(roundTrip(Buffer, 0)).to.equal(0)
expect(roundTrip(Buffer, 1)).to.equal(1)
expect(roundTrip(Buffer, 63)).to.equal(63)
expect(roundTrip(Buffer, 127)).to.equal(127)
end)
it("round-trips negative fixint (-32 to -1)", function()
expect(roundTrip(Buffer, -1)).to.equal(-1)
expect(roundTrip(Buffer, -16)).to.equal(-16)
expect(roundTrip(Buffer, -32)).to.equal(-32)
end)
it("round-trips u8 range (128-255)", function()
expect(roundTrip(Buffer, 128)).to.equal(128)
expect(roundTrip(Buffer, 200)).to.equal(200)
expect(roundTrip(Buffer, 255)).to.equal(255)
end)
it("round-trips u16 range (256-65535)", function()
expect(roundTrip(Buffer, 256)).to.equal(256)
expect(roundTrip(Buffer, 1000)).to.equal(1000)
expect(roundTrip(Buffer, 65535)).to.equal(65535)
end)
it("round-trips u32 range (65536-4294967295)", function()
expect(roundTrip(Buffer, 65536)).to.equal(65536)
expect(roundTrip(Buffer, 1000000)).to.equal(1000000)
expect(roundTrip(Buffer, 4294967295)).to.equal(4294967295)
end)
it("round-trips i8 range (-128 to -33)", function()
expect(roundTrip(Buffer, -33)).to.equal(-33)
expect(roundTrip(Buffer, -100)).to.equal(-100)
expect(roundTrip(Buffer, -128)).to.equal(-128)
end)
it("round-trips i16 range (-32768 to -129)", function()
expect(roundTrip(Buffer, -129)).to.equal(-129)
expect(roundTrip(Buffer, -10000)).to.equal(-10000)
expect(roundTrip(Buffer, -32768)).to.equal(-32768)
end)
it("round-trips i32 range (-2147483648 to -32769)", function()
expect(roundTrip(Buffer, -32769)).to.equal(-32769)
expect(roundTrip(Buffer, -1000000)).to.equal(-1000000)
expect(roundTrip(Buffer, -2147483648)).to.equal(-2147483648)
end)
it("round-trips f32 floats", function()
local val = roundTrip(Buffer, 3.14)
expect(approxEqual(val, 3.14)).to.equal(true)
end)
it("round-trips f64 floats (high precision)", function()
local val = roundTrip(Buffer, 3.141592653589793)
expect(approxEqual(val, 3.141592653589793, 1e-12)).to.equal(true)
end)
it("round-trips large integers as f64", function()
local big = 2 ^ 40
expect(roundTrip(Buffer, big)).to.equal(big)
end)
it("round-trips NaN", function()
local val = roundTrip(Buffer, 0 / 0)
expect(val ~= val).to.equal(true) -- NaN ~= NaN
end)
-- strings
it("round-trips empty string", function()
expect(roundTrip(Buffer, "")).to.equal("")
end)
it("round-trips inline string (1-15 chars)", function()
expect(roundTrip(Buffer, "a")).to.equal("a")
expect(roundTrip(Buffer, "hello world!!!!")).to.equal("hello world!!!!")
end)
it("round-trips str8 string (16-255 chars)", function()
local s = string.rep("x", 200)
expect(roundTrip(Buffer, s)).to.equal(s)
end)
it("round-trips str16 string (256-65535 chars)", function()
local s = string.rep("y", 1000)
expect(roundTrip(Buffer, s)).to.equal(s)
end)
-- arrays & maps
it("round-trips arrays and maps (write/read)", function()
local arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
local decodedArr = roundTrip(Buffer, arr)
expect(type(decodedArr)).to.equal("table")
expect(#decodedArr).to.equal(#arr)
for i = 1, #arr do
expect(decodedArr[i]).to.equal(arr[i])
end
local map = { a = 1, b = true, c = "ok" }
local decodedMap = roundTrip(Buffer, map)
expect(type(decodedMap)).to.equal("table")
expect(decodedMap.a).to.equal(1)
expect(decodedMap.b).to.equal(true)
expect(decodedMap.c).to.equal("ok")
end)
it("round-trips empty array", function()
local decoded = roundTrip(Buffer, {})
expect(type(decoded)).to.equal("table")
expect(#decoded).to.equal(0)
end)
it("round-trips inline array (1-7 elements)", function()
local arr = { "a", "b", "c" }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(3)
expect(decoded[1]).to.equal("a")
expect(decoded[2]).to.equal("b")
expect(decoded[3]).to.equal("c")
end)
it("round-trips arr8 array (8-255 elements)", function()
local arr = {}
for i = 1, 100 do
arr[i] = "item" .. i
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(100)
expect(decoded[1]).to.equal("item1")
expect(decoded[100]).to.equal("item100")
end)
it("round-trips arr16 array (256+ elements)", function()
local arr = {}
for i = 1, 300 do
arr[i] = "v" .. i
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(300)
expect(decoded[1]).to.equal("v1")
expect(decoded[300]).to.equal("v300")
end)
it("round-trips inline map (1-7 pairs)", function()
local map = { x = 1, y = 2 }
local decoded = roundTrip(Buffer, map)
expect(decoded.x).to.equal(1)
expect(decoded.y).to.equal(2)
end)
it("round-trips map8 map (8-255 pairs)", function()
local map = {}
for i = 1, 50 do
map["key" .. i] = i
end
local decoded = roundTrip(Buffer, map)
for i = 1, 50 do
expect(decoded["key" .. i]).to.equal(i)
end
end)
it("round-trips deeply nested tables", function()
local nested = { a = { b = { c = { d = { 1, 2, 3 } } } } }
local decoded = roundTrip(Buffer, nested)
expect(decoded.a.b.c.d[1]).to.equal(1)
expect(decoded.a.b.c.d[2]).to.equal(2)
expect(decoded.a.b.c.d[3]).to.equal(3)
end)
it("round-trips mixed nested arrays and maps", function()
local data = {
{ name = "Alice", scores = { 10, 20, 30 } },
{ name = "Bob", scores = { 40, 50 } },
}
local decoded = roundTrip(Buffer, data)
expect(#decoded).to.equal(2)
expect(decoded[1].name).to.equal("Alice")
expect(#decoded[1].scores).to.equal(3)
expect(decoded[1].scores[2]).to.equal(20)
expect(decoded[2].name).to.equal("Bob")
expect(decoded[2].scores[1]).to.equal(40)
end)
-- typed number arrays
it("round-trips typed u8 array", function()
local arr = { 0, 50, 100, 200, 255 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(5)
for i = 1, 5 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed i8 array", function()
local arr = { -128, -50, 0, 50, 127 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(5)
for i = 1, 5 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed u16 array", function()
local arr = { 0, 1000, 30000, 65535 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(4)
for i = 1, 4 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed i16 array", function()
local arr = { -32768, -1000, 0, 1000, 32767 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(5)
for i = 1, 5 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed u32 array", function()
local arr = { 0, 100000, 1000000, 4294967295 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(4)
for i = 1, 4 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed i32 array", function()
local arr = { -2147483648, -100000, 0, 100000, 2147483647 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(5)
for i = 1, 5 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips typed f32 array", function()
local arr = { 1.5, 2.5, 3.5, 4.5 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(4)
for i = 1, 4 do
expect(approxEqual(decoded[i], arr[i])).to.equal(true)
end
end)
it("does not use typed array for fewer than 4 homogeneous numbers", function()
-- 3 elements should use regular array path, not typed
local arr = { 10, 20, 30 }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(3)
for i = 1, 3 do
expect(decoded[i]).to.equal(arr[i])
end
end)
-- boolean array bitpacking
it("round-trips boolean array (exact 8 = 1 byte boundary)", function()
local arr = { true, false, true, true, false, false, true, false }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(8)
for i = 1, 8 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips boolean array (9 elements = partial last byte)", function()
local arr = { true, false, true, true, false, false, true, false, true }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(9)
for i = 1, 9 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips boolean array (2 elements = minimum for bitpacking)", function()
local arr = { true, false }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(2)
expect(decoded[1]).to.equal(true)
expect(decoded[2]).to.equal(false)
end)
it("round-trips boolean array (all true)", function()
local arr = {}
for i = 1, 32 do
arr[i] = true
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(32)
for i = 1, 32 do
expect(decoded[i]).to.equal(true)
end
end)
it("round-trips boolean array (all false)", function()
local arr = {}
for i = 1, 32 do
arr[i] = false
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(32)
for i = 1, 32 do
expect(decoded[i]).to.equal(false)
end
end)
it("round-trips large boolean array (100 elements)", function()
local arr = {}
for i = 1, 100 do
arr[i] = (i % 3 == 0)
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(100)
for i = 1, 100 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("round-trips large boolean array (1000 elements)", function()
local arr = {}
for i = 1, 1000 do
arr[i] = (i % 7 < 3)
end
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(1000)
for i = 1, 1000 do
expect(decoded[i]).to.equal(arr[i])
end
end)
it("boolean array is smaller than per-element encoding", function()
local arr = {}
for i = 1, 64 do
arr[i] = (i % 2 == 0)
end
local packedBuf = Buffer.write(arr)
-- 64 bools bitpacked = tag(1) + varuint(1) + 8 bytes = 10 bytes
-- 64 bools tagged individually would be much larger
-- Use a reasonable threshold that accounts for bitpacking
expect(buffer.len(packedBuf) < 66).to.equal(true)
end)
it("does not bitpack a single boolean in an array", function()
-- count=1 should NOT trigger bitpacking, should use regular array
local arr = { true }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(1)
expect(decoded[1]).to.equal(true)
end)
it("does not bitpack mixed boolean/number array", function()
local arr = { true, false, 42, true }
local decoded = roundTrip(Buffer, arr)
expect(#decoded).to.equal(4)
expect(decoded[1]).to.equal(true)
expect(decoded[2]).to.equal(false)
expect(decoded[3]).to.equal(42)
expect(decoded[4]).to.equal(true)
end)
-- roblox types
it("round-trips Vector3", function()
local v = Vector3.new(1.5, -2.5, 3.75)
local decoded = roundTrip(Buffer, v)
expect(approxEqual(decoded.X, 1.5)).to.equal(true)
expect(approxEqual(decoded.Y, -2.5)).to.equal(true)
expect(approxEqual(decoded.Z, 3.75)).to.equal(true)
end)
it("round-trips Vector3.zero", function()
local decoded = roundTrip(Buffer, Vector3.zero)
expect(decoded.X).to.equal(0)
expect(decoded.Y).to.equal(0)
expect(decoded.Z).to.equal(0)
end)
it("round-trips Vector2", function()
local v = Vector2.new(10.5, -20.25)
local decoded = roundTrip(Buffer, v)
expect(approxEqual(decoded.X, 10.5)).to.equal(true)
expect(approxEqual(decoded.Y, -20.25)).to.equal(true)
end)
it("round-trips CFrame", function()
local cf = CFrame.new(1, 2, 3) * CFrame.Angles(0.5, 1.0, 1.5)
local decoded = roundTrip(Buffer, cf)
expect(approxEqual(decoded.Position.X, cf.Position.X)).to.equal(true)
expect(approxEqual(decoded.Position.Y, cf.Position.Y)).to.equal(true)
expect(approxEqual(decoded.Position.Z, cf.Position.Z)).to.equal(true)
local rx1, ry1, rz1 = cf:ToOrientation()
local rx2, ry2, rz2 = decoded:ToOrientation()
expect(approxEqual(rx1, rx2, 1e-3)).to.equal(true)
expect(approxEqual(ry1, ry2, 1e-3)).to.equal(true)
expect(approxEqual(rz1, rz2, 1e-3)).to.equal(true)
end)
it("round-trips CFrame.identity", function()
local decoded = roundTrip(Buffer, CFrame.identity)
expect(approxEqual(decoded.Position.X, 0)).to.equal(true)
expect(approxEqual(decoded.Position.Y, 0)).to.equal(true)
expect(approxEqual(decoded.Position.Z, 0)).to.equal(true)
end)
it("round-trips Color3 (byte precision)", function()
local c = Color3.fromRGB(128, 64, 255)
local decoded = roundTrip(Buffer, c)
expect(math.round(decoded.R * 255)).to.equal(128)
expect(math.round(decoded.G * 255)).to.equal(64)
expect(math.round(decoded.B * 255)).to.equal(255)
end)
it("round-trips Color3 (float precision for HDR)", function()
local c = Color3.new(2.0, 0.5, 1.5) -- R > 1 triggers float path
local decoded = roundTrip(Buffer, c)
expect(approxEqual(decoded.R, 2.0)).to.equal(true)
expect(approxEqual(decoded.G, 0.5)).to.equal(true)
expect(approxEqual(decoded.B, 1.5)).to.equal(true)
end)
it("round-trips BrickColor", function()
local bc = BrickColor.new("Bright red")
local decoded = roundTrip(Buffer, bc)
expect(decoded.Number).to.equal(bc.Number)
end)
it("round-trips UDim2", function()
local ud = UDim2.new(0.5, 10, 1.0, -20)
local decoded = roundTrip(Buffer, ud)
expect(approxEqual(decoded.X.Scale, 0.5)).to.equal(true)
expect(decoded.X.Offset).to.equal(10)
expect(approxEqual(decoded.Y.Scale, 1.0)).to.equal(true)
expect(decoded.Y.Offset).to.equal(-20)
end)
it("round-trips UDim", function()
local ud = UDim.new(0.75, -5)
local decoded = roundTrip(Buffer, ud)
expect(approxEqual(decoded.Scale, 0.75)).to.equal(true)
expect(decoded.Offset).to.equal(-5)
end)
it("round-trips Rect", function()
local r = Rect.new(10, 20, 300, 400)
local decoded = roundTrip(Buffer, r)
expect(approxEqual(decoded.Min.X, 10)).to.equal(true)
expect(approxEqual(decoded.Min.Y, 20)).to.equal(true)
expect(approxEqual(decoded.Max.X, 300)).to.equal(true)
expect(approxEqual(decoded.Max.Y, 400)).to.equal(true)
end)
it("round-trips NumberRange", function()
local nr = NumberRange.new(-5, 10)
local decoded = roundTrip(Buffer, nr)
expect(approxEqual(decoded.Min, -5)).to.equal(true)
expect(approxEqual(decoded.Max, 10)).to.equal(true)
end)
it("round-trips Ray", function()
local r = Ray.new(Vector3.new(1, 2, 3), Vector3.new(0, 1, 0))
local decoded = roundTrip(Buffer, r)
expect(approxEqual(decoded.Origin.X, 1)).to.equal(true)
expect(approxEqual(decoded.Origin.Y, 2)).to.equal(true)
expect(approxEqual(decoded.Origin.Z, 3)).to.equal(true)
expect(approxEqual(decoded.Direction.X, 0)).to.equal(true)
expect(approxEqual(decoded.Direction.Y, 1)).to.equal(true)
expect(approxEqual(decoded.Direction.Z, 0)).to.equal(true)
end)
it("round-trips EnumItem", function()
local e = Enum.Material.Grass
local decoded = roundTrip(Buffer, e)
expect(decoded).to.equal(e)
end)
it("round-trips ColorSequence", function()
local cs = ColorSequence.new({
ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 0)),
ColorSequenceKeypoint.new(0.5, Color3.fromRGB(0, 255, 0)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(0, 0, 255)),
})
local decoded = roundTrip(Buffer, cs)
expect(#decoded.Keypoints).to.equal(3)
expect(approxEqual(decoded.Keypoints[1].Time, 0)).to.equal(true)
expect(math.round(decoded.Keypoints[1].Value.R * 255)).to.equal(255)
expect(approxEqual(decoded.Keypoints[2].Time, 0.5)).to.equal(true)
expect(math.round(decoded.Keypoints[2].Value.G * 255)).to.equal(255)
end)
it("round-trips NumberSequence", function()
local ns = NumberSequence.new({
NumberSequenceKeypoint.new(0, 0, 0),
NumberSequenceKeypoint.new(0.5, 1, 0.2),
NumberSequenceKeypoint.new(1, 0, 0),
})
local decoded = roundTrip(Buffer, ns)
expect(#decoded.Keypoints).to.equal(3)
expect(approxEqual(decoded.Keypoints[2].Time, 0.5)).to.equal(true)
expect(approxEqual(decoded.Keypoints[2].Value, 1)).to.equal(true)
expect(approxEqual(decoded.Keypoints[2].Envelope, 0.2)).to.equal(true)
end)
-- buffer type
it("round-trips buffer value", function()
local b = buffer.create(8)
buffer.writeu32(b, 0, 12345678)
buffer.writef32(b, 4, 3.14)
local decoded = roundTrip(Buffer, b)
expect(buffer.len(decoded)).to.equal(8)
expect(buffer.readu32(decoded, 0)).to.equal(12345678)
expect(approxEqual(buffer.readf32(decoded, 4), 3.14)).to.equal(true)
end)
it("round-trips empty buffer", function()
local b = buffer.create(0)
local decoded = roundTrip(Buffer, b)
expect(buffer.len(decoded)).to.equal(0)
end)
-- instances
it("round-trips Instances through refs (write/read)", function()
local inst = Instance.new("Folder")
inst.Name = "WarpBufferTestFolder"
local buf, refs = Buffer.write(inst)
expect(type(buf)).to.equal("buffer")
expect(type(refs)).to.equal("table")
expect((refs :: { any })[1]).to.equal(inst)
local decodedWithRefs = Buffer.read(buf, refs)
expect(decodedWithRefs).to.equal(inst)
local decodedWithoutRefs = Buffer.read(buf)
expect(decodedWithoutRefs).to.equal(nil)
inst:Destroy()
end)
it("round-trips multiple instances with correct ref indices", function()
local a = Instance.new("Folder")
local b = Instance.new("Folder")
a.Name = "A"
b.Name = "B"
local data = { a, b, a } -- 'a' appears twice
local buf, refs = Buffer.write(data)
local decoded = Buffer.read(buf, refs)
expect(decoded[1]).to.equal(a)
expect(decoded[2]).to.equal(b)
expect(decoded[3]).to.equal(a)
a:Destroy()
b:Destroy()
end)
-- writer ops
it("can pack multiple tagged values and read them back (readAll)", function()
local w = Buffer.createWriter()
Buffer.pack(w, "a")
Buffer.pack(w, 123)
Buffer.pack(w, { 1, 2, 3, 4 })
local buf, refs = Buffer.buildWithRefs(w)
local values = Buffer.readAll(buf, refs)
expect(#values).to.equal(3)
expect(values[1]).to.equal("a")
expect(values[2]).to.equal(123)
expect(type(values[3])).to.equal("table")
expect(#(values[3] :: { any })).to.equal(4)
end)
it("writer reset clears cursor and refs", function()
local w = Buffer.createWriter()
Buffer.pack(w, "hello")
Buffer.pack(w, 42)
expect(w.cursor > 0).to.equal(true)
Buffer.reset(w)
expect(w.cursor).to.equal(0)
expect(#w.refs).to.equal(0)
end)
it("writer grows capacity automatically", function()
local w = Buffer.createWriter(4) -- tiny initial capacity
local longStr = string.rep("z", 200)
Buffer.pack(w, longStr)
expect(w.capacity >= 200).to.equal(true)
local buf = Buffer.build(w)
local decoded = Buffer.read(buf)
expect(decoded).to.equal(longStr)
end)
-- schema: primitives
it("schema round-trips u8", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.u8, 200)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.u8)
expect(val).to.equal(200)
end)
it("schema round-trips i16", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.i16, -1234)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.i16)
expect(val).to.equal(-1234)
end)
it("schema round-trips f32", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.f32, 3.14)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.f32)
expect(approxEqual(val, 3.14)).to.equal(true)
end)
it("schema round-trips boolean", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.boolean, true)
Buffer.packStrict(w, S.boolean, false)
local buf = Buffer.build(w)
local v1, pos = Buffer.readStrict(buf, 0, S.boolean)
local v2 = Buffer.readStrict(buf, pos, S.boolean)
expect(v1).to.equal(true)
expect(v2).to.equal(false)
end)
it("schema round-trips string", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.string, "test string!")
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.string)
expect(val).to.equal("test string!")
end)
-- (schema) roblox types
it("schema round-trips vector3", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.vector3, Vector3.new(1, 2, 3))
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.vector3)
expect(approxEqual(val.X, 1)).to.equal(true)
expect(approxEqual(val.Y, 2)).to.equal(true)
expect(approxEqual(val.Z, 3)).to.equal(true)
end)
it("schema round-trips vector2", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.vector2, Vector2.new(-5, 10))
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.vector2)
expect(approxEqual(val.X, -5)).to.equal(true)
expect(approxEqual(val.Y, 10)).to.equal(true)
end)
it("schema round-trips color3", function()
local S = Buffer.Schema
local w = Buffer.createWriter()
Buffer.packStrict(w, S.color3, Color3.fromRGB(100, 200, 50))
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.color3)
expect(math.round(val.R * 255)).to.equal(100)
expect(math.round(val.G * 255)).to.equal(200)
expect(math.round(val.B * 255)).to.equal(50)
end)
it("schema round-trips cframe", function()
local S = Buffer.Schema
local cf = CFrame.new(10, -5, 3) * CFrame.Angles(0.1, 0.2, 0.3)
local w = Buffer.createWriter()
Buffer.packStrict(w, S.cframe, cf)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, S.cframe)
expect(approxEqual(val.Position.X, cf.Position.X)).to.equal(true)
expect(approxEqual(val.Position.Y, cf.Position.Y)).to.equal(true)
expect(approxEqual(val.Position.Z, cf.Position.Z)).to.equal(true)
end)
it("schema round-trips instance", function()
local S = Buffer.Schema
local inst = Instance.new("Folder")
local w = Buffer.createWriter()
Buffer.packStrict(w, S.instance, inst)
local buf, refs = Buffer.buildWithRefs(w)
local val = Buffer.readStrict(buf, 0, S.instance, refs)
expect(val).to.equal(inst)
inst:Destroy()
end)
-- (schema) composites
it("schema round-trips struct", function()
local S = Buffer.Schema
local schema = S.struct({
x = S.f32,
y = S.f32,
name = S.string,
active = S.boolean,
})
local data = { x = 1.5, y = -2.5, name = "test", active = true }
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, data)
local buf = Buffer.build(w)
local decoded = Buffer.readStrict(buf, 0, schema)
expect(decoded.name).to.equal("test")
expect(decoded.active).to.equal(true)
expect(approxEqual(decoded.x, 1.5)).to.equal(true)
expect(approxEqual(decoded.y, -2.5)).to.equal(true)
end)
it("schema round-trips array of primitives", function()
local S = Buffer.Schema
local schema = S.array(S.u16)
local data = { 100, 200, 300, 65535 }
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, data)
local buf = Buffer.build(w)
local decoded = Buffer.readStrict(buf, 0, schema)
expect(#decoded).to.equal(4)
for i = 1, 4 do
expect(decoded[i]).to.equal(data[i])
end
end)
it("schema round-trips map", function()
local S = Buffer.Schema
local schema = S.map(S.string, S.i32)
local data = { hp = 100, mp = -50, xp = 99999 }
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, data)
local buf = Buffer.build(w)
local decoded = Buffer.readStrict(buf, 0, schema)
expect(decoded.hp).to.equal(100)
expect(decoded.mp).to.equal(-50)
expect(decoded.xp).to.equal(99999)
end)
it("schema round-trips optional (present)", function()
local S = Buffer.Schema
local schema = S.optional(S.u32)
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, 42)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, schema)
expect(val).to.equal(42)
end)
it("schema round-trips optional (nil)", function()
local S = Buffer.Schema
local schema = S.optional(S.u32)
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, nil)
local buf = Buffer.build(w)
local val = Buffer.readStrict(buf, 0, schema)
expect(val).to.equal(nil)
end)
it("schema round-trips nested struct with arrays", function()
local S = Buffer.Schema
local schema = S.struct({
name = S.string,
scores = S.array(S.u16),
pos = S.vector3,
})
local data = {
name = "Player1",
scores = { 100, 250, 999 },
pos = Vector3.new(10, 20, 30),
}
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, data)
local buf = Buffer.build(w)
local decoded = Buffer.readStrict(buf, 0, schema)
expect(decoded.name).to.equal("Player1")
expect(#decoded.scores).to.equal(3)
expect(decoded.scores[1]).to.equal(100)
expect(decoded.scores[2]).to.equal(250)
expect(decoded.scores[3]).to.equal(999)
expect(approxEqual(decoded.pos.X, 10)).to.equal(true)
end)
it("schema round-trips array of structs", function()
local S = Buffer.Schema
local schema = S.array(S.struct({
id = S.u16,
alive = S.boolean,
}))
local data = {
{ id = 1, alive = true },
{ id = 2, alive = false },
{ id = 100, alive = true },
}
local w = Buffer.createWriter()
Buffer.packStrict(w, schema, data)
local buf = Buffer.build(w)
local decoded = Buffer.readStrict(buf, 0, schema)
expect(#decoded).to.equal(3)
expect(decoded[1].id).to.equal(1)
expect(decoded[1].alive).to.equal(true)
expect(decoded[2].id).to.equal(2)
expect(decoded[2].alive).to.equal(false)
expect(decoded[3].id).to.equal(100)
expect(decoded[3].alive).to.equal(true)
end)
-- event batches
it("encodes/decodes event batches (writeEvents/readEvents), with and without schemas", function()
local S = Buffer.Schema
local schemas = {
MyRemote = S.struct({
count = S.u8,
msg = S.string,
}),
}
local payload = {
count = 7,
msg = "hello",
}
local events = {
{ "MyRemote", { payload } },
{ "OtherRemote", { 1, 2 } },
}
local w = Buffer.createWriter()
Buffer.writeEvents(w, events, schemas)
local buf, refs = Buffer.buildWithRefs(w)
local decoded = Buffer.readEvents(buf, refs, schemas)
expect(#decoded).to.equal(2)
expect(decoded[1][1]).to.equal("MyRemote")
expect(decoded[1][2][1].count).to.equal(7)
expect(decoded[1][2][1].msg).to.equal("hello")
expect(decoded[2][1]).to.equal("OtherRemote")
expect(decoded[2][2][1]).to.equal(1)
expect(decoded[2][2][2]).to.equal(2)
end)
it("encodes/decodes empty event batch", function()
local w = Buffer.createWriter()
Buffer.writeEvents(w, {}, {})
local buf, refs = Buffer.buildWithRefs(w)
local decoded = Buffer.readEvents(buf, refs, {})
expect(#decoded).to.equal(0)
end)
-- edge cases
it("round-trips table with nil holes treated as map", function()
-- sparse table should not crash
local data = { [1] = "a", [3] = "c", [5] = "e" }
local decoded = roundTrip(Buffer, data)
expect(decoded[1]).to.equal("a")
expect(decoded[3]).to.equal("c")
expect(decoded[5]).to.equal("e")
end)
it("round-trips complex real-world payload", function()
local payload = {
position = Vector3.new(100, 50, -200),
health = 85,
inventory = { 1, 2, 3, 4, 5, 6, 7, 8 },
equipped = true,
name = "SwordOfDoom",
color = Color3.fromRGB(255, 128, 0),
settings = {
volume = 75,
muted = false,
},
}
local decoded = roundTrip(Buffer, payload)
expect(approxEqual(decoded.position.X, 100)).to.equal(true)
expect(decoded.health).to.equal(85)
expect(#decoded.inventory).to.equal(8)
expect(decoded.equipped).to.equal(true)
expect(decoded.name).to.equal("SwordOfDoom")
expect(decoded.settings.volume).to.equal(75)
expect(decoded.settings.muted).to.equal(false)
end)
end)
end