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
|
local utils = require 'lem.utils'
local updatenow = utils.updatenow
local pack = table.pack
local unpack = table.unpack
local format = string.format
local master
local function test_assert(v, msg, ...)
if not v then
error(msg or 'assertion failed!', 2)
end
master.assert_cnt = master.assert_cnt + 1
return v, msg, ...
end
local function msg_handler(msg)
local trace = debug.traceback(msg, 4)
return trace
--return string.match(trace, '^(.-)\n[^\n]-tester_cut_traceback_here')
end
local test = {}
test.__index = test
function test:enter()
self.prev_test = master.activetest
master.activetest = self
self.real_assert = assert
_ENV.assert = test_assert
end
function test:leave()
master.activetest = self.prev_test
self.prev_test = nil
_ENV.assert = self.real_assert
self.real_assert = nil
end
function test:depend(t)
if type(t) == 'string' then
t = require('test.'..t)
end
table.insert(self.dependencies, t)
end
local function tester_cut_traceback_here(self)
local deps = self.dependencies
for i=1,#deps do
local dep = deps[i]
dep:run()
end
if self.test then
self:test()
end
end
local function tester(...)
-- hack due to the first function called by xpcall
-- not being refered to by name in tracebacks
local ret, msg = tester_cut_traceback_here(...)
return ret, msg
end
function test:run()
if master == nil then
self:setmaster()
end
if master.have_run[self] then
return self.passed -- TODO return previous result
end
self:enter()
local t1, t2
t1 = updatenow()
local ret, msg = xpcall(tester, msg_handler, self)
t2 = updatenow()
self.runtime = t2 - t1
self:leave()
if not ret then
self.error_msg = msg
end
master.have_run[self] = true
self.passed = ret
master.run = master.run + 1
if ret then
master.passed = master.passed + 1
else
table.insert(master.failed, self)
end
return ret
end
function test:setmaster()
assert(master == nil, 'master already set')
master = {
test = self,
assert_cnt = 0,
have_run = {},
passed = 0,
run = 0,
failed = {},
}
end
function test:reset()
if self ~= master.test then
error('you can only run reset on current master task')
end
master = nil
end
local function new_test(func)
local src = debug.getinfo(func or 2, 'S').short_src
return setmetatable({
test = func,
dependencies = {},
source = src,
}, test)
end
function test:stats()
if self ~= master.test then
error('you can only run stats on current master task')
end
local run, passed, asserts = master.run, master.passed, master.assert_cnt
if run == passed then
print(format('%d/%d All tests passed, %d assertions in %.3fs',
run, passed, asserts, master.test.runtime))
else
print(format('%d/%d %d failed', run, passed, run-passed))
end
end
function test:failed()
if self ~= master.test then
error('you can only run stats on current master task')
end
for i=1,#master.failed do
local t = master.failed[i]
print(t.error_msg)
end
end
local function assert_fail(cb, ...)
local ret = pack(pcall(cb, ...))
if ret[1] then assert(false, 'doomed function succeeded') end
return unpack(ret)
end
local M = {}
function M.test()
local index = require 'test.all'
index:run()
index:stats()
index:failed()
end
M.new = new_test
M.fail = assert_fail
return M
|