code / lua-immutable

Lines1.0K C473 Lua341 Markdown173 make28
(377 lines)
1 local bright = string.char(27).."[1m"
2 local dim = string.char(27).."[2m"
3 local underscore = string.char(27).."[4m"
4 local red = string.char(27).."[31m"
5 local green = string.char(27).."[32m"
6 local reset = string.char(27).."[0m"
7 print(bright..underscore.."\nTesting with ".._VERSION..":"..reset)
8 repr = function(t)
9 local buff = tostring(t)
10 if type(t) == 'table' then
11 buff = "{"..buff.." "
12 for k, v in pairs(t) do
13 buff = buff..("%s = %s, "):format(k,v)
14 end
15 buff = buff.."}"
16 end
17 return buff
18 end
20 local num_errors = 0
21 local test_number = 0
22 local function test(description, fn)
23 test_number = test_number + 1
24 description = (bright.."% 3d. "..reset.."%s"):format(test_number, description)
25 io.write(description)
26 io.write(red)
27 local ok, err = pcall(fn)
28 if not ok then
29 io.write(reset..dim.."\r.......................................")
30 io.write(reset.."["..bright..red.."FAILED"..reset.."]\r")
31 io.write("\r"..description.."\n"..reset)
32 print(reset..red..(err or "")..reset)
33 num_errors = num_errors + 1
34 --os.exit(true)
35 else
36 io.write(reset..dim.."\r.......................................")
37 io.write(reset.."["..green.."PASSED"..reset.."]\r")
38 io.write(description.."\n")
39 end
40 end
42 test("Loading module", function()
43 package.cpath = "./immutable.so;"..package.cpath
44 immutable = require"immutable"
45 end)
47 test("Creating class", function()
48 Vec = immutable({"x","y"}, {
49 name="Vec",
50 len2=function(self)
51 return self.x*self.x + self.y*self.y
52 end,
53 __add=function(self, other)
54 local cls = getmetatable(self)
55 return cls(self.x+other.x, self.y+other.y)
56 end,
57 })
58 end)
60 test("Instantiating class", function()
61 v = assert(Vec(1,3))
62 end)
64 test("Testing indexing", function()
65 assert(v.x == 1)
66 end)
68 test("Testing # operator", function()
69 assert(#v == 0)
70 local T = immutable()
71 assert(#T(1,2,3) == 3)
72 end)
74 test("Testing # operator for mixed table", function()
75 assert(#Vec(1,2,3,4,5) == 3)
76 end)
78 test("Testing method", function()
79 assert(v:len2() == 10)
80 end)
82 test("Testing tostring", function()
83 assert(tostring(v) == "Vec(1, 3)")
84 end)
86 test("Testing from_table", function()
87 assert(Vec:from_table({x=1,y=2}) == Vec(1,2), "assertion 1 failed!")
88 assert(Vec:from_table({3, x=1,y=2}) == Vec(1,2,3), "assertion 2 failed!")
89 assert(Vec:from_table(setmetatable({y=3}, {__index={x=1}})) == v, "assertion 3 failed!")
90 assert(Vec:from_table({x=1}) == Vec(1, nil), "assertion 4 failed!")
91 end)
93 test("Testing from_table for tuples", function()
94 local T = immutable(nil)
95 assert(T(1,2) == T:from_table({1,2,n=2}))
96 assert(T(1,2) == T:from_table({1,2}))
97 end)
99 test("Testing equality", function()
100 also_v = Vec(1,3)
101 assert(v == also_v)
102 assert(tostring(v) == tostring(also_v))
103 -- Hash collision in the current implementation
104 not_v = Vec(true,3)
105 assert(v ~= not_v)
106 assert(tostring(not_v) == "Vec(true, 3)")
107 also_not_v = Vec(true,3)
108 assert(not_v == also_not_v)
109 end)
111 test("Testing singletons", function()
112 local T1 = immutable()
113 local T2 = immutable()
114 assert(T1() == T1())
115 assert(T1() ~= T2())
116 end)
118 test("Testing __add metamethod", function()
119 assert(v + Vec(5,6) == Vec(6,9))
120 end)
121 collectgarbage()
122 test("Testing string members", function()
123 Vec("hello", "world")
124 end)
125 test("Testing table members", function()
126 Vec({}, {})
127 end)
128 test("Testing function members", function()
129 Vec(function() end, function() end)
130 end)
131 test("Testing immutable members", function()
132 Vec(v, v)
133 end)
134 collectgarbage()
135 collectgarbage()
137 test("Testing garbage collection", function()
138 local Foo = immutable({"x"})
139 local function countFoos()
140 collectgarbage()
141 collectgarbage()
142 local n = 0
143 local buckets = 0
144 for h,bucket in pairs(Foo.__instances) do
145 buckets = buckets + 1
146 for _ in pairs(bucket) do
147 n = n + 1
148 end
149 end
150 collectgarbage()
151 collectgarbage()
152 return n, buckets
153 end
154 local f1, f2, also_f2 = Foo(1), Foo(2), Foo(2)
155 local foos, buckets = countFoos()
156 assert(foos == 2, "WTF? "..tostring(foos))
157 f1, f2 = nil, nil
158 foos, buckets = countFoos()
159 assert(foos == 1)
160 also_f2 = nil
161 foos, buckets = countFoos()
162 assert(foos == 0, "Leaking instances")
163 assert(buckets == 0, "Leaking hash buckets")
164 assert(next(Foo.__instances) == nil)
165 end)
167 test("Testing __index", function()
168 local Baz = immutable({"x","y"}, {z=99})
169 local b = Baz(1,nil)
170 assert(b.x == 1 and b.y == nil and b.z == 99 and b.asdf == nil)
171 local Foo = immutable({"x","y"}, {z=99, __index=function() return "foo" end})
172 local f = Foo(1,nil)
173 assert(f.x == "foo" and f.y == "foo" and f.z == "foo" and f.asdf == "foo")
174 end)
176 test("Testing __rawindex", function()
177 local Foo = immutable({"x","y"}, {
178 classvar = 99,
179 __index=function(self,k)
180 local cls = getmetatable(self)
181 local inst_val, found = cls.__super.__index(self, k)
182 if found then return inst_val end
183 return cls[k] or tostring(k)..tostring(cls.__super.__index(self, 'x'))
184 end,
186 local f = Foo(23, nil)
187 assert(f.x == 23 and f.y == nil and f.classvar == 99 and f.asdf == "asdf23")
188 end)
190 test("Testing similar class", function()
191 FooVec = immutable({"x","y"}, {
192 len2=function(self)
193 return self.x*self.x + self.y*self.y
194 end,
195 __add=function(self, other)
196 local cls = getmetatable(self)
197 return cls(self.x+other.x, self.y+other.y)
198 end,
199 __tostring=function(self)
200 return "Vec("..tostring(self.x)..", "..tostring(self.y)..")"
201 end,
203 assert(FooVec(1,1) ~= Vec(1,1))
204 end)
206 test("Testing is_instance", function()
207 fv = FooVec(1,2)
208 assert(FooVec:is_instance(fv))
209 assert(not FooVec:is_instance(v))
210 assert(not FooVec:is_instance("asdf"))
211 assert(not FooVec:is_instance({}))
212 assert(not FooVec:is_instance(5))
213 end)
215 test("Testing spoofing", function()
216 local t = {99,100}
217 setmetatable(t, Vec)
218 assert(not pcall(function() return t.x end))
219 assert(not pcall(function() return Vec.__index(88, 9) end))
220 assert(not pcall(function() return Vec.__tostring(t) end))
221 end)
223 if _VERSION == "Lua 5.3" then
224 test("Testing unpacking", function()
225 local Tuple = immutable()
226 if table.unpack then
227 local a, b = table.unpack(Tuple(5,6))
228 assert(a == 5 and b == 6)
229 end
230 end)
231 end
233 test("Testing immutable(nil)", function()
234 local Tup3 = immutable(nil, {name="Tuple"})
235 assert(tostring(Tup3(1,2,3)) == "Tuple(1, 2, 3)")
236 end)
238 test("Testing tostring(class)", function()
239 local C1 = immutable(nil, {name="MYNAME"})
240 assert(tostring(C1) == "MYNAME")
241 local C2 = immutable()
242 assert(tostring(C2):match("immutable type: 0x.*"))
243 end)
245 test("Testing tuple tostring", function()
246 local tup3 = immutable(nil)
247 assert(tostring(tup3(1,2,3)) == "(1, 2, 3)")
248 assert(tostring(tup3(1,tup3(2,3,4),5)) == "(1, (2, 3, 4), 5)")
249 end)
251 test("Testing giant immutable table", function()
252 local keys = {}
253 local N = 100000
254 for i=1,N do keys[i] = "key_"..tostring(i) end
255 local T = immutable(keys)
256 local values = {}
257 for i,key in ipairs(keys) do values[key] = i*i end
258 pcall(function() T:from_table(values) end)
259 end)
261 if _VERSION == "Lua 5.2" or _VERSION == "Lua 5.3" then
262 test("Testing tuple iteration", function()
263 local T = immutable()
264 local t = T(1,4,9,nil,16,nil)
265 local checks = {1,4,9,nil,16,nil}
266 local passed, iterations = 0, 0
267 for i,v in ipairs(t) do
268 assert(checks[i] == v)
269 passed = passed + 1
270 iterations = iterations + 1
271 end
272 assert(passed == 6 and iterations == passed)
273 passed, iterations = 0, 0
274 for k,v in pairs(t) do
275 assert(checks[k] == v)
276 passed = passed + 1
277 iterations = iterations + 1
278 end
279 assert(passed == 6 and iterations == passed)
280 end)
282 test("Testing table iteration", function()
283 local Foo = immutable({"x", "y", "z","w"})
284 local f = Foo(1,nil,2,nil)
285 local checks = {1,nil,2,nil}
286 local passed, iterations = 0, 0
287 checks = {x=1,y=nil,z=2,w=nil}
288 for k,v in pairs(f) do
289 assert(checks[k] == v)
290 passed = passed + 1
291 iterations = iterations + 1
292 end
293 assert(passed == 4 and iterations == passed)
294 end)
296 test("Testing mixed iteration", function()
297 local T = immutable({"skip"})
298 local t = T('a','b','c')
299 local checks = {'b','c'}
300 local passed, iterations = 0, 0
301 for i,v in ipairs(t) do
302 assert(checks[i] == v, "checks["..i.."] ~= "..v)
303 passed = passed + 1
304 iterations = iterations + 1
305 end
306 assert(passed == 2 and iterations == passed, "ipairs failed")
307 checks = {skip='a','b','c'}
308 passed = 0
309 for k,v in pairs(t) do
310 assert(checks[k] == v, "checks["..k.."] ~= "..v)
311 passed = passed + 1
312 end
313 assert(passed == 3, "pairs failed")
314 end)
315 end
317 test("Testing __new", function()
318 local Foo = immutable({"x","y"}, {
319 __new=function(cls,x,y)
320 return x+1,y+1
321 end
323 local f = Foo(2,3)
324 assert(f.x == 3 and f.y == 4)
325 end)
327 test("Testing __new with #args", function()
328 local Foo = immutable({"x","y"}, {
329 __new=function(cls,x,y,z)
330 return x+z,y+z
331 end
333 local f = Foo(2,3,1)
334 assert(f.x == 3 and f.y == 4)
335 end)
337 test("Testing __new with varargs", function()
338 local Foo = immutable(nil, {
339 __new=function(cls,...)
340 return "prefix",...
341 end
343 local f = Foo(1,2)
344 assert(f[1] == "prefix" and f[2] == 1 and f[3] == 2 and f[4] == nil)
345 end)
347 test("Testing __hash", function()
348 local T = immutable()
349 local t = T(1,2,3)
350 local h = t:__hash()
351 assert(type(h) == 'number')
352 assert(T.__instances[h][t])
353 end)
355 test("Testing __rawindex", function()
356 local T = immutable({"x"}, {
357 classvar = 99,
358 __index = function(self, key)
359 local cls = getmetatable(self)
360 local value, found = cls.__super.__index(self, key)
361 if not found then
362 return cls[key]
363 else
364 return tostring(value)
365 end
366 end
368 assert(T(3).x == "3" and T(nil).x == "nil" and T(1).classvar == 99)
369 end)
371 if num_errors == 0 then
372 print(green.."All tests passed!"..reset)
373 else
374 print(bright..red.."*** "..tostring(num_errors).." test"..(num_errors > 1 and "s" or "").." failed! ***"..reset)
375 io.write(reset)
376 error()
377 end