123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- _G.core = {}
- _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()
- assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}})
- end)
- it("handles characters", function()
- 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()
- assert_preserves(0.2695949158945771)
- end)
- it("handles big integers", function()
- 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 = {},
- },
- }
- 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()
- local test_in = {
- func = function(a, b)
- error("test")
- end,
- foo = "bar"
- }
- setfenv(test_in.func, _G)
- local str = core.serialize(test_in)
- assert.not_nil(str:find("loadstring"))
- local test_out = core.deserialize(str, true)
- assert.is_nil(test_out.func)
- assert.equals(test_out.foo, "bar")
- end)
- it("vectors work", function()
- local v = vector.new(1, 2, 3)
- assert_preserves({v})
- assert_preserves(v)
- -- abuse
- v = vector.new(1, 2, 3)
- v.a = "bla"
- 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
- -- HACK math.random(a, b) requires a, b & b - a to fit within a 32-bit int
- -- Use two random calls to generate a random number from 0 - 2^50 as lower & upper 25 bits
- local val = math.random(0, 2^25) * 2^25 + math.random(0, 2^25 - 1)
- 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[math.random(1, #tables)]
- 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)
|