Skip to content

Commit 6c492e0

Browse files
committed
Add finalize_teardown method to clean up if setup or teardown fail
If the `setup` or `teardown` methods fail, the tests session waits for timeout (1800s per benchmark!) provided a sub process had been created beforehand. This lead to excruciatingly long test sessions, appearing fully stuck.
1 parent b5a83cc commit 6c492e0

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

asv/benchmark.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ def __init__(self, name, func, attr_sources):
442442
self.pretty_name = getattr(func, "pretty_name", None)
443443
self._attr_sources = attr_sources
444444
self._setups = list(_get_all_attrs(attr_sources, 'setup', True))[::-1]
445+
self._finalize_teardowns = list(_get_all_attrs(attr_sources, 'finalize_teardown', True))
445446
self._teardowns = list(_get_all_attrs(attr_sources, 'teardown', True))
446447
self._setup_cache = _get_first_attr(attr_sources, 'setup_cache', None)
447448
self.setup_cache_key = get_setup_cache_key(self._setup_cache)
@@ -590,6 +591,11 @@ def method_caller():
590591
code, {'run': self.func, 'params': self._current_params},
591592
{}, filename)
592593

594+
def do_finalize_teardown(self, exc_type, exception, trace):
595+
"""Called if the setup or the teardown of any test raise an exception.
596+
"""
597+
for finalize_teardown in self._finalize_teardowns:
598+
finalize_teardown(exc_type, exception, trace, self._current_params)
593599

594600
class TimeBenchmark(Benchmark):
595601
"""
@@ -1151,17 +1157,22 @@ def main_run(args):
11511157
if cache is not None:
11521158
benchmark.insert_param(cache)
11531159

1154-
skip = benchmark.do_setup()
1155-
11561160
try:
1157-
if skip:
1158-
result = float('nan')
1159-
else:
1160-
result = benchmark.do_run()
1161-
if profile_path is not None:
1162-
benchmark.do_profile(profile_path)
1161+
skip = benchmark.do_setup()
1162+
1163+
try:
1164+
if skip:
1165+
result = float('nan')
1166+
else:
1167+
result = benchmark.do_run()
1168+
if profile_path is not None:
1169+
benchmark.do_profile(profile_path)
1170+
finally:
1171+
benchmark.do_teardown()
11631172
finally:
1164-
benchmark.do_teardown()
1173+
exc_type, exc, trace = sys.exc_info()
1174+
benchmark.do_finalize_teardown(exc_type, exc, trace)
1175+
raise exc
11651176

11661177
# Write the output value
11671178
with open(result_file, 'w') as fp:

docs/source/writing_benchmarks.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,29 @@ The ``setup_cache`` timeout can be specified by setting the
170170
``.timeout`` attribute of the ``setup_cache`` function. The default
171171
value is the maximum of the timeouts of the benchmarks using it.
172172

173+
If the ``setup`` or ``teardown`` of the test function raise an
174+
exception, the ``finalize_teardown`` method (if it exists) will be
175+
called with the exception as first argument, and the current
176+
benchmark parameters as second. The exception is then re-raised.
177+
178+
This method gives you a chance to clean up any side-effects (or
179+
shared state) of the suite::
180+
181+
class Suite:
182+
def setup(self, *args, **kwargs):
183+
if not hasattr(self, '_server'):
184+
self._server = MySuperServer()
185+
self._server.launch()
186+
# other code
187+
188+
def finalize_teardown(self, exc_info, current_params):
189+
if hasattr(self, '_server'):
190+
self._server.stop()
191+
192+
Had the ``finalize_teardown`` not stopped the server, ASV would have
193+
waited until the ``setup`` timeout to go to the next benchmark, since
194+
a running process was still attached.
195+
173196
.. _benchmark-attributes:
174197

175198
Benchmark attributes

test/benchmark/finalize_teardown.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
3+
4+
5+
class FinalizeTeardownSetupTest:
6+
def setup(self, n):
7+
raise ValueError("Setup fails")
8+
9+
def time_it(self, n):
10+
pass
11+
12+
def finalize_teardown(self, exc_type, exc, trace):
13+
assert exc_type == ValueError
14+
assert str(exc) == "Setup fails"
15+
assert trace is not None
16+
17+
18+
class FinalizeTeardownTeardownTest:
19+
def teardown(self, n):
20+
raise NotImplementedError("Teardown fails")
21+
22+
def time_it(self, n):
23+
pass
24+
25+
def finalize_teardown(self, exc_type, exc, trace):
26+
assert exc_type == NotImplementedError
27+
assert str(exc) == "Teardown fails"
28+
assert trace is not None

0 commit comments

Comments
 (0)