-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlunity.lua
383 lines (342 loc) · 10 KB
/
lunity.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
--[=========================================================================[
Lunity v0.12 by Gavin Kistner
See http://github.com/Phrogz/Lunity for usage documentation.
Licensed under Creative Commons Attribution 3.0 United States License.
See http://creativecommons.org/licenses/by/3.0/us/ for details.
--]=========================================================================]
-- Cache these so we can silence the real ones during a run
local print,write = print,io.write
-- FIXME: this will fail if two test suites are running interleaved
local assertsPassed, assertsAttempted
local function assertionSucceeded()
assertsPassed = assertsPassed + 1
write('.')
return true
end
-- This is the table that will be used as the environment for the tests,
-- making assertions available within the file.
local lunity = setmetatable({}, {__index=_G})
function lunity.fail(msg)
assertsAttempted = assertsAttempted + 1
if not msg then msg = "(test failure)" end
error(msg, 2)
end
function lunity.assert(testCondition, msg)
assertsAttempted = assertsAttempted + 1
if not testCondition then
if not msg then msg = "assert() failed: value was "..tostring(testCondition) end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertEqual(actual, expected, msg)
assertsAttempted = assertsAttempted + 1
if actual~=expected then
if not msg then
msg = string.format("assertEqual() failed: expected %s, was %s",
tostring(expected),
tostring(actual)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertType(actual, expectedType, msg)
assertsAttempted = assertsAttempted + 1
if type(actual) ~= expectedType then
if not msg then
msg = string.format("assertType() failed: value %s is a %s, expected to be a %s",
tostring(actual),
type(actual),
expectedType
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertTableEquals(actual, expected, msg, keyPath)
assertsAttempted = assertsAttempted + 1
-- Easy out
if actual == expected then
if not keyPath then
return assertionSucceeded()
else
return true
end
end
if not keyPath then keyPath = {} end
if type(actual) ~= 'table' then
if not msg then
msg = "Value passed to assertTableEquals() was not a table."
end
error(msg, 2 + #keyPath)
end
-- Ensure all keys in t1 match in t2
for key,expectedValue in pairs(expected) do
keyPath[#keyPath+1] = tostring(key)
local actualValue = actual[key]
if type(expectedValue)=='table' then
if type(actualValue)~='table' then
if not msg then
msg = "Tables not equal; expected "..table.concat(keyPath,'.').." to be a table, but was a "..type(actualValue)
end
error(msg, 1 + #keyPath)
elseif expectedValue ~= actualValue then
lunity.assertTableEquals(actualValue, expectedValue, msg, keyPath)
end
else
if actualValue ~= expectedValue then
if not msg then
if actualValue == nil then
msg = "Tables not equal; missing key '"..table.concat(keyPath,'.').."'."
else
msg = "Tables not equal; expected '"..table.concat(keyPath,'.').."' to be "..tostring(expectedValue)..", but was "..tostring(actualValue)
end
end
error(msg, 1 + #keyPath)
end
end
keyPath[#keyPath] = nil
end
-- Ensure actual doesn't have keys that aren't expected
for k,_ in pairs(actual) do
if expected[k] == nil then
if not msg then
msg = "Tables not equal; found unexpected key '"..table.concat(keyPath,'.').."."..tostring(k).."'"
end
error(msg, 2 + #keyPath)
end
end
return assertionSucceeded()
end
function lunity.assertNotEqual(actual, expected, msg)
assertsAttempted = assertsAttempted + 1
if actual==expected then
if not msg then
msg = string.format("assertNotEqual() failed: value not allowed to be %s",
tostring(actual)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertTrue(actual, msg)
assertsAttempted = assertsAttempted + 1
if actual ~= true then
if not msg then
msg = string.format("assertTrue() failed: value was %s, expected true",
tostring(actual)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertFalse(actual, msg)
assertsAttempted = assertsAttempted + 1
if actual ~= false then
if not msg then
msg = string.format("assertFalse() failed: value was %s, expected false",
tostring(actual)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertNil(actual, msg)
assertsAttempted = assertsAttempted + 1
if actual ~= nil then
if not msg then
msg = string.format("assertNil() failed: value was %s, expected nil",
tostring(actual)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertNotNil(actual, msg)
assertsAttempted = assertsAttempted + 1
if actual == nil then
if not msg then msg = "assertNotNil() failed: value was nil" end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertTableEmpty(actual, msg)
assertsAttempted = assertsAttempted + 1
if type(actual) ~= "table" then
msg = string.format("assertTableEmpty() failed: expected a table, but got a %s",
type(actual)
)
error(msg, 2)
else
local key, value = next(actual)
if key ~= nil then
if not msg then
msg = string.format("assertTableEmpty() failed: table has non-nil key %s=%s",
tostring(key),
tostring(value)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
end
function lunity.assertTableNotEmpty(actual, msg)
assertsAttempted = assertsAttempted + 1
if type(actual) ~= "table" then
msg = string.format("assertTableNotEmpty() failed: expected a table, but got a %s",
type(actual)
)
error(msg, 2)
else
if next(actual) == nil then
if not msg then
msg = "assertTableNotEmpty() failed: table has no keys"
end
error(msg, 2)
end
return assertionSucceeded()
end
end
function lunity.assertSameKeys(t1, t2, msg)
assertsAttempted = assertsAttempted + 1
local function bail(k,x,y)
if not msg then msg = string.format("Table #%d has key '%s' not present in table #%d",x,tostring(k),y) end
error(msg, 3)
end
for k,_ in pairs(t1) do if t2[k]==nil then bail(k,1,2) end end
for k,_ in pairs(t2) do if t1[k]==nil then bail(k,2,1) end end
return assertionSucceeded()
end
-- Ensures that the value is a function OR may be called as one
function lunity.assertInvokable(value, msg)
assertsAttempted = assertsAttempted + 1
local meta = getmetatable(value)
if (type(value) ~= 'function') and not (meta and meta.__call and (type(meta.__call)=='function')) then
if not msg then
msg = string.format("assertInvokable() failed: '%s' can not be called as a function",
tostring(value)
)
end
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertErrors(invokable, ...)
lunity.assertInvokable(invokable)
if pcall(invokable,...) then
local msg = string.format("assertErrors() failed: %s did not raise an error",
tostring(invokable)
)
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.assertDoesNotError(invokable, ...)
lunity.assertInvokable(invokable)
if not pcall(invokable,...) then
local msg = string.format("assertDoesNotError() failed: %s raised an error",
tostring(invokable)
)
error(msg, 2)
end
return assertionSucceeded()
end
function lunity.is_nil(value) return type(value)=='nil' end
function lunity.is_boolean(value) return type(value)=='boolean' end
function lunity.is_number(value) return type(value)=='number' end
function lunity.is_string(value) return type(value)=='string' end
function lunity.is_table(value) return type(value)=='table' end
function lunity.is_function(value) return type(value)=='function' end
function lunity.is_thread(value) return type(value)=='thread' end
function lunity.is_userdata(value) return type(value)=='userdata' end
local function run(self, opts)
if not opts then opts = {} end
if opts.quiet then
_G.print = function() end
io.write = function() end
end
assertsPassed = 0
assertsAttempted = 0
local useANSI,useHTML = true, false
if opts.useHTML ~= nil then useHTML=opts.useHTML end
if not useHTML and opts.useANSI ~= nil then useANSI=opts.useANSI end
local suiteName = getmetatable(self).name
if useHTML then
print("<h2 style='background:#000; color:#fff; margin:1em 0 0 0; padding:0.1em 0.4em; font-size:120%'>"..suiteName.."</h2><pre style='margin:0; padding:0.2em 1em; background:#ffe; border:1px solid #eed; overflow:auto'>")
else
print(string.rep('=',78))
print(suiteName)
print(string.rep('=',78))
end
io.stdout:flush()
local testnames = {}
for name, test in pairs(self) do
if type(test)=='function' and name~='before' and name~='after' then
testnames[#testnames+1]=name
end
end
table.sort(testnames)
local startTime = os.clock()
local passed = 0
for _,name in ipairs(testnames) do
local scratchpad = {}
write(name..": ")
if self.before then self.before(scratchpad) end
local successFlag, errorMessage = pcall(self[name], scratchpad)
if successFlag then
print("pass")
passed = passed + 1
else
if useANSI then
print("\27[31m\27[1mFAIL!\27[0m")
print("\27[31m"..errorMessage.."\27[0m")
elseif useHTML then
print("<b style='color:red'>FAIL!</b>")
print("<span style='color:red'>"..errorMessage.."</span>")
else
print("FAIL!")
print(errorMessage)
end
end
io.stdout:flush()
if self.after then self.after(scratchpad) end
end
local elapsed = os.clock() - startTime
if useHTML then
print("</pre>")
else
print(string.rep('-', 78))
end
print(string.format("%d/%d tests passed (%0.1f%%)",
passed,
#testnames,
100 * passed / #testnames
))
if useHTML then print("<br>") end
print(string.format("%d total successful assertion%s in ~%.0fms (%.0f assertions/second)",
assertsPassed,
assertsPassed == 1 and "" or "s",
elapsed*1000,
assertsAttempted / elapsed
))
if not useHTML then print("") end
io.stdout:flush()
if opts.quiet then
_G.print = print
io.write = write
end
end
return function(name)
return setmetatable(
{test=setmetatable({}, {__call=run, name=name or '(test suite)'})},
{__index=lunity}
)
end