serialize_spec.lua 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. _G.core = {}
  2. _G.vector = {metatable = {}}
  3. _G.setfenv = require 'busted.compatibility'.setfenv
  4. dofile("builtin/common/serialize.lua")
  5. dofile("builtin/common/vector.lua")
  6. -- Supports circular tables; does not support table keys
  7. -- Correctly checks whether a mapping of references ("same") exists
  8. -- Is significantly more efficient than assert.same
  9. local function assert_same(a, b, same)
  10. same = same or {}
  11. if same[a] or same[b] then
  12. assert(same[a] == b and same[b] == a)
  13. return
  14. end
  15. if a == b then
  16. return
  17. end
  18. if type(a) ~= "table" or type(b) ~= "table" then
  19. assert(a == b)
  20. return
  21. end
  22. same[a] = b
  23. same[b] = a
  24. local count = 0
  25. for k, v in pairs(a) do
  26. count = count + 1
  27. assert(type(k) ~= "table")
  28. assert_same(v, b[k], same)
  29. end
  30. for _ in pairs(b) do
  31. count = count - 1
  32. end
  33. assert(count == 0)
  34. end
  35. local x, y = {}, {}
  36. local t1, t2 = {x, x, y, y}, {x, y, x, y}
  37. assert.same(t1, t2) -- will succeed because it only checks whether the depths match
  38. assert(not pcall(assert_same, t1, t2)) -- will correctly fail because it checks whether the refs match
  39. describe("serialize", function()
  40. local function assert_preserves(value)
  41. local preserved_value = core.deserialize(core.serialize(value))
  42. assert_same(value, preserved_value)
  43. end
  44. it("works", function()
  45. assert_preserves({cat={sound="nyan", speed=400}, dog={sound="woof"}})
  46. end)
  47. it("handles characters", function()
  48. assert_preserves({escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"})
  49. end)
  50. it("handles NaN & infinities", function()
  51. local nan = core.deserialize(core.serialize(0/0))
  52. assert(nan ~= nan)
  53. assert_preserves(math.huge)
  54. assert_preserves(-math.huge)
  55. end)
  56. it("handles precise numbers", function()
  57. assert_preserves(0.2695949158945771)
  58. end)
  59. it("handles big integers", function()
  60. assert_preserves(269594915894577)
  61. end)
  62. it("handles recursive structures", function()
  63. local test_in = { hello = "world" }
  64. test_in.foo = test_in
  65. assert_preserves(test_in)
  66. end)
  67. it("handles cross-referencing structures", function()
  68. local test_in = {
  69. foo = {
  70. baz = {
  71. {}
  72. },
  73. },
  74. bar = {
  75. baz = {},
  76. },
  77. }
  78. test_in.foo.baz[1].foo = test_in.foo
  79. test_in.foo.baz[1].bar = test_in.bar
  80. test_in.bar.baz[1] = test_in.foo.baz[1]
  81. assert_preserves(test_in)
  82. end)
  83. it("strips functions in safe mode", function()
  84. local test_in = {
  85. func = function(a, b)
  86. error("test")
  87. end,
  88. foo = "bar"
  89. }
  90. setfenv(test_in.func, _G)
  91. local str = core.serialize(test_in)
  92. assert.not_nil(str:find("loadstring"))
  93. local test_out = core.deserialize(str, true)
  94. assert.is_nil(test_out.func)
  95. assert.equals(test_out.foo, "bar")
  96. end)
  97. it("vectors work", function()
  98. local v = vector.new(1, 2, 3)
  99. assert_preserves({v})
  100. assert_preserves(v)
  101. -- abuse
  102. v = vector.new(1, 2, 3)
  103. v.a = "bla"
  104. assert_preserves(v)
  105. end)
  106. it("handles keywords as keys", function()
  107. assert_preserves({["and"] = "keyword", ["for"] = "keyword"})
  108. end)
  109. describe("fuzzing", function()
  110. local atomics = {true, false, math.huge, -math.huge} -- no NaN or nil
  111. local function atomic()
  112. return atomics[math.random(1, #atomics)]
  113. end
  114. local function num()
  115. local sign = math.random() < 0.5 and -1 or 1
  116. local val = math.random(0, 2^52)
  117. local exp = math.random() < 0.5 and 1 or 2^(math.random(-120, 120))
  118. return sign * val * exp
  119. end
  120. local function charcodes(count)
  121. if count == 0 then return end
  122. return math.random(0, 0xFF), charcodes(count - 1)
  123. end
  124. local function str()
  125. return string.char(charcodes(math.random(0, 100)))
  126. end
  127. local primitives = {atomic, num, str}
  128. local function primitive()
  129. return primitives[math.random(1, #primitives)]()
  130. end
  131. local function tab(max_actions)
  132. local root = {}
  133. local tables = {root}
  134. local function random_table()
  135. return tables[#tables == 1 and 1 or math.random(1, #tables)] -- luacheck: ignore
  136. end
  137. for _ = 1, math.random(1, max_actions) do
  138. local tab = random_table()
  139. local value
  140. if math.random() < 0.5 then
  141. if math.random() < 0.5 then
  142. value = random_table()
  143. else
  144. value = {}
  145. table.insert(tables, value)
  146. end
  147. else
  148. value = primitive()
  149. end
  150. tab[math.random() < 0.5 and (#tab + 1) or primitive()] = value
  151. end
  152. return root
  153. end
  154. it("primitives work", function()
  155. for _ = 1, 1e3 do
  156. assert_preserves(primitive())
  157. end
  158. end)
  159. it("tables work", function()
  160. for _ = 1, 100 do
  161. local fuzzed_table = tab(1e3)
  162. assert_same(fuzzed_table, table.copy(fuzzed_table))
  163. assert_preserves(fuzzed_table)
  164. end
  165. end)
  166. end)
  167. end)