Skip to content

Commit a30fe7d

Browse files
committed
Show local variables of a failed test
1 parent 373fffc commit a30fe7d

File tree

6 files changed

+88
-5
lines changed

6 files changed

+88
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Show local variables of a failed test (gh-430).
6+
37
## 1.2.1
48

59
- Fixed a bug when `Server:grep_log()` didn't consider the `reset` option.

luatest/output/tap.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ function Output.mt:update_status(node)
2929
end
3030
if (node:is('fail') or node:is('error')) and self.verbosity >= self.class.VERBOSITY.VERBOSE then
3131
print(prefix .. node.trace:gsub('\n', '\n' .. prefix))
32+
if node.locals ~= nil then
33+
print(prefix .. 'locals:')
34+
print(prefix .. node.locals:gsub('\n', '\n' .. prefix))
35+
end
3236
if utils.table_len(node.servers) > 0 then
3337
print(prefix .. 'artifacts:')
3438
for _, server in pairs(node.servers) do

luatest/output/text.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ function Output.mt:display_one_failed_test(index, fail) -- luacheck: no unused
6161
print(index..") " .. fail.name .. self.class.ERROR_COLOR_CODE)
6262
print(fail.message .. self.class.RESET_TERM)
6363
print(fail.trace)
64+
65+
if fail.locals ~= nil then
66+
print(self.class.WARN_COLOR_CODE)
67+
print('locals:')
68+
print(fail.locals)
69+
print(self.class.RESET_TERM)
70+
end
71+
6472
if utils.table_len(fail.servers) > 0 then
6573
print('artifacts:')
6674
for _, server in pairs(fail.servers) do

luatest/runner.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ function Runner.mt:update_status(node, err)
376376
return
377377
elseif err.status == 'fail' or err.status == 'error' or err.status == 'skip'
378378
or err.status == 'xfail' or err.status == 'xsuccess' then
379-
node:update_status(err.status, err.message, err.trace)
379+
node:update_status(err.status, err.message, err.trace, err.locals)
380380
if utils.table_len(node.servers) > 0 then
381381
for _, server in pairs(node.servers) do
382382
server:save_artifacts()
@@ -434,10 +434,11 @@ function Runner.mt:protected_call(instance, method, pretty_name)
434434
trace:sub(string.len('stack traceback:\n') + 1)
435435
e = e.error
436436
end
437+
local locals = utils.locals()
437438
if utils.is_luatest_error(e) then
438-
return {status = e.status, message = e.message, trace = trace}
439+
return {status = e.status, message = e.message, trace = trace, locals = locals}
439440
else
440-
return {status = 'error', message = e, trace = trace}
441+
return {status = 'error', message = e, trace = trace, locals = locals}
441442
end
442443
end)
443444

luatest/test_instance.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ function TestInstance.mt:initialize()
1919
self.servers = {}
2020
end
2121

22-
function TestInstance.mt:update_status(status, message, trace)
22+
function TestInstance.mt:update_status(status, message, trace, locals)
2323
self.status = status
2424
self.message = message
2525
self.trace = trace
26+
self.locals = locals
2627
end
2728

2829
function TestInstance.mt:is(status)

luatest/utils.lua

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
local digest = require('digest')
22
local fio = require('fio')
33
local fun = require('fun')
4-
local yaml = require('yaml')
4+
local yaml = require('yaml').new()
5+
6+
-- yaml.encode() fails on a function value otherwise.
7+
yaml.cfg({encode_use_tostring = true})
58

69
local utils = {}
710

@@ -168,6 +171,68 @@ function utils.upvalues(fn)
168171
return ret
169172
end
170173

174+
-- Get local variables from a first call frame outside luatest.
175+
--
176+
-- Returns nil if nothing found due to any reason.
177+
function utils.locals()
178+
-- Determine a first frame with the user code (outside of
179+
-- luatest).
180+
local level = 3
181+
while true do
182+
local info = debug.getinfo(level, 'S')
183+
184+
-- If nothing found, exit earlier.
185+
if info == nil then
186+
return nil
187+
end
188+
189+
-- Stop on first non-luatest frame.
190+
if type(info.source) == 'string' and
191+
info.what ~= 'C' and
192+
not is_luatest_internal_line(info.source) then
193+
break
194+
end
195+
196+
level = level + 1
197+
end
198+
199+
-- Don't try to show more then 100 variables.
200+
local LIMIT = 100
201+
202+
local res = setmetatable({}, {__serialize = 'mapping'})
203+
for i = 1, LIMIT do
204+
local name, value = debug.getlocal(level, i)
205+
206+
-- Stop if there are no more local variables.
207+
if name == nil then
208+
break
209+
end
210+
211+
-- > Variable names starting with '(' (open parentheses)
212+
-- > represent internal variables (loop control variables,
213+
-- > temporaries, and C function locals).
214+
--
215+
-- https://www.lua.org/manual/5.1/manual.html#pdf-debug.getlocal
216+
if not name:startswith('(') then
217+
res[name] = value
218+
end
219+
end
220+
221+
-- If no local variables found, return just nil to don't
222+
-- show a garbage output like the following.
223+
--
224+
-- | locals:
225+
-- | --- {}
226+
-- | ...
227+
if next(res) == nil then
228+
return nil
229+
end
230+
231+
-- Encode right here to hold the state of the locals and
232+
-- ignore all the future changes.
233+
return yaml.encode(res):rstrip()
234+
end
235+
171236
function utils.get_fn_location(fn)
172237
local fn_details = debug.getinfo(fn)
173238
local fn_source = fn_details.source:split('/')

0 commit comments

Comments
 (0)