Skip to content
This repository was archived by the owner on Nov 20, 2020. It is now read-only.

Commit 3a7b923

Browse files
committed
Distinguish between test errors and test failures
A test error occurs when an error is thrown or an assert fails outside of the test. While test failures only occur when an assert fails inside a test. Call setfenv on 'assert' to replace 'error' with 'fail', such that 'fail' throws a special failure table instead of a string. Hence, assertion failures will throw a failure table and are distinct from errors. Additionally, the failure table contains the same error string that would have been thrown with 'error'.
1 parent e5b51d6 commit 3a7b923

File tree

9 files changed

+227
-14
lines changed

9 files changed

+227
-14
lines changed

Diff for: busted/compatibility.lua

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
return {
2+
getfenv = getfenv or function(f)
3+
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
4+
local name, value
5+
local up = 0
6+
7+
repeat
8+
up = up + 1
9+
name, value = debug.getupvalue(f, up)
10+
until name == '_ENV' or name == nil
11+
12+
return (name and value or _G)
13+
end,
14+
215
setfenv = setfenv or function(f, t)
316
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
417
local name

Diff for: busted/core.lua

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
local metatype = function(obj)
2+
local otype = type(obj)
3+
if otype == 'table' then
4+
local mt = getmetatable(obj)
5+
if mt and mt.__type then
6+
return mt.__type
7+
end
8+
end
9+
return otype
10+
end
11+
12+
local failureMt = {
13+
__index = {},
14+
__tostring = function(e) return e.message end,
15+
__type = 'failure'
16+
}
17+
18+
local getfenv = require 'busted.compatibility'.getfenv
19+
local setfenv = require 'busted.compatibility'.setfenv
20+
local throw = error
21+
122
return function()
223
local mediator = require 'mediator'()
324

@@ -16,6 +37,12 @@ return function()
1637
level = level or 3
1738

1839
local info = debug.getinfo(level, 'Sl')
40+
while info.what == 'C' or info.short_src:match('luassert[/\\].*%.lua$') or
41+
info.short_src:match('busted[/\\].*%.lua$') do
42+
level = level + 1
43+
info = debug.getinfo(level, 'Sl')
44+
end
45+
1946
info.traceback = debug.traceback('', level)
2047
info.message = msg
2148

@@ -64,6 +91,21 @@ return function()
6491
return parent
6592
end
6693

94+
function busted.fail(msg, level)
95+
local _, emsg = pcall(error, msg, level+2)
96+
local e = { message = emsg }
97+
setmetatable(e, failureMt)
98+
throw(e, level+1)
99+
end
100+
101+
function busted.replaceErrorWithFail(callable)
102+
local env = {}
103+
local f = getmetatable(callable).__call or callable
104+
setmetatable(env, { __index = getfenv(f) })
105+
env.error = busted.fail
106+
setfenv(f, env)
107+
end
108+
67109
function busted.safe(descriptor, run, element, setenv)
68110
if setenv and (type(run) == 'function' or getmetatable(run).__call) then
69111
-- prioritize __call if it exists, like in files
@@ -72,15 +114,19 @@ return function()
72114

73115
busted.context.push(element)
74116
local trace, message
117+
local status = 'success'
75118

76119
local ret = { xpcall(run, function(msg)
77-
message = busted.rewriteMessage(element, msg)
120+
local errType = metatype(msg)
121+
status = (errType == 'string' and 'error' or errType)
122+
message = busted.rewriteMessage(element, tostring(msg))
78123
trace = busted.getTrace(element, 3, msg)
79124
end) }
80125

81126
if not ret[1] then
82-
busted.publish({ 'error', descriptor }, element, busted.context.parent(element), message, trace)
127+
busted.publish({ status, descriptor }, element, busted.context.parent(element), message, trace)
83128
end
129+
ret[1] = status
84130

85131
busted.context.pop()
86132
return unpack(ret)

Diff for: busted/init.lua

+7-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ return function(busted)
4141
local file = function(file)
4242
busted.publish({ 'file', 'start' }, file.name)
4343

44-
if busted.safe('file', file.run, file, true) then
44+
if busted.safe('file', file.run, file, true) == 'success' then
4545
busted.execute(file)
4646
end
4747

@@ -60,7 +60,7 @@ return function(busted)
6060
randomize = true
6161
end
6262

63-
if busted.safe('describe', describe.run, describe) then
63+
if busted.safe('describe', describe.run, describe) == 'success' then
6464
if randomize then
6565
shuffle(busted.context.children(describe))
6666
end
@@ -87,9 +87,9 @@ return function(busted)
8787

8888
busted.publish({ 'test', 'start' }, element, parent)
8989

90-
local res = busted.safe('element', element.run, element)
90+
local status = busted.safe('element', element.run, element)
9191
if not element.env.done then
92-
busted.publish({ 'test', 'end' }, element, parent, res and 'success' or 'failure')
92+
busted.publish({ 'test', 'end' }, element, parent, status)
9393
if finally then busted.safe('finally', finally, element) end
9494
dexecAll('after_each', parent, true)
9595
end
@@ -119,5 +119,8 @@ return function(busted)
119119
mock = require 'luassert.mock'
120120
stub = require 'luassert.stub'
121121

122+
busted.replaceErrorWithFail(assert)
123+
busted.replaceErrorWithFail(assert.True)
124+
122125
return busted
123126
end

Diff for: busted/outputHandlers/base.lua

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ return function(busted)
2727
busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd)
2828
busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { predicate = handler.cancelOnPending })
2929
busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { predicate = handler.cancelOnPending })
30+
busted.subscribe({ 'failure' }, handler.baseError)
3031
busted.subscribe({ 'error' }, handler.baseError)
3132
end
3233

@@ -87,6 +88,7 @@ return function(busted)
8788

8889
handler.baseTestEnd = function(element, parent, status, debug)
8990

91+
local isError
9092
local insertTable
9193
local id = tostring(element)
9294

@@ -99,9 +101,13 @@ return function(busted)
99101
elseif status == 'failure' then
100102
insertTable = handler.failures
101103
handler.failuresCount = handler.failuresCount + 1
104+
elseif status == 'error' then
105+
insertTable = handler.errors
106+
handler.errorsCount = handler.errorsCount + 1
107+
isError = true
102108
end
103109

104-
insertTable[id] = handler.format(element, parent, nil, debug)
110+
insertTable[id] = handler.format(element, parent, nil, debug, isError)
105111

106112
if handler.inProgress[id] then
107113
for k, v in pairs(handler.inProgress[id]) do

Diff for: busted/outputHandlers/plainTerminal.lua

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ return function(options, busted)
2929
if failure.message then
3030
string = string .. '' .. failure.message .. '\n'
3131
end
32+
string = string ..
33+
handler.getFullName(failure)
3234
else
3335
string = string ..
3436
failure.trace.short_src .. ' @ ' ..
@@ -44,7 +46,7 @@ return function(options, busted)
4446
end
4547
end
4648

47-
if options.verbose and failure.trace.traceback then
49+
if options.verbose and failure.trace and failure.trace.traceback then
4850
string = string .. '\n' .. failure.trace.traceback
4951
end
5052

@@ -104,6 +106,8 @@ return function(options, busted)
104106
string = pendingDot
105107
elseif status == 'failure' then
106108
string = failureDot
109+
elseif status == 'error' then
110+
string = errorDot
107111
end
108112

109113
io.write(string)

Diff for: busted/outputHandlers/utfTerminal.lua

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ return function(options, busted)
3030
if failure.message then
3131
string = string .. '' .. ansicolors('%{cyan}' .. failure.message) .. '\n'
3232
end
33+
string = string ..
34+
ansicolors('%{bright}' .. handler.getFullName(failure))
3335
else
3436
string = string ..
3537
ansicolors('%{cyan}' .. failure.trace.short_src) .. ' @ ' ..
@@ -45,7 +47,7 @@ return function(options, busted)
4547
end
4648
end
4749

48-
if options.verbose and failure.trace.traceback then
50+
if options.verbose and failure.trace and failure.trace.traceback then
4951
string = string .. '\n' .. failure.trace.traceback
5052
end
5153

@@ -105,6 +107,8 @@ return function(options, busted)
105107
string = pendingDot
106108
elseif status == 'failure' then
107109
string = failureDot
110+
elseif status == 'error' then
111+
string = errorDot
108112
end
109113

110114
io.write(string)
@@ -143,7 +147,7 @@ return function(options, busted)
143147
return nil, true
144148
end
145149

146-
local s = busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
150+
busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
147151
busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
148152
busted.subscribe({ 'error', 'file' }, handler.error)
149153
busted.subscribe({ 'error', 'describe' }, handler.error)

Diff for: spec/cl_errors.lua

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- supporting testfile; belongs to 'cl_spec.lua'
2+
3+
describe('Tests the busted error detection through the commandline', function()
4+
5+
it('is a test that throws an error #testerr', function()
6+
error('force an error')
7+
end)
8+
9+
it('is a test with a Lua error #luaerr', function()
10+
local foo
11+
foo.bar = nil
12+
end)
13+
end)
14+

Diff for: spec/cl_spec.lua

+123
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ local execute = function(cmd)
3636
return not not success, modexit(exitcode)
3737
end
3838

39+
local run = function(cmd)
40+
local p = io.popen(cmd, 'r')
41+
local out = p:read('*a')
42+
p:close()
43+
return out
44+
end
45+
3946

4047
it('Tests the busted command-line options', function()
4148

@@ -178,5 +185,121 @@ describe('Tests failing tests through the commandline', function()
178185
assert.is_equal(8, exitcode)
179186
error_end()
180187
end)
188+
189+
it('tests failing support functions as errors', function()
190+
error_start()
191+
local result = run('bin/busted --output=plainTerminal --pattern=cl_failing_support.lua$')
192+
local _, numErrors = result:gsub('Error → .-\n','')
193+
assert.is_equal(8, numErrors)
194+
error_end()
195+
end)
196+
end)
197+
198+
describe('Tests distinguish between errors and failures', function()
199+
it('by detecting errors as test errors', function()
200+
error_start()
201+
local result = run('bin/busted --output=plainTerminal --pattern=cl_errors.lua$ --tags=testerr')
202+
local errmsg = result:match('(Error → .-)\n')
203+
assert.is_truthy(errmsg)
204+
error_end()
205+
end)
206+
207+
it('by detecting assert failures as test failures', function()
208+
error_start()
209+
local result = run('bin/busted --output=plainTerminal --pattern=cl_two_failures.lua$')
210+
local failmsg = result:match('(Failure → .-)\n')
211+
assert.is_truthy(failmsg)
212+
error_end()
213+
end)
214+
215+
it('by detecting Lua runtime errors as test errors', function()
216+
error_start()
217+
local result = run('bin/busted --output=plainTerminal --pattern=cl_errors.lua$ --tags=luaerr')
218+
local failmsg = result:match('(Error → .-)\n')
219+
assert.is_truthy(failmsg)
220+
error_end()
221+
end)
222+
end)
223+
224+
describe('Tests stack trackback', function()
225+
it('when throwing an error', function()
226+
error_start()
227+
local result = run('bin/busted --verbose --pattern=cl_errors.lua$ --tags=testerr')
228+
local errmsg = result:match('(stack traceback:.*)\n')
229+
local expected = [[stack traceback:
230+
./spec/cl_errors.lua:6: in function <./spec/cl_errors.lua:5>
231+
]]
232+
assert.is_equal(expected, errmsg)
233+
error_end()
234+
end)
235+
236+
it('when assertion fails', function()
237+
error_start()
238+
local result = run('bin/busted --verbose --pattern=cl_two_failures.lua$ --tags=err1')
239+
local errmsg = result:match('(stack traceback:.*)\n')
240+
local expected = [[stack traceback:
241+
./spec/cl_two_failures.lua:6: in function <./spec/cl_two_failures.lua:5>
242+
]]
243+
assert.is_equal(expected, errmsg)
244+
error_end()
245+
end)
246+
247+
it('when Lua runtime error', function()
248+
error_start()
249+
local result = run('bin/busted --verbose --pattern=cl_errors.lua$ --tags=luaerr')
250+
local errmsg = result:match('(stack traceback:.*)\n')
251+
local expected = [[stack traceback:
252+
./spec/cl_errors.lua:11: in function <./spec/cl_errors.lua:9>
253+
]]
254+
assert.is_equal(expected, errmsg)
255+
error_end()
256+
end)
257+
end)
258+
259+
describe('Tests error messages through the command line', function()
260+
it('when throwing errors in a test', function()
261+
error_start()
262+
local result = run('bin/busted --output=plainTerminal --pattern=cl_errors.lua$ --tags=testerr')
263+
local errmsg = result:match('(Error → .-)\n')
264+
local expected = "Error → ./spec/cl_errors.lua:6: force an error"
265+
assert.is_equal(expected, errmsg)
266+
error_end()
267+
end)
268+
269+
it('when running a non-compiling testfile', function()
270+
error_start()
271+
local result = run('bin/busted --output=plainTerminal --pattern=cl_compile_fail.lua$')
272+
local errmsg = result:match('(Error → .-)\n')
273+
local expected = "Error → ./spec/cl_compile_fail.lua:3: '=' expected near 'here'"
274+
assert.is_equal(expected, errmsg)
275+
error_end()
276+
end)
277+
278+
it('when a testfile throws errors', function()
279+
error_start()
280+
local result = run('bin/busted --output=plainTerminal --pattern=cl_execute_fail.lua$')
281+
local errmsg = result:match('(Error → .-)\n')
282+
local expected = 'Error → ./spec/cl_execute_fail.lua:4: This compiles fine, but throws an error when being run'
283+
assert.is_equal(expected, errmsg)
284+
error_end()
285+
end)
286+
287+
it('when output library not found', function()
288+
error_start()
289+
local result = run('bin/busted --pattern=cl_two_failures.lua$ --output=not_found_here')
290+
local errmsg = result:match('(.-)\n')
291+
local expected = 'Cannot load output library: not_found_here'
292+
assert.is_equal(expected, errmsg)
293+
error_end()
294+
end)
295+
296+
it('when no test files matching Lua pattern', function()
297+
error_start()
298+
local result = run('bin/busted --output=plainTerminal --pattern=this_filename_does_simply_not_exist$')
299+
local errmsg = result:match('(.-)\n')
300+
local expected = 'No test files found matching Lua pattern: this_filename_does_simply_not_exist$'
301+
assert.is_equal(expected, errmsg)
302+
error_end()
303+
end)
181304
end)
182305
--]]

0 commit comments

Comments
 (0)