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)
9 local buff = tostring(t)
10 if type(t) == 'table' then
12 for k, v in pairs(t) do
13 buff = buff..("%s = %s, "):format(k,v)
22 local function test(description, fn)
23 test_number = test_number + 1
24 description = (bright.."% 3d. "..reset.."%s"):format(test_number, description)
27 local ok, err = pcall(fn)
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
36 io.write(reset..dim.."\r.......................................")
37 io.write(reset.."["..green.."PASSED"..reset.."]\r")
38 io.write(description.."\n")
42 test("Loading module", function()
43 package.cpath = "./immutable.so;"..package.cpath
44 immutable = require"immutable"
47 test("Creating class", function()
48 Vec = immutable({"x","y"}, {
51 return self.x*self.x + self.y*self.y
53 __add=function(self, other)
54 local cls = getmetatable(self)
55 return cls(self.x+other.x, self.y+other.y)
60 test("Instantiating class", function()
64 test("Testing indexing", function()
68 test("Testing # operator", function()
71 assert(#T(1,2,3) == 3)
74 test("Testing # operator for mixed table", function()
75 assert(#Vec(1,2,3,4,5) == 3)
78 test("Testing method", function()
79 assert(v:len2() == 10)
82 test("Testing tostring", function()
83 assert(tostring(v) == "Vec(1, 3)")
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!")
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}))
99 test("Testing equality", function()
102 assert(tostring(v) == tostring(also_v))
103 -- Hash collision in the current implementation
106 assert(tostring(not_v) == "Vec(true, 3)")
107 also_not_v = Vec(true,3)
108 assert(not_v == also_not_v)
111 test("Testing singletons", function()
112 local T1 = immutable()
113 local T2 = immutable()
118 test("Testing __add metamethod", function()
119 assert(v + Vec(5,6) == Vec(6,9))
122 test("Testing string members", function()
123 Vec("hello", "world")
125 test("Testing table members", function()
128 test("Testing function members", function()
129 Vec(function() end, function() end)
131 test("Testing immutable members", function()
137 test("Testing garbage collection", function()
138 local Foo = immutable({"x"})
139 local function countFoos()
144 for h,bucket in pairs(Foo.__instances) do
145 buckets = buckets + 1
146 for _ in pairs(bucket) do
154 local f1, f2, also_f2 = Foo(1), Foo(2), Foo(2)
155 local foos, buckets = countFoos()
156 assert(foos == 2, "WTF? "..tostring(foos))
158 foos, buckets = countFoos()
161 foos, buckets = countFoos()
162 assert(foos == 0, "Leaking instances")
163 assert(buckets == 0, "Leaking hash buckets")
164 assert(next(Foo.__instances) == nil)
167 test("Testing __index", function()
168 local Baz = immutable({"x","y"}, {z=99})
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})
173 assert(f.x == "foo" and f.y == "foo" and f.z == "foo" and f.asdf == "foo")
176 test("Testing __rawindex", function()
177 local Foo = immutable({"x","y"}, {
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'))
186 local f = Foo(23, nil)
187 assert(f.x == 23 and f.y == nil and f.classvar == 99 and f.asdf == "asdf23")
190 test("Testing similar class", function()
191 FooVec = immutable({"x","y"}, {
193 return self.x*self.x + self.y*self.y
195 __add=function(self, other)
196 local cls = getmetatable(self)
197 return cls(self.x+other.x, self.y+other.y)
199 __tostring=function(self)
200 return "Vec("..tostring(self.x)..", "..tostring(self.y)..")"
203 assert(FooVec(1,1) ~= Vec(1,1))
206 test("Testing is_instance", function()
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))
215 test("Testing spoofing", function()
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))
223 if _VERSION == "Lua 5.3" then
224 test("Testing unpacking", function()
225 local Tuple = immutable()
227 local a, b = table.unpack(Tuple(5,6))
228 assert(a == 5 and b == 6)
233 test("Testing immutable(nil)", function()
234 local Tup3 = immutable(nil, {name="Tuple"})
235 assert(tostring(Tup3(1,2,3)) == "Tuple(1, 2, 3)")
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.*"))
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)")
251 test("Testing giant immutable table", function()
254 for i=1,N do keys[i] = "key_"..tostring(i) end
255 local T = immutable(keys)
257 for i,key in ipairs(keys) do values[key] = i*i end
258 pcall(function() T:from_table(values) 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)
270 iterations = iterations + 1
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)
277 iterations = iterations + 1
279 assert(passed == 6 and iterations == passed)
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)
291 iterations = iterations + 1
293 assert(passed == 4 and iterations == passed)
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)
304 iterations = iterations + 1
306 assert(passed == 2 and iterations == passed, "ipairs failed")
307 checks = {skip='a','b','c'}
309 for k,v in pairs(t) do
310 assert(checks[k] == v, "checks["..k.."] ~= "..v)
313 assert(passed == 3, "pairs failed")
317 test("Testing __new", function()
318 local Foo = immutable({"x","y"}, {
319 __new=function(cls,x,y)
324 assert(f.x == 3 and f.y == 4)
327 test("Testing __new with #args", function()
328 local Foo = immutable({"x","y"}, {
329 __new=function(cls,x,y,z)
334 assert(f.x == 3 and f.y == 4)
337 test("Testing __new with varargs", function()
338 local Foo = immutable(nil, {
339 __new=function(cls,...)
344 assert(f[1] == "prefix" and f[2] == 1 and f[3] == 2 and f[4] == nil)
347 test("Testing __hash", function()
348 local T = immutable()
351 assert(type(h) == 'number')
352 assert(T.__instances[h][t])
355 test("Testing __rawindex", function()
356 local T = immutable({"x"}, {
358 __index = function(self, key)
359 local cls = getmetatable(self)
360 local value, found = cls.__super.__index(self, key)
364 return tostring(value)
368 assert(T(3).x == "3" and T(nil).x == "nil" and T(1).classvar == 99)
371 if num_errors == 0 then
372 print(green.."All tests passed!"..reset)
374 print(bright..red.."*** "..tostring(num_errors).." test"..(num_errors > 1 and "s" or "").." failed! ***"..reset)