302 lines
9.4 KiB
Lua
302 lines
9.4 KiB
Lua
|
local LibSerialize = require("LibSerialize")
|
||
|
|
||
|
local pairs = pairs
|
||
|
local type = type
|
||
|
local tostring = tostring
|
||
|
local assert = assert
|
||
|
local unpack = unpack
|
||
|
local pcall = pcall
|
||
|
|
||
|
|
||
|
--[[---------------------------------------------------------------------------
|
||
|
Examples from the top of LibSerialize.lua
|
||
|
--]]---------------------------------------------------------------------------
|
||
|
|
||
|
do
|
||
|
local t = { "test", [false] = {} }
|
||
|
t[ t[false] ] = "hello"
|
||
|
local serialized = LibSerialize:Serialize(t, "extra")
|
||
|
local success, tab, str = LibSerialize:Deserialize(serialized)
|
||
|
assert(success)
|
||
|
assert(tab[1] == "test")
|
||
|
assert(tab[ tab[false] ] == "hello")
|
||
|
assert(str == "extra")
|
||
|
end
|
||
|
|
||
|
do
|
||
|
local serialized = LibSerialize:SerializeEx(
|
||
|
{ errorOnUnserializableType = false },
|
||
|
print, { a = 1, b = print })
|
||
|
local success, fn, tab = LibSerialize:Deserialize(serialized)
|
||
|
assert(success)
|
||
|
assert(fn == nil)
|
||
|
assert(tab.a == 1)
|
||
|
assert(tab.b == nil)
|
||
|
end
|
||
|
|
||
|
do
|
||
|
local t = { a = 1 }
|
||
|
t.t = t
|
||
|
t[t] = "test"
|
||
|
local serialized = LibSerialize:Serialize(t)
|
||
|
local success, tab = LibSerialize:Deserialize(serialized)
|
||
|
assert(success)
|
||
|
assert(tab.t.t.t.t.t.t.a == 1)
|
||
|
assert(tab[tab.t] == "test")
|
||
|
end
|
||
|
|
||
|
do
|
||
|
local t = { a = 1, b = print, c = 3 }
|
||
|
local nested = { a = 1, b = print, c = 3 }
|
||
|
t.nested = nested
|
||
|
setmetatable(nested, { __LibSerialize = {
|
||
|
filter = function(t, k, v) return k ~= "c" end
|
||
|
}})
|
||
|
local opts = {
|
||
|
filter = function(t, k, v) return LibSerialize:IsSerializableType(k, v) end
|
||
|
}
|
||
|
local serialized = LibSerialize:SerializeEx(opts, t)
|
||
|
local success, tab = LibSerialize:Deserialize(serialized)
|
||
|
assert(success)
|
||
|
assert(tab.a == 1)
|
||
|
assert(tab.b == nil)
|
||
|
assert(tab.c == 3)
|
||
|
assert(tab.nested.a == 1)
|
||
|
assert(tab.nested.b == nil)
|
||
|
assert(tab.nested.c == nil)
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[---------------------------------------------------------------------------
|
||
|
Test of stable serialization
|
||
|
--]]---------------------------------------------------------------------------
|
||
|
|
||
|
do
|
||
|
local t = { a = 1, b = print, c = 3 }
|
||
|
local nested = { a = 1, b = print, c = 3 }
|
||
|
t.nested = nested
|
||
|
setmetatable(nested, { __LibSerialize = {
|
||
|
filter = function(t, k, v) return k ~= "c" end
|
||
|
}})
|
||
|
local opts = {
|
||
|
filter = function(t, k, v) return LibSerialize:IsSerializableType(k, v) end,
|
||
|
stable = true
|
||
|
}
|
||
|
local serialized = LibSerialize:SerializeEx(opts, t)
|
||
|
local success, tab = LibSerialize:Deserialize(serialized)
|
||
|
assert(success)
|
||
|
assert(tab.a == 1)
|
||
|
assert(tab.b == nil)
|
||
|
assert(tab.c == 3)
|
||
|
assert(tab.nested.a == 1)
|
||
|
assert(tab.nested.b == nil)
|
||
|
assert(tab.nested.c == nil)
|
||
|
end
|
||
|
|
||
|
do
|
||
|
local t1 = { x = "y", "test", [false] = { 1, 2, 3, a = "b" } }
|
||
|
local opts = {
|
||
|
stable = true,
|
||
|
filter = function(t, k, v) return not tonumber(k) or tonumber(k) < 100 end
|
||
|
}
|
||
|
local serialized1 = LibSerialize:SerializeEx(opts, t1)
|
||
|
local success1, tab1 = LibSerialize:Deserialize(serialized1)
|
||
|
assert(success1)
|
||
|
assert(tab1[1] == "test")
|
||
|
assert(tab1.x == "y")
|
||
|
assert(tab1[false][1] == 1)
|
||
|
assert(tab1[false][2] == 2)
|
||
|
assert(tab1[false][3] == 3)
|
||
|
assert(tab1[false].a == "b")
|
||
|
|
||
|
-- make a copy of the original table, but first insert a bunch of extra keys (which we'll
|
||
|
-- filter out) to force the order of the hashes to be different (tested with lua 5.1 and 5.2)
|
||
|
local t2 = {}
|
||
|
for i = 100, 10000 do
|
||
|
t2[tostring(i)] = i
|
||
|
end
|
||
|
t2.x = "y"
|
||
|
t2[1] = "test"
|
||
|
t2[false] = { 1, 2, 3, a = "b" }
|
||
|
|
||
|
-- ensure the iteration order is different
|
||
|
local isDifferent = false
|
||
|
local k1, k2 = nil, nil
|
||
|
while true do
|
||
|
k1 = next(t1, k1)
|
||
|
-- get the next key from t2 that's not going to be filtered
|
||
|
while true do
|
||
|
k2 = next(t2, k2)
|
||
|
if k2 == nil or not tonumber(k2) or tonumber(k2) < 100 then
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if k1 == nil and k2 == nil then
|
||
|
break
|
||
|
end
|
||
|
assert(k1 ~= nil and k2 ~= nil)
|
||
|
isDifferent = isDifferent or k1 ~= k2
|
||
|
end
|
||
|
assert(isDifferent)
|
||
|
|
||
|
-- serialize the copy and ensure the result is the same
|
||
|
local serialized2 = LibSerialize:SerializeEx(opts, t2)
|
||
|
assert(serialized2 == serialized1)
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[---------------------------------------------------------------------------
|
||
|
Utilities
|
||
|
--]]---------------------------------------------------------------------------
|
||
|
|
||
|
local function tCompare(lhsTable, rhsTable, depth)
|
||
|
depth = depth or 1
|
||
|
for key, value in pairs(lhsTable) do
|
||
|
if type(value) == "table" then
|
||
|
local rhsValue = rhsTable[key]
|
||
|
if type(rhsValue) ~= "table" then
|
||
|
return false
|
||
|
end
|
||
|
if depth > 1 then
|
||
|
if not tCompare(value, rhsValue, depth - 1) then
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
elseif value ~= rhsTable[key] then
|
||
|
-- print("mismatched value: " .. key .. ": " .. tostring(value) .. ", " .. tostring(rhsTable[key]))
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
-- Check for any keys that are in rhsTable and not lhsTable.
|
||
|
for key, value in pairs(rhsTable) do
|
||
|
if lhsTable[key] == nil then
|
||
|
-- print("mismatched key: " .. key)
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
|
||
|
--[[---------------------------------------------------------------------------
|
||
|
Test cases for serialization
|
||
|
--]]---------------------------------------------------------------------------
|
||
|
|
||
|
local function fail(value, desc)
|
||
|
assert(false, ("Test failed (%s): %s"):format(tostring(value), desc))
|
||
|
end
|
||
|
|
||
|
local function testfilter(t, k, v)
|
||
|
return k ~= "banned" and v ~= "banned"
|
||
|
end
|
||
|
|
||
|
local function check(value, bytelen, cmp)
|
||
|
local serialized = LibSerialize:SerializeEx({ errorOnUnserializableType = false, filter = testfilter }, value)
|
||
|
if #serialized ~= bytelen then
|
||
|
fail(value, ("Unexpected serialized length (%d, expected %d)"):format(#serialized, bytelen))
|
||
|
end
|
||
|
|
||
|
local success, deserialized = LibSerialize:Deserialize(serialized)
|
||
|
if not success then
|
||
|
fail(value, ("Deserialization failed: %s"):format(deserialized))
|
||
|
end
|
||
|
|
||
|
local typ = type(value)
|
||
|
if typ == "table" and not tCompare(cmp or value, deserialized) then
|
||
|
fail(value, "Non-matching deserialization result")
|
||
|
elseif typ ~= "table" and value ~= deserialized then
|
||
|
fail(value, ("Non-matching deserialization result: %s"):format(tostring(deserialized)))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Format: each test case is { value, bytelen, cmp }. The value will be serialized
|
||
|
-- and then deserialized, checking for success and equality, and the length of
|
||
|
-- the serialized string will be compared against bytelen. If `cmp` is provided,
|
||
|
-- it will be used for comparison against the deserialized result instead of `value`.
|
||
|
-- Note that the length always contains one extra byte for the version number.
|
||
|
local testCases = {
|
||
|
{ nil, 2 },
|
||
|
{ true, 2 },
|
||
|
{ false, 2 },
|
||
|
{ 0, 2 },
|
||
|
{ 1, 2 },
|
||
|
{ 127, 2 },
|
||
|
{ 128, 3 },
|
||
|
{ 4095, 3 },
|
||
|
{ 4096, 4 },
|
||
|
{ 65535, 4 },
|
||
|
{ 65536, 5 },
|
||
|
{ 16777215, 5 },
|
||
|
{ 16777216, 6 },
|
||
|
{ 4294967295, 6 },
|
||
|
{ 4294967296, 9 },
|
||
|
{ 9007199254740992, 9 },
|
||
|
{ 1.5, 6 },
|
||
|
{ 27.32, 8 },
|
||
|
{ 123.45678901235, 10 },
|
||
|
{ 148921291233.23, 10 },
|
||
|
{ -1, 3 },
|
||
|
{ -4095, 3 },
|
||
|
{ -4096, 4 },
|
||
|
{ -65535, 4 },
|
||
|
{ -65536, 5 },
|
||
|
{ -16777215, 5 },
|
||
|
{ -16777216, 6 },
|
||
|
{ -4294967295, 6 },
|
||
|
{ -4294967296, 9 },
|
||
|
{ -9007199254740992, 9 },
|
||
|
{ -1.5, 6 },
|
||
|
{ -123.45678901235, 10 },
|
||
|
{ -148921291233.23, 10 },
|
||
|
{ "", 2 },
|
||
|
{ "a", 3 },
|
||
|
{ "abcdefghijklmno", 17 },
|
||
|
{ "abcdefghijklmnop", 19 },
|
||
|
{ ("1234567890"):rep(30), 304 },
|
||
|
{ {}, 2 },
|
||
|
{ { 1 }, 3 },
|
||
|
{ { 1, 2, 3, 4, 5 }, 7 },
|
||
|
{ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, 17 },
|
||
|
{ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, 19 },
|
||
|
{ { 1, 2, 3, 4, a = 1, b = 2, [true] = 3, d = 4 }, 17 },
|
||
|
{ { 1, 2, 3, 4, 5, a = 1, b = 2, c = true, d = 4 }, 21 },
|
||
|
{ { 1, 2, 3, 4, 5, a = 1, b = 2, c = 3, d = 4, e = false }, 24 },
|
||
|
{ { a = 1, b = 2, c = 3 }, 11 },
|
||
|
{ { "aa", "bb", "aa", "bb" }, 14 },
|
||
|
{ { "aa1", "bb2", "aa3", "bb4" }, 18 },
|
||
|
{ { "aa1", "bb2", "aa1", "bb2" }, 14 },
|
||
|
{ { "aa1", "bb2", "bb2", "aa1" }, 14 },
|
||
|
{ { "abcdefghijklmno", "abcdefghijklmno", "abcdefghijklmno", "abcdefghijklmno" }, 24 },
|
||
|
{ { "abcdefghijklmno", "abcdefghijklmno", "abcdefghijklmno", "abcdefghijklmnop" }, 40 },
|
||
|
{ { 1, 2, 3, print, print, 6 }, 7, { 1, 2, 3, nil, nil, 6 } },
|
||
|
{ { 1, 2, 3, print, 5, 6 }, 8, { 1, 2, 3, nil, 5, 6 } },
|
||
|
{ { a = print, b = 1, c = print }, 5, { b = 1 } },
|
||
|
{ { a = print, [print] = "a" }, 2, {} },
|
||
|
{ { "banned", 1, 2, 3, banned = 4, test = "banned", a = 1 }, 9, { nil, 1, 2, 3, a = 1 } },
|
||
|
}
|
||
|
|
||
|
do
|
||
|
local t = { a = 1, b = 2 }
|
||
|
table.insert(testCases, { { t, t, t }, 13 })
|
||
|
table.insert(testCases, { { { a = 1, b = 2 }, { a = 1, b = 2 }, { a = 1, b = 2 } }, 23 })
|
||
|
end
|
||
|
|
||
|
for _, testCase in ipairs(testCases) do
|
||
|
check(unpack(testCase))
|
||
|
end
|
||
|
|
||
|
-- Since all the above tests assume serialization success, try some failures now.
|
||
|
local failCases = {
|
||
|
{ print },
|
||
|
{ [print] = true },
|
||
|
{ [true] = print },
|
||
|
print,
|
||
|
}
|
||
|
|
||
|
for _, testCase in ipairs(failCases) do
|
||
|
local success = pcall(LibSerialize.Serialize, LibSerialize, testCase)
|
||
|
assert(success == false)
|
||
|
end
|
||
|
|
||
|
print("All tests passed!")
|