@@ -10,6 +10,8 @@ local mismatch_formatter = require('luatest.mismatch_formatter')
10
10
local pp = require (' luatest.pp' )
11
11
local log = require (' luatest.log' )
12
12
local utils = require (' luatest.utils' )
13
+ local tarantool = require (' tarantool' )
14
+ local ffi = require (' ffi' )
13
15
14
16
local prettystr = pp .tostring
15
17
local prettystr_pairs = pp .tostring_pair
@@ -18,6 +20,8 @@ local M = {}
18
20
19
21
local xfail = false
20
22
23
+ local box_error_type = ffi .typeof (box .error .new (box .error .UNKNOWN ))
24
+
21
25
-- private exported functions (for testing)
22
26
M .private = {}
23
27
@@ -85,12 +89,87 @@ local function error_msg_equality(actual, expected, deep_analysis)
85
89
end
86
90
M .private .error_msg_equality = error_msg_equality
87
91
92
+ --
93
+ -- The wrapper is used when trace check is required. See pcall_check_trace.
94
+ --
95
+ -- Without wrapper the trace will point to the pcall implementation. So trace
96
+ -- check is not strict enough (the trace can point to any pcall in below in
97
+ -- call trace).
98
+ --
99
+ local trace_line = debug.getinfo (1 , ' l' ).currentline + 2
100
+ local function wrapped_call (fn , ...)
101
+ local res = utils .table_pack (fn (... ))
102
+ -- With `return fn(...)` wrapper does not work due to tail call
103
+ -- optimization.
104
+ return unpack (res , 1 , res .n )
105
+ end
106
+
107
+ -- Expected trace for trace check. See pcall_check_trace.
108
+ local wrapped_trace = {
109
+ file = debug.getinfo (1 , ' S' ).short_src ,
110
+ line = trace_line ,
111
+ }
112
+
113
+ -- Used in tests to force check for given module.
114
+ M .private .check_trace_module = nil
115
+
116
+ --
117
+ -- Return true if error trace check is required for function. Basically it is
118
+ -- just a wrapper around Tarantool's utils.proper_trace_required. Additionally
119
+ -- old Tarantool versions where this function is not present are handled.
120
+ --
121
+ local function trace_check_is_required (fn )
122
+ local src = debug.getinfo (fn , ' S' ).short_src
123
+ if M .private .check_trace_module == src then
124
+ return true
125
+ end
126
+ if tarantool ._internal ~= nil and
127
+ tarantool ._internal .trace_check_is_required ~= nil then
128
+ local path = debug.getinfo (fn , ' S' ).short_src
129
+ return tarantool ._internal .trace_check_is_required (path )
130
+ end
131
+ return false
132
+ end
133
+
134
+ --
135
+ -- Substitute for pcall but additionally checks error trace if required.
136
+ --
137
+ -- The error should be box.error and trace should point to the place
138
+ -- where fn is called.
139
+ --
140
+ -- level is used to set proper level in error assertions that use this function.
141
+ --
142
+ local function pcall_check_trace (level , fn , ...)
143
+ local fn_explicit = fn
144
+ if type (fn ) ~= ' function' then
145
+ fn_explicit = debug.getmetatable (fn ).__call
146
+ end
147
+ if not trace_check_is_required (fn_explicit ) then
148
+ return pcall (fn , ... )
149
+ end
150
+ local ok , err = pcall (wrapped_call , fn , ... )
151
+ if ok then
152
+ return ok , err
153
+ end
154
+ if type (err ) ~= ' cdata' or ffi .typeof (err ) ~= box_error_type then
155
+ fail_fmt (level + 1 , nil , ' Error raised is not a box.error: %s' ,
156
+ prettystr (err ))
157
+ end
158
+ local unpacked = err :unpack ()
159
+ if not comparator .equals (unpacked .trace [1 ], wrapped_trace ) then
160
+ fail_fmt (level + 1 , nil ,
161
+ ' Unexpected error trace, expected: %s, actual: %s' ,
162
+ prettystr (wrapped_trace ), prettystr (unpacked .trace [1 ]))
163
+ end
164
+ return ok , err
165
+ end
166
+
88
167
--- Check that calling fn raises an error.
89
168
--
90
169
-- @func fn
91
170
-- @param ... arguments for function
92
171
function M .assert_error (fn , ...)
93
- local ok , err = pcall ( fn , ... )
172
+ local ok , err = pcall_check_trace ( 2 , fn , ... )
94
173
if ok then
95
174
failure (" Expected an error when calling function but no error generated" , nil , 2 )
96
175
end
@@ -464,7 +543,7 @@ function M.assert_str_matches(value, pattern, start, final, message)
464
543
end
465
544
466
545
local function _assert_error_msg_equals (stripFileAndLine , expectedMsg , func , ...)
467
- local no_error , error_msg = pcall ( func , ... )
546
+ local no_error , error_msg = pcall_check_trace ( 3 , func , ... )
468
547
if no_error then
469
548
local failure_message = string.format (
470
549
' Function successfully returned: %s\n Expected error: %s' ,
530
609
-- @func fn
531
610
-- @param ... arguments for function
532
611
function M .assert_error_msg_contains (expected_partial , fn , ...)
533
- local no_error , error_msg = pcall ( fn , ... )
612
+ local no_error , error_msg = pcall_check_trace ( 2 , fn , ... )
534
613
log .info (' Assert error message %s contains %s' , error_msg , expected_partial )
535
614
if no_error then
536
615
local failure_message = string.format (
553
632
-- @func fn
554
633
-- @param ... arguments for function
555
634
function M .assert_error_msg_matches (pattern , fn , ...)
556
- local no_error , error_msg = pcall ( fn , ... )
635
+ local no_error , error_msg = pcall_check_trace ( 2 , fn , ... )
557
636
if no_error then
558
637
local failure_message = string.format (
559
638
' Function successfully returned: %s\n Expected error matching: %s' ,
578
657
-- @func fn
579
658
-- @param ... arguments for function
580
659
function M .assert_error_covers (expected , fn , ...)
581
- local ok , actual = pcall ( fn , ... )
660
+ local ok , actual = pcall_check_trace ( 2 , fn , ... )
582
661
if ok then
583
662
fail_fmt (2 , nil ,
584
663
' Function successfully returned: %s\n Expected error: %s' ,
0 commit comments