Skip to content

Commit 2dcbed7

Browse files
mandeserolocker
authored andcommitted
hooks: forbid direct hook assignment
Before this patch luatest accepted two ways to register hooks: * call-style API: `group.before_all(function() ... end)` * direct assignment: `group.before_all = function() ... end` The second form does not work correctly with parametrized tests. We already encountered this on the tarantool side and had to rewrite such assignments in tarantool/tarantool#8187. With this change luatest enforces a single, consistent way to register hooks: * hooks must be registered via calls like `before_each(function() ... end)`, `after_each(function() ... end)`, `before_all(function() ... end)`, `after_all(function() ... end)`, `before_suite(function() ... end)`, `after_suite(function() ... end)`, `before_test('name', function() ... end)` and `after_test('name', function() ... end)`; * assigning to these names (e.g. `group.before_each = fn`) now raises an error with a clear message. Legacy `group.setup` / `group.teardown` remain supported and are not affected by this change. Legacy `group.setup` / `group.teardown` are no longer supported; use `before_each`/`after_each` instead. Closes #390
1 parent c070196 commit 2dcbed7

File tree

9 files changed

+217
-113
lines changed

9 files changed

+217
-113
lines changed

CHANGELOG.md

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

3+
## Unreleased
4+
5+
- Group and suite hooks must now be registered using the call-style
6+
API. Use:
7+
8+
- `before_each(function() ... end)`
9+
- `after_each(function() ... end)`
10+
- `before_all(function() ... end)`
11+
- `after_all(function() ... end)`
12+
- `before_suite(function() ... end)`
13+
- `after_suite(function() ... end)`
14+
- `before_test('name', function() ... end)`
15+
- `after_test('name', function() ... end)`
16+
17+
Assigning hooks as fields (for example, `group.before_each = fn` or
18+
`luatest.before_suite = fn`) is no longer allowed and results in an
19+
error with a descriptive message.
20+
Legacy `group.setup` and `group.teardown` are no longer supported; use
21+
`before_each`/`after_each` instead (gh-390).
22+
323
## 1.3.1
424

525
- Fixed a bug when `assert_covers` didn't check array items for coverage and

luatest/hooks.lua

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,56 @@ local function check_params(required, actual)
9393
return true
9494
end
9595

96+
local function set_hook_assignment_guard(object, hooks_type, accessor, opts)
97+
opts = opts or {}
98+
local mt = getmetatable(object)
99+
local guard = mt.__luatest_hook_guard
100+
101+
if not guard then
102+
local new_mt = table.copy(mt)
103+
guard = {
104+
original_index = mt.__index,
105+
original_newindex = mt.__newindex,
106+
values = {},
107+
error_messages = {},
108+
}
109+
new_mt.__luatest_hook_guard = guard
110+
new_mt.__index = function(tbl, key)
111+
if guard.values[key] ~= nil then
112+
return guard.values[key]
113+
end
114+
115+
local original_index = guard.original_index
116+
if type(original_index) == 'function' then
117+
return original_index(tbl, key)
118+
end
119+
if type(original_index) == 'table' then
120+
return original_index[key]
121+
end
122+
end
123+
new_mt.__newindex = function(tbl, key, value)
124+
if guard.values[key] ~= nil then
125+
local message = guard.error_messages[key] or
126+
string.format('Hook \'%s\' should be registered ' ..
127+
'using %s(<function>)', key, key)
128+
error(message)
129+
end
130+
if guard.original_newindex then
131+
return guard.original_newindex(tbl, key, value)
132+
end
133+
rawset(tbl, key, value)
134+
end
135+
setmetatable(object, new_mt)
136+
end
137+
guard.values[hooks_type] = accessor
138+
guard.error_messages[hooks_type] = opts.error_message
139+
end
140+
96141
local function define_hooks(object, hooks_type, preloaded_hook)
97142
local hooks = {}
98143
object[hooks_type .. '_hooks'] = hooks
99144

100-
object[hooks_type] = function(...)
145+
local register_hook = function(...)
101146
local params, fn = ...
102147
if fn == nil then
103148
fn = params
@@ -112,7 +157,7 @@ local function define_hooks(object, hooks_type, preloaded_hook)
112157
params = params or {}
113158
table.insert(hooks, {fn, params})
114159
end
115-
object['_original_' .. hooks_type] = object[hooks_type] -- for leagacy hooks support
160+
set_hook_assignment_guard(object, hooks_type, register_hook)
116161

117162
local function run_preloaded_hooks()
118163
if preloaded_hook == nil then
@@ -160,7 +205,7 @@ local function define_named_hooks(object, hooks_type)
160205
local hooks = {}
161206
object[hooks_type .. '_hooks'] = hooks
162207

163-
object[hooks_type] = function(...)
208+
local register_hook = function(...)
164209
local test_name, params, fn = ...
165210
if fn == nil then
166211
fn = params
@@ -182,6 +227,8 @@ local function define_named_hooks(object, hooks_type)
182227
table.insert(hooks[test_name], {fn, params})
183228
end
184229

230+
set_hook_assignment_guard(object, hooks_type, register_hook)
231+
185232
object['run_' .. hooks_type] = function(test)
186233
local active_hooks = object[hooks_type .. '_hooks']
187234
local test_name = test.name
@@ -214,6 +261,16 @@ function export._define_group_hooks(group)
214261
define_hooks(group, 'before_all', preloaded_hooks.before_all)
215262
define_hooks(group, 'after_all', preloaded_hooks.after_all)
216263

264+
local setup_error = 'Hook \'setup\' is removed. Use \'before_each\' instead'
265+
set_hook_assignment_guard(group, 'setup', function()
266+
error(setup_error)
267+
end, {error_message = setup_error})
268+
269+
local teardown_error = 'Hook \'teardown\' is removed. Use \'after_each\' instead'
270+
set_hook_assignment_guard(group, 'teardown', function()
271+
error(teardown_error)
272+
end, {error_message = teardown_error})
273+
217274
define_named_hooks(group, 'before_test')
218275
define_named_hooks(group, 'after_test')
219276
return group
@@ -228,28 +285,18 @@ end
228285
local function run_group_hooks(runner, group, hooks_type)
229286
local result
230287
local hook = group and group['run_' .. hooks_type]
231-
-- If _original_%hook_name% is not equal to %hook_name%, it means
232-
-- that this method was assigned by user (legacy API).
233-
if hook and group[hooks_type] == group['_original_' .. hooks_type] then
288+
if hook then
234289
result = runner:protected_call(group, hook, group.name .. '.run_before_all_hooks')
235-
elseif group and group[hooks_type] then
236-
result = runner:protected_call(group, group[hooks_type], group.name .. '.before_all')
237290
end
238291
if result and result.status ~= 'success' then
239292
return result
240293
end
241294
end
242295

243-
local function run_test_hooks(self, test, hooks_type, legacy_name)
296+
local function run_test_hooks(self, test, hooks_type)
244297
log.info('Run hook %s', hooks_type)
245298
local group = test.group
246-
local hook
247-
-- Support for group.setup/teardown methods (legacy API)
248-
hook = group[legacy_name]
249-
if hook and type(hook) == 'function' then
250-
self:update_status(test, self:protected_call(group, hook, group.name .. '.' .. legacy_name))
251-
end
252-
hook = group['run_' .. hooks_type]
299+
local hook = group['run_' .. hooks_type]
253300
if hook then
254301
self:update_status(test, self:protected_call(group, hook))
255302
end
@@ -282,7 +329,7 @@ function export._patch_runner(Runner)
282329
break
283330
end
284331

285-
run_test_hooks(self, test, 'before_each', 'setup')
332+
run_test_hooks(self, test, 'before_each')
286333
run_named_test_hooks(self, test, 'before_test')
287334

288335
if test:is('success') then
@@ -292,7 +339,7 @@ function export._patch_runner(Runner)
292339
end
293340

294341
run_named_test_hooks(self, test, 'after_test')
295-
run_test_hooks(self, test, 'after_each', 'teardown')
342+
run_test_hooks(self, test, 'after_each')
296343
end
297344
end end)
298345

luatest/parametrizer.lua

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,19 @@ end
3737

3838
local function redirect_index(group)
3939
local super_group_mt = getmetatable(group)
40-
if super_group_mt.__newindex then
41-
return
42-
end
43-
40+
local origin_newindex = super_group_mt.__newindex
4441
super_group_mt.__newindex = function(_group, key, value)
4542
if _group.pgroups then
4643
for _, pgroup in ipairs(_group.pgroups) do
4744
pgroup[key] = value
4845
end
49-
else
50-
rawset(_group, key, value)
5146
end
47+
48+
if origin_newindex then
49+
return origin_newindex(_group, key, value)
50+
end
51+
52+
rawset(_group, key, value)
5253
end
5354
end
5455

test/capture_test.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ local capture = Capture:new()
88
-- Disable luatest logging to avoid capturing it.
99
require('luatest.log').info = function() end
1010

11-
g.setup = function() capture:enable() end
12-
g.teardown = function()
11+
g.before_each(function()
12+
capture:enable()
13+
end)
14+
15+
g.after_each(function()
1316
capture:flush()
1417
capture:disable()
15-
end
18+
end)
1619

1720
g.before_all(function()
1821
local err

test/capturing_test.lua

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ local capture = Capture:new()
88
-- Disable luatest logging to avoid capturing it.
99
require('luatest.log').info = function() end
1010

11-
g.setup = function() capture:enable() end
12-
g.teardown = function()
11+
g.before_each(function()
12+
capture:enable()
13+
end)
14+
15+
g.after_each(function()
1316
capture:flush()
1417
capture:disable()
15-
end
18+
end)
1619

1720
local function assert_capture_restored()
1821
io.stdout:write('capture-restored')
@@ -67,8 +70,8 @@ g.test_example_failed = function()
6770
-- Don't show captures from group hooks when test failed.
6871
assert_captured(function(lu2)
6972
local group = lu2.group('test')
70-
group.before_all = write_to_io
71-
group.after_all = write_to_io
73+
group.before_all(write_to_io)
74+
group.after_all(write_to_io)
7275
group.test = function() error('custom-error') end
7376
end)
7477

@@ -83,34 +86,34 @@ end
8386
g.test_example_hook = function()
8487
assert_captured(function(lu2)
8588
local group = lu2.group('test')
86-
group.setup = write_to_io
87-
group.teardown = group.setup
89+
group.before_each(write_to_io)
90+
group.after_each(write_to_io)
8891
group.test = function() end
8992
end)
9093
end
9194

9295
g.test_example_hook_failed = function()
9396
assert_shown(function(lu2)
9497
local group = lu2.group('test')
95-
group.setup = function()
98+
group.before_each(function()
9699
write_to_io()
97100
error('test')
98-
end
101+
end)
99102
group.test = function() end
100103
end)
101104

102105
assert_shown(function(lu2)
103106
local group = lu2.group('test')
104-
group.teardown = function()
107+
group.after_each(function()
105108
write_to_io()
106109
error('test')
107-
end
110+
end)
108111
group.test = function() end
109112
end)
110113

111114
assert_shown(function(lu2)
112115
local group = lu2.group('test')
113-
group.setup = write_to_io
116+
group.before_each(write_to_io)
114117
group.test = function() error('test') end
115118
end)
116119
end
@@ -129,33 +132,34 @@ end
129132
g.test_group_hook = function()
130133
assert_captured(function(lu2)
131134
local group = lu2.group('test')
132-
group.before_all = write_to_io
133-
group.after_all = group.before_all
135+
local hook = write_to_io
136+
group.before_all(hook)
137+
group.after_all(hook)
134138
group.test = function() end
135139

136140
local group2 = lu2.group('test2')
137-
group2.before_all = write_to_io
138-
group2.after_all = group2.before_all
141+
group2.before_all(hook)
142+
group2.after_all(hook)
139143
group2.test = function() end
140144
end)
141145
end
142146

143147
g.test_group_hook_failed = function()
144148
assert_shown(function(lu2)
145149
local group = lu2.group('test')
146-
group.before_all = function()
150+
group.before_all(function()
147151
write_to_io()
148152
error('custom-error')
149-
end
153+
end)
150154
group.test = function() end
151155
end)
152156

153157
assert_shown(function(lu2)
154158
local group = lu2.group('test')
155-
group.after_all = function()
159+
group.after_all(function()
156160
write_to_io()
157161
error('custom-error')
158-
end
162+
end)
159163
group.test = function() end
160164
end)
161165
end

0 commit comments

Comments
 (0)