serialize_spec.lua 5.3 KB

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