Skip to content

Commit e1c4371

Browse files
winpationelmc
authored andcommitted
Add support for teardown handler to pedantic mode
Sometimes benchmarks have side effects which need to be cleaned up after every round. For example if a benchmark writes to a file, then you might want to delete it in between rounds and start from a clean slate. It's already possible to pass a setup function to pedantic mode, this PR introduces a similar mechanism but for cleaning up resources after a round has been completed by passing a teardown function.
1 parent fcc60e0 commit e1c4371

File tree

5 files changed

+109
-4
lines changed

5 files changed

+109
-4
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ Authors
3636
* Johnny Huang - https://github.com/jnhyperion
3737
* Tony Kuo - https://github.com/tony92151
3838
* Eugeniy - https://github.com/zcoder
39+
* Patrick Winter - https://github.com/winpat

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Changelog
33
=========
44

5+
Unreleased
6+
----------
7+
8+
* Add support for a per-round ``teardown`` function to pedantic mode.
9+
510
5.1.0 (2024-10-30)
611
------------------
712

docs/pedantic.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Reference
2424
:param kwargs: Named arguments to the ``target`` function.
2525

2626
:type setup: callable
27-
:param setup: A function to call right before calling the ``target`` function.
27+
:param setup: A function to call right before calling the ``target`` function in the first iteration of every round.
2828

2929
The setup function can also return the arguments for the function (in case you need to create new arguments every time).
3030

@@ -43,6 +43,24 @@ Reference
4343

4444
if you use a ``setup`` function then you cannot use the ``args``, ``kwargs`` and ``iterations`` options.
4545

46+
:type teardown: callable
47+
:param teardown: A function to call after the last iteration of every round.
48+
49+
.. sourcecode:: python
50+
51+
def stuff(a, b, c, foo):
52+
pass
53+
54+
def test_with_teardown(benchmark):
55+
def teardown():
56+
# cleanup the side effect of the previous bench mark round.
57+
pass
58+
benchmark.pedantic(stuff, teardown=teardown, rounds=100)
59+
60+
.. note::
61+
62+
the ``teardown`` function receives the same ``args`` and ``kwargs`` as the ``target``.
63+
4664
:type rounds: int
4765
:param rounds: Number of rounds to run.
4866

src/pytest_benchmark/fixture.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,21 @@ def __call__(self, function_to_benchmark, *args, **kwargs):
158158
self.has_error = True
159159
raise
160160

161-
def pedantic(self, target, args=(), kwargs=None, setup=None, rounds=1, warmup_rounds=0, iterations=1):
161+
def pedantic(self, target, args=(), kwargs=None, setup=None, teardown=None, rounds=1, warmup_rounds=0, iterations=1):
162162
if self._mode:
163163
self.has_error = True
164164
raise FixtureAlreadyUsed(f'Fixture can only be used once. Previously it was used in {self._mode} mode.')
165165
try:
166166
self._mode = 'benchmark.pedantic(...)'
167167
return self._raw_pedantic(
168-
target, args=args, kwargs=kwargs, setup=setup, rounds=rounds, warmup_rounds=warmup_rounds, iterations=iterations
168+
target,
169+
args=args,
170+
kwargs=kwargs,
171+
setup=setup,
172+
teardown=teardown,
173+
rounds=rounds,
174+
warmup_rounds=warmup_rounds,
175+
iterations=iterations,
169176
)
170177
except Exception:
171178
self.has_error = True
@@ -209,7 +216,7 @@ def _raw(self, function_to_benchmark, *args, **kwargs):
209216
function_result = function_to_benchmark(*args, **kwargs)
210217
return function_result
211218

212-
def _raw_pedantic(self, target, args=(), kwargs=None, setup=None, rounds=1, warmup_rounds=0, iterations=1):
219+
def _raw_pedantic(self, target, args=(), kwargs=None, setup=None, teardown=None, rounds=1, warmup_rounds=0, iterations=1):
213220
if kwargs is None:
214221
kwargs = {}
215222

@@ -248,6 +255,9 @@ def make_arguments(args=args, kwargs=kwargs):
248255
runner = self._make_runner(target, args, kwargs)
249256
runner(loops_range)
250257

258+
if teardown is not None:
259+
teardown(*args, **kwargs)
260+
251261
for _ in range(rounds):
252262
args, kwargs = make_arguments()
253263

@@ -258,10 +268,15 @@ def make_arguments(args=args, kwargs=kwargs):
258268
duration, result = runner(loops_range)
259269
stats.update(duration)
260270

271+
if teardown is not None:
272+
teardown(*args, **kwargs)
273+
261274
if loops_range:
262275
# if it has been looped then we don't have the result, we need to do 1 extra run for it
263276
args, kwargs = make_arguments()
264277
result = target(*args, **kwargs)
278+
if teardown is not None:
279+
teardown(*args, **kwargs)
265280

266281
if self.cprofile:
267282
if self.cprofile_loops is None:
@@ -273,6 +288,8 @@ def make_arguments(args=args, kwargs=kwargs):
273288
args, kwargs = make_arguments()
274289
for _ in cprofile_loops:
275290
profile.runcall(target, *args, **kwargs)
291+
if teardown is not None:
292+
teardown(*args, **kwargs)
276293
self._save_cprofile(profile)
277294

278295
return result

tests/test_pedantic.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ def setup():
2222
assert runs == [(1, 2)]
2323

2424

25+
def test_teardown(benchmark):
26+
runs = []
27+
28+
def stuff(foo, bar=1234):
29+
runs.append((foo, bar))
30+
31+
def teardown(foo, bar=1234):
32+
assert foo == 1
33+
assert bar == 2
34+
runs.append('teardown')
35+
36+
benchmark.pedantic(stuff, args=[1], kwargs={'bar': 2}, teardown=teardown)
37+
assert runs == [(1, 2), 'teardown']
38+
39+
2540
@pytest.mark.benchmark(cprofile=True)
2641
def test_setup_cprofile(benchmark):
2742
runs = []
@@ -36,6 +51,22 @@ def setup():
3651
assert runs == [(1, 2), (1, 2)]
3752

3853

54+
@pytest.mark.benchmark(cprofile=True)
55+
def test_teardown_cprofile(benchmark):
56+
runs = []
57+
58+
def stuff():
59+
runs.append('stuff')
60+
61+
def teardown():
62+
runs.append('teardown')
63+
64+
benchmark.pedantic(stuff, teardown=teardown)
65+
assert runs == ['stuff', 'teardown', 'stuff', 'teardown']
66+
67+
runs = []
68+
69+
3970
def test_args_kwargs(benchmark):
4071
runs = []
4172

@@ -101,6 +132,39 @@ def setup():
101132
assert runs == [(1, 2)] * 10
102133

103134

135+
def test_teardown_many_rounds(benchmark):
136+
runs = []
137+
138+
def stuff():
139+
runs.append('stuff')
140+
141+
def teardown():
142+
runs.append('teardown')
143+
144+
benchmark.pedantic(stuff, teardown=teardown, rounds=10)
145+
assert runs == ['stuff', 'teardown'] * 10
146+
147+
148+
def test_teardown_many_iterations(benchmark):
149+
runs = []
150+
151+
def stuff():
152+
runs.append('stuff')
153+
154+
def teardown():
155+
runs.append('teardown')
156+
157+
benchmark.pedantic(stuff, teardown=teardown, iterations=3)
158+
assert runs == [
159+
'stuff',
160+
'stuff',
161+
'stuff',
162+
'teardown', # first round
163+
'stuff',
164+
'teardown', # computing the final result
165+
]
166+
167+
104168
def test_cant_use_both_args_and_setup_with_return(benchmark):
105169
runs = []
106170

0 commit comments

Comments
 (0)