aboutsummaryrefslogtreecommitdiff
path: root/builtin/common/tests
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/common/tests')
-rw-r--r--builtin/common/tests/serialize_spec.lua155
1 files changed, 136 insertions, 19 deletions
diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua
index 69b2b567c..ea79680d7 100644
--- a/builtin/common/tests/serialize_spec.lua
+++ b/builtin/common/tests/serialize_spec.lua
@@ -6,38 +6,92 @@ _G.setfenv = require 'busted.compatibility'.setfenv
dofile("builtin/common/serialize.lua")
dofile("builtin/common/vector.lua")
+-- Supports circular tables; does not support table keys
+-- Correctly checks whether a mapping of references ("same") exists
+-- Is significantly more efficient than assert.same
+local function assert_same(a, b, same)
+ same = same or {}
+ if same[a] or same[b] then
+ assert(same[a] == b and same[b] == a)
+ return
+ end
+ if a == b then
+ return
+ end
+ if type(a) ~= "table" or type(b) ~= "table" then
+ assert(a == b)
+ return
+ end
+ same[a] = b
+ same[b] = a
+ local count = 0
+ for k, v in pairs(a) do
+ count = count + 1
+ assert(type(k) ~= "table")
+ assert_same(v, b[k], same)
+ end
+ for _ in pairs(b) do
+ count = count - 1
+ end
+ assert(count == 0)
+end
+
+local x, y = {}, {}
+local t1, t2 = {x, x, y, y}, {x, y, x, y}
+assert.same(t1, t2) -- will succeed because it only checks whether the depths match
+assert(not pcall(assert_same, t1, t2)) -- will correctly fail because it checks whether the refs match
+
describe("serialize", function()
+ local function assert_preserves(value)
+ local preserved_value = core.deserialize(core.serialize(value))
+ assert_same(value, preserved_value)
+ end
it("works", function()
- local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
- local test_out = core.deserialize(core.serialize(test_in))
-
- assert.same(test_in, test_out)
+ assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}})
end)
it("handles characters", function()
- local test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
- local test_out = core.deserialize(core.serialize(test_in))
- assert.same(test_in, test_out)
+ assert_preserves({escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"})
+ end)
+
+ it("handles NaN & infinities", function()
+ local nan = core.deserialize(core.serialize(0/0))
+ assert(nan ~= nan)
+ assert_preserves(math.huge)
+ assert_preserves(-math.huge)
end)
it("handles precise numbers", function()
- local test_in = 0.2695949158945771
- local test_out = core.deserialize(core.serialize(test_in))
- assert.same(test_in, test_out)
+ assert_preserves(0.2695949158945771)
end)
it("handles big integers", function()
- local test_in = 269594915894577
- local test_out = core.deserialize(core.serialize(test_in))
- assert.same(test_in, test_out)
+ assert_preserves(269594915894577)
end)
it("handles recursive structures", function()
local test_in = { hello = "world" }
test_in.foo = test_in
+ assert_preserves(test_in)
+ end)
+
+ it("handles cross-referencing structures", function()
+ local test_in = {
+ foo = {
+ baz = {
+ {}
+ },
+ },
+ bar = {
+ baz = {},
+ },
+ }
- local test_out = core.deserialize(core.serialize(test_in))
- assert.same(test_in, test_out)
+ test_in.foo.baz[1].foo = test_in.foo
+ test_in.foo.baz[1].bar = test_in.bar
+ test_in.bar.baz[1] = test_in.foo.baz[1]
+
+ assert_preserves(test_in)
end)
it("strips functions in safe mode", function()
@@ -47,6 +101,7 @@ describe("serialize", function()
end,
foo = "bar"
}
+ setfenv(test_in.func, _G)
local str = core.serialize(test_in)
assert.not_nil(str:find("loadstring"))
@@ -58,13 +113,75 @@ describe("serialize", function()
it("vectors work", function()
local v = vector.new(1, 2, 3)
- assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
- assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
+ assert_preserves({v})
+ assert_preserves(v)
-- abuse
v = vector.new(1, 2, 3)
v.a = "bla"
- assert.same({x = 1, y = 2, z = 3, a = "bla"},
- core.deserialize(core.serialize(v)))
+ assert_preserves(v)
+ end)
+
+ it("handles keywords as keys", function()
+ assert_preserves({["and"] = "keyword", ["for"] = "keyword"})
+ end)
+
+ describe("fuzzing", function()
+ local atomics = {true, false, math.huge, -math.huge} -- no NaN or nil
+ local function atomic()
+ return atomics[math.random(1, #atomics)]
+ end
+ local function num()
+ local sign = math.random() < 0.5 and -1 or 1
+ local val = math.random(0, 2^52)
+ local exp = math.random() < 0.5 and 1 or 2^(math.random(-120, 120))
+ return sign * val * exp
+ end
+ local function charcodes(count)
+ if count == 0 then return end
+ return math.random(0, 0xFF), charcodes(count - 1)
+ end
+ local function str()
+ return string.char(charcodes(math.random(0, 100)))
+ end
+ local primitives = {atomic, num, str}
+ local function primitive()
+ return primitives[math.random(1, #primitives)]()
+ end
+ local function tab(max_actions)
+ local root = {}
+ local tables = {root}
+ local function random_table()
+ return tables[#tables == 1 and 1 or math.random(1, #tables)] -- luacheck: ignore
+ end
+ for _ = 1, math.random(1, max_actions) do
+ local tab = random_table()
+ local value
+ if math.random() < 0.5 then
+ if math.random() < 0.5 then
+ value = random_table()
+ else
+ value = {}
+ table.insert(tables, value)
+ end
+ else
+ value = primitive()
+ end
+ tab[math.random() < 0.5 and (#tab + 1) or primitive()] = value
+ end
+ return root
+ end
+ it("primitives work", function()
+ for _ = 1, 1e3 do
+ assert_preserves(primitive())
+ end
+ end)
+ it("tables work", function()
+ for _ = 1, 100 do
+ local fuzzed_table = tab(1e3)
+ assert_same(fuzzed_table, table.copy(fuzzed_table))
+ assert_preserves(fuzzed_table)
+ end
+ end)
end)
end)