-
Notifications
You must be signed in to change notification settings - Fork 2
/
u-test.lua
311 lines (266 loc) · 8.58 KB
/
u-test.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
-- COMMAND LINE ----------------------------------------------------------------
local function print_help()
print "Usage: target.lua [options] [regex]"
print "\t-h show this message"
print "\t-q quiet mode"
print "\t-s disable colours"
end
local quiet, grey, test_regex
if arg then
for i = 1, #arg do
if arg[i] == "-h" then
print_help()
os.exit(0)
elseif arg[i] == "-q" then
quiet = true
elseif arg[i] == "-s" then
grey = true
elseif not test_regex then
test_regex = arg[i]
else
print_help()
os.exit(1)
end
end
end
-- UTILS -----------------------------------------------------------------------
local function red(str) return grey and str or "\27[1;31m" .. str .. "\27[0m" end
local function blue(str) return grey and str or "\27[1;34m" .. str .. "\27[0m" end
local function green(str) return grey and str or "\27[1;32m" .. str .. "\27[0m" end
local function yellow(str) return grey and str or "\27[1;33m" .. str .. "\27[0m" end
local tab_tag = blue "[----------]"
local done_tag = blue "[==========]"
local run_tag = blue "[ RUN ]"
local ok_tag = green "[ OK ]"
local fail_tag = red "[ FAIL]"
local disabled_tag = yellow "[ DISABLED ]"
local passed_tag = green "[ PASSED ]"
local failed_tag = red "[ FAILED ]"
local ntests = 0
local failed = false
local failed_list = {}
local function trace(start_frame)
print "Trace:"
local frame = start_frame
while true do
local info = debug.getinfo(frame, "Sl")
if not info then break end
if info.what == "C" then
print(frame - start_frame, "??????")
else
print(frame - start_frame, info.short_src .. ":" .. info.currentline .. " ")
end
frame = frame + 1
end
end
local function log(msg)
if not quiet then
print(msg)
end
end
local function fail(msg, start_frame)
failed = true
print("Fail: " .. msg)
trace(start_frame or 4)
end
local function stringize_var_arg(...)
local args = { n = select("#", ...), ... }
local result = {}
for i = 1, args.n do
if type(args[i]) == 'string' then
result[i] = '"' .. tostring(args[i]) .. '"'
else
result[i] = tostring(args[i])
end
end
return table.concat(result, ", ")
end
local function test_pretty_name(suite_name, test_name)
if suite_name == "__root" then
return test_name
else
return suite_name .. "." .. test_name
end
end
-- PUBLIC API -----------------------------------------------------------------
local api = { test_suite_name = "__root", skip = false }
api.assert = function (cond)
if not cond then
fail("assertion " .. tostring(cond) .. " failed")
end
end
api.equal = function (l, r)
if l ~= r then
fail(tostring(l) .. " ~= " .. tostring(r))
end
end
api.not_equal = function (l, r)
if l == r then
fail(tostring(l) .. " == " .. tostring(r))
end
end
api.almost_equal = function (l, r, diff)
if require("math").abs(l - r) > diff then
fail("|" .. tostring(l) .. " - " .. tostring(r) .."| > " .. tostring(diff))
end
end
api.is_false = function (maybe_false)
if maybe_false or type(maybe_false) ~= "boolean" then
fail("got " .. tostring(maybe_false) .. " instead of false")
end
end
api.is_true = function (maybe_true)
if not maybe_true or type(maybe_true) ~= "boolean" then
fail("got " .. tostring(maybe_true) .. " instead of true")
end
end
api.is_not_nil = function (maybe_not_nil)
if type(maybe_not_nil) == "nil" then
fail("got nil")
end
end
api.error_raised = function (f, error_message, ...)
local status, err = pcall(f, ...)
if status == true then
fail("error not raised")
else
if error_message ~= nil then
-- we set "plain" to true to avoid pattern matching in string.find
if err:find(error_message, 1, true) == nil then
fail("'" .. error_message .. "' not found in error '" .. tostring(err) .. "'")
end
end
end
end
api.register_assert = function(assert_name, assert_func)
rawset(api, assert_name, function(...)
local result, msg = assert_func(...)
if not result then
msg = msg or "Assertion "..assert_name.." failed"
fail(msg)
end
end)
end
local function make_type_checker(typename)
api["is_" .. typename] = function (maybe_type)
if type(maybe_type) ~= typename then
fail("got " .. tostring(maybe_type) .. " instead of " .. typename, 4)
end
end
end
local supported_types = {"nil", "boolean", "string", "number", "userdata", "table", "function", "thread"}
for i, supported_type in ipairs(supported_types) do
make_type_checker(supported_type)
end
local last_test_suite
local function run_test(test_suite, test_name, test_function, ...)
local suite_name = test_suite.test_suite_name
local full_test_name = test_pretty_name(suite_name, test_name)
if test_regex and not string.match(full_test_name, test_regex) then
return
end
if test_suite.skip then
log(disabled_tag .. " " .. full_test_name)
return
end
if suite_name ~= last_test_suite then
log(tab_tag)
last_test_suite = suite_name
end
ntests = ntests + 1
failed = false
log(run_tag .. " " .. full_test_name)
local start = os.time()
local status, err
for _, f in ipairs({test_suite.start_up, test_function, test_suite.tear_down}) do
status, err = pcall(f, ...)
if not status then
failed = true
print(tostring(err))
end
end
local stop = os.time()
local is_test_failed = not status or failed
log(string.format("%s %s %d sec",
is_test_failed and fail_tag or ok_tag,
full_test_name,
os.difftime(stop, start)))
if is_test_failed then
table.insert(failed_list, full_test_name)
end
end
api.summary = function ()
log(done_tag)
local nfailed = #failed_list
if nfailed == 0 then
log(passed_tag .. " " .. ntests .. " test(s)")
os.exit(0)
else
log(failed_tag .. " " .. nfailed .. " out of " .. ntests .. ":")
for _, test_name in ipairs(failed_list) do
log(failed_tag .. "\t" .. test_name)
end
os.exit(1)
end
end
api.result = function ( ... )
return ntests, #failed_list
end
local default_start_up = function () end
local default_tear_down = function () collectgarbage() end
api.start_up = default_start_up
api.tear_down = default_tear_down
local all_test_cases = { __root = {} }
local function handle_new_test(suite, test_name, test_function)
local suite_name = suite.test_suite_name
if not all_test_cases[suite_name] then
all_test_cases[suite_name] = {}
end
all_test_cases[suite_name][test_name] = test_function
local info = debug.getinfo(test_function)
if info.nparams == nil and
string.sub(test_name, #test_name - 1, #test_name) ~= "_p"
or info.nparams == 0 then
run_test(suite, test_name, test_function)
end
end
local function lookup_test_with_params(suite, test_name)
local suite_name = suite.test_suite_name
if all_test_cases[suite_name] and all_test_cases[suite_name][test_name] then
return function (...)
run_test(suite
, test_name .. "(" .. stringize_var_arg(...) .. ")"
, all_test_cases[suite_name][test_name], ...)
end
else
local full_test_name = test_pretty_name(suite_name, test_name)
table.insert(failed_list, full_test_name)
ntests = ntests + 1
log(fail_tag .. " No " .. full_test_name .. " parametrized test case!")
end
end
local function new_test_suite(_, name)
local test_suite = {
test_suite_name = name,
start_up = default_start_up,
tear_down = default_tear_down,
skip = false }
setmetatable(test_suite, {
__newindex = handle_new_test,
__index = lookup_test_with_params })
return test_suite
end
local test_suites = {}
setmetatable(api, {
__index = function (tbl, name)
if all_test_cases.__root[name] then
return lookup_test_with_params(tbl, name)
end
if not test_suites[name] then
test_suites[name] = new_test_suite(tbl, name)
end
return test_suites[name]
end,
__newindex = handle_new_test
})
return api