Skip to content

Commit dfee2f3

Browse files
nshyylobankov
authored andcommitted
Make assert_error_covers support stacked diagnostics
That is if there is `expected.prev` then it should cover `actual.prev` and so on recursively. Also make existing tests for `assert_error_covers` more precise. Closes #372
1 parent e7667b0 commit dfee2f3

File tree

2 files changed

+117
-30
lines changed

2 files changed

+117
-30
lines changed

luatest/assertions.lua

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -648,10 +648,39 @@ function M.assert_error_msg_matches(pattern, fn, ...)
648648
end
649649
end
650650

651+
-- If it is box.error that unpack it recursively. If it is not then
652+
-- return argument unchanged.
653+
local function error_unpack(err)
654+
if type(err) ~= 'cdata' or ffi.typeof(err) ~= box_error_type then
655+
return err
656+
end
657+
local unpacked = err:unpack()
658+
local tmp = unpacked
659+
while tmp.prev ~= nil do
660+
tmp.prev = tmp.prev:unpack()
661+
tmp = tmp.prev
662+
end
663+
return unpacked
664+
end
665+
666+
-- Return table with keys from expected but values from actual. Apply
667+
-- same changes recursively for key 'prev'.
668+
local function error_slice(actual, expected)
669+
if type(expected) ~= 'table' or type(actual) ~= 'table' then
670+
return actual
671+
end
672+
local sliced = {}
673+
for k, _ in pairs(expected) do
674+
sliced[k] = actual[k]
675+
end
676+
sliced.prev = error_slice(sliced.prev, expected.prev)
677+
return sliced
678+
end
679+
651680
--- Checks that error raised by function is table that includes expected one.
652-
---
653-
--- If error object supports unpack() method (like Tarantool errors) then
654-
--- error is unpacked to get the table to be compared.
681+
--- box.error is unpacked to convert to table. Stacked errors are supported.
682+
--- That is if there is prev field in expected then it should cover prev field
683+
--- in actual and so on recursively.
655684
--
656685
-- @tab expected
657686
-- @func fn
@@ -663,12 +692,10 @@ function M.assert_error_covers(expected, fn, ...)
663692
'Function successfully returned: %s\nExpected error: %s',
664693
prettystr(actual), prettystr(expected))
665694
end
666-
if actual.unpack ~= nil then
667-
actual = actual:unpack()
668-
end
669-
if type(actual) ~= 'table' or not table_covers(actual, expected) then
670-
actual, expected = prettystr_pairs(actual, expected)
671-
fail_fmt(2, nil, 'Error expected: %s\nError received: %s\n',
695+
local unpacked = error_unpack(actual)
696+
if not comparator.equals(error_slice(unpacked, expected), expected) then
697+
actual, expected = prettystr_pairs(unpacked, expected)
698+
fail_fmt(2, nil, 'Error expected: %s\nError received: %s',
672699
expected, actual)
673700
end
674701
end

test/luaunit/assertions_error_test.lua

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ local helper = require('test.helpers.general')
55
local assert_failure = helper.assert_failure
66
local assert_failure_equals = helper.assert_failure_equals
77
local assert_failure_contains = helper.assert_failure_contains
8+
local assert_failure_matches = helper.assert_failure_matches
89

910
local function f()
1011
end
@@ -22,13 +23,13 @@ local f_check_trace = function(level)
2223
box.error(box.error.UNKNOWN, level)
2324
end
2425

25-
local line = debug.getinfo(1, 'l').currentline + 2
26+
local wrapper_line = debug.getinfo(1, 'l').currentline + 2
2627
local f_check_trace_wrapper = function()
2728
f_check_trace(2)
2829
end
2930

30-
local _, err = pcall(f_check_trace_wrapper)
31-
local box_error_has_level = err:unpack().trace[1].line == line
31+
local _, wrapper_err = pcall(f_check_trace_wrapper)
32+
local box_error_has_level = wrapper_err:unpack().trace[1].line == wrapper_line
3233

3334
local f_check_success = function()
3435
return {1, 'foo'}
@@ -163,26 +164,85 @@ function g.test_assert_errorMsgMatches()
163164
end
164165

165166
function g.test_assert_errorCovers()
167+
local actual
168+
local expected
166169
-- function executes successfully
167-
assert_failure(t.assert_error_covers, {}, f, 1)
168-
-- function expected to raise a table or has unpack()
169-
assert_failure(t.assert_error_covers, {}, f_with_error, 1)
170-
171-
-- good error coverage, error is table
172-
t.assert_error_covers({b = 2},
173-
function(a, b) error({a = a, b = b}) end, 1, 2)
174-
local error_with_unpack = function(a, b)
175-
local e = {}
176-
e.unpack = function()
177-
return {a = a, b = b}
178-
end
179-
error(e)
170+
assert_failure_equals('Function successfully returned: {1, "foo"}\n' ..
171+
'Expected error: {}', t.assert_error_covers, {},
172+
function() return {1, 'foo'} end)
173+
----------------
174+
-- good coverage
175+
----------------
176+
t.assert_error_covers({}, error, {})
177+
t.assert_error_covers({b = 2}, error, {b = 2})
178+
t.assert_error_covers({b = 2}, error, {a = 1, b = 2})
179+
actual = {a = 1, b = 2, prev = {x = 3, y = 4}}
180+
expected = {b = 2, prev = {x = 3}}
181+
t.assert_error_covers(expected, error, actual)
182+
actual.prev.prev = {i = 5, j = 6}
183+
expected.prev.prev = {j = 6}
184+
t.assert_error_covers(expected, error, actual)
185+
---------------
186+
-- bad coverage
187+
---------------
188+
local msg = 'Error expected: .*\nError received: .*'
189+
assert_failure_matches(msg, t.assert_error_covers,
190+
{b = 2}, error, {a = 1, b = 3})
191+
assert_failure_matches(msg, t.assert_error_covers, {b = 2}, error, {a = 1})
192+
assert_failure_matches(msg, t.assert_error_covers, {b = 2}, error, {})
193+
actual = {a = 1, b = 2, prev = {x = 3, y = 4}}
194+
expected = {b = 2, prev = {x = 4}}
195+
assert_failure_matches(msg, t.assert_error_covers, expected, error, actual)
196+
actual = {a = 1, b = 2, prev = {x = 3, y = 4, prev = {i = 5, j = 6}}}
197+
expected = {b = 2, prev = {x = 3, prev = {i = 6}}}
198+
assert_failure_matches(msg, t.assert_error_covers, expected, error, actual)
199+
--------
200+
--- misc
201+
--------
202+
-- several arguments for tested function
203+
local error_args = function(a, b) error({a = a, b = b}) end
204+
t.assert_error_covers({b = 2}, error_args, 1, 2)
205+
-- full error message
206+
assert_failure_equals('Error expected: {b = 2}\n' ..
207+
'Error received: {a = 1, b = 3}',
208+
t.assert_error_covers, {b = 2}, error, {a = 1, b = 3})
209+
---------------
210+
-- corner cases
211+
---------------
212+
-- strange, but still
213+
t.assert_error_covers('foo', error, 'foo')
214+
-- same but for stacked diagnostics
215+
t.assert_error_covers({b = 2, prev = 'foo'},
216+
error, {a = 1, b = 2, prev = 'foo'})
217+
-- actual error is not table
218+
assert_failure_matches(msg, t.assert_error_covers, {}, error, 'foo')
219+
-- expected in not a table
220+
assert_failure_matches(msg, t.assert_error_covers, 2, error, {})
221+
-- actual error is not indexable
222+
assert_failure_matches(msg, t.assert_error_covers, {}, error, 1LL)
223+
-- actual error prev is not table
224+
assert_failure_matches(msg, t.assert_error_covers, {prev = {}},
225+
error, {prev = 'foo'})
226+
-- expected error prev is not table
227+
assert_failure_matches(msg, t.assert_error_covers, {prev = 'foo'},
228+
error, {prev = {}})
229+
-- actual error prev is not indexable
230+
assert_failure_matches(msg, t.assert_error_covers, {prev = {}},
231+
error, {prev = 1LL})
232+
------------
233+
-- box.error
234+
------------
235+
t.assert_error_covers({type = 'ClientError', code = 0}, box.error, 0)
236+
local err = box.error.new(box.error.UNKNOWN)
237+
if err.set_prev ~= nil then
238+
err:set_prev(box.error.new(box.error.ILLEGAL_PARAMS, 'foo'))
239+
expected = {
240+
type = 'ClientError',
241+
code = box.error.UNKNOWN,
242+
prev = {code = box.error.ILLEGAL_PARAMS}
243+
}
244+
t.assert_error_covers(expected, err.raise, err)
180245
end
181-
-- good error coverage, error has unpack()
182-
t.assert_error_covers({b = 2}, error_with_unpack, 1, 2)
183-
-- bad error coverage
184-
assert_failure(t.assert_error_covers, {b = 2},
185-
function(a, b) error({a = a, b = b}) end, 1, 3)
186246

187247
-- test assert failure due to unexpected error trace
188248
t.private.check_trace_module = THIS_MODULE

0 commit comments

Comments
 (0)