Skip to content

Commit e53b447

Browse files
Евгений БлиновЕвгений Блинов
authored andcommitted
Add CLI output validation and ScenarioGroup edge cases
1 parent 1f3041c commit e53b447

File tree

4 files changed

+109
-6
lines changed

4 files changed

+109
-6
lines changed

tests/cli/test_scenario_cli.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import re
34
import subprocess
45
import sys
56
import textwrap
@@ -54,7 +55,19 @@ def test_cli_outputs_worst(self) -> None:
5455

5556
def test_cli_output_has_s_suffix(self) -> None:
5657
proc = run_script(scenario_script())
57-
assert 's\n' in proc.stdout or proc.stdout.rstrip().endswith('s')
58+
# Each value line (mean/best/worst) must end with 's'
59+
lines = proc.stdout.strip().splitlines()
60+
for line in lines[1:]: # skip 'benchmark: bench' header
61+
assert line.endswith('s'), f'line does not end with s: {line!r}'
62+
63+
def test_cli_exact_output_format(self) -> None:
64+
proc = run_script(scenario_script())
65+
lines = proc.stdout.strip().splitlines()
66+
assert len(lines) == 4
67+
assert lines[0] == 'benchmark: bench'
68+
assert re.match(r'^mean: \d+\.\d{6}s$', lines[1]), f'unexpected format: {lines[1]!r}'
69+
assert re.match(r'^best: \d+\.\d{6}s$', lines[2]), f'unexpected format: {lines[2]!r}'
70+
assert re.match(r'^worst: \d+\.\d{6}s$', lines[3]), f'unexpected format: {lines[3]!r}'
5871

5972
def test_cli_exit_code_0_by_default(self) -> None:
6073
proc = run_script(scenario_script())

tests/cli/test_scenario_group_cli.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,37 @@ def test_help_mentions_number(self) -> None:
105105
combined = proc.stdout + proc.stderr
106106
assert 'number' in combined.lower()
107107

108+
def test_help_mentions_max_mean(self) -> None:
109+
proc = run_script(group_script(), '--help')
110+
combined = proc.stdout + proc.stderr
111+
assert '--max-mean' in combined
112+
113+
def test_help_does_not_run_benchmark(self) -> None:
114+
proc = run_script(group_script(), '--help')
115+
assert 'benchmark:' not in proc.stdout
116+
117+
118+
def empty_group_script() -> str:
119+
return textwrap.dedent(f'''
120+
import sys
121+
sys.path.insert(0, {str(__import__('pathlib').Path(__file__).parent.parent.parent)!r})
122+
from microbenchmark import ScenarioGroup
123+
124+
group = ScenarioGroup()
125+
group.cli()
126+
''')
127+
128+
129+
class TestScenarioGroupCliEmptyGroup:
130+
def test_empty_group_exits_0(self) -> None:
131+
proc = run_script(empty_group_script())
132+
assert proc.returncode == 0
133+
134+
def test_empty_group_no_output(self) -> None:
135+
proc = run_script(empty_group_script())
136+
assert proc.stdout == ''
137+
assert proc.stderr == ''
138+
108139

109140
def single_scenario_script() -> str:
110141
return textwrap.dedent(f'''

tests/units/test_scenario.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,31 @@ def fn() -> None:
243243
s.run()
244244
assert counter[0] == 3
245245

246+
def test_run_exception_during_warmup_propagates(self) -> None:
247+
counter = [0]
248+
249+
def fn() -> None:
250+
counter[0] += 1
251+
if counter[0] == 2:
252+
raise RuntimeError('fail in warmup')
253+
254+
s = Scenario(fn, name='s', number=5)
255+
with pytest.raises(RuntimeError, match='fail in warmup'):
256+
s.run(warmup=3)
257+
assert counter[0] == 2 # stopped at 2nd warmup call
258+
259+
def test_run_number_one(self) -> None:
260+
tick = [0]
261+
262+
def fake_timer() -> float:
263+
tick[0] += 1
264+
return float(tick[0])
265+
266+
s = Scenario(lambda: None, name='s', number=1, timer=fake_timer)
267+
result = s.run()
268+
assert len(result.durations) == 1
269+
assert result.durations[0] == pytest.approx(1.0) # end(2) - start(1) = 1
270+
246271

247272
class TestScenarioAdd:
248273
def test_add_scenario_returns_group(self) -> None:

tests/units/test_scenario_group.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,59 @@ def test_scenario_plus_scenario(self) -> None:
3232
s1, s2 = make_scenario('s1'), make_scenario('s2')
3333
group = s1 + s2
3434
assert isinstance(group, ScenarioGroup)
35-
assert len(group.run()) == 2
35+
results = group.run()
36+
assert len(results) == 2
37+
assert results[0].scenario is s1
38+
assert results[1].scenario is s2
3639

3740
def test_group_plus_scenario(self) -> None:
3841
s1, s2, s3 = make_scenario('s1'), make_scenario('s2'), make_scenario('s3')
3942
group = ScenarioGroup(s1, s2) + s3
4043
assert isinstance(group, ScenarioGroup)
41-
assert len(group.run()) == 3
44+
results = group.run()
45+
assert len(results) == 3
46+
assert results[0].scenario is s1
47+
assert results[1].scenario is s2
48+
assert results[2].scenario is s3
4249

4350
def test_scenario_plus_group(self) -> None:
4451
s1, s2, s3 = make_scenario('s1'), make_scenario('s2'), make_scenario('s3')
4552
group = s1 + ScenarioGroup(s2, s3)
4653
assert isinstance(group, ScenarioGroup)
47-
assert len(group.run()) == 3
54+
results = group.run()
55+
assert len(results) == 3
56+
assert results[0].scenario is s1
57+
assert results[1].scenario is s2
58+
assert results[2].scenario is s3
4859

4960
def test_group_plus_group(self) -> None:
5061
s1, s2, s3 = make_scenario('s1'), make_scenario('s2'), make_scenario('s3')
5162
group = ScenarioGroup(s1) + ScenarioGroup(s2, s3)
5263
assert isinstance(group, ScenarioGroup)
53-
assert len(group.run()) == 3
64+
results = group.run()
65+
assert len(results) == 3
66+
assert results[0].scenario is s1
67+
assert results[1].scenario is s2
68+
assert results[2].scenario is s3
5469

5570
def test_triple_sum_is_flat(self) -> None:
5671
s1, s2, s3 = make_scenario('s1'), make_scenario('s2'), make_scenario('s3')
5772
group = s1 + s2 + s3
5873
assert isinstance(group, ScenarioGroup)
59-
assert len(group.run()) == 3
74+
results = group.run()
75+
assert len(results) == 3
76+
assert results[0].scenario is s1
77+
assert results[1].scenario is s2
78+
assert results[2].scenario is s3
6079

6180
def test_add_returns_new_group(self) -> None:
6281
s1, s2 = make_scenario('s1'), make_scenario('s2')
6382
g = ScenarioGroup(s1)
6483
new_g = g + s2
6584
assert new_g is not g
6685
assert len(g._scenarios) == 1 # original not mutated
86+
assert new_g._scenarios[0] is s1
87+
assert new_g._scenarios[1] is s2
6788

6889
def test_add_unknown_type_returns_not_implemented(self) -> None:
6990
g = ScenarioGroup()
@@ -124,6 +145,19 @@ def test_empty_group_run_with_warmup(self) -> None:
124145
g = ScenarioGroup()
125146
assert g.run(warmup=10) == []
126147

148+
def test_run_negative_warmup_acts_as_zero(self) -> None:
149+
counter = [0]
150+
151+
def fn() -> None:
152+
counter[0] += 1
153+
154+
s = Scenario(fn, name='s', number=3)
155+
g = ScenarioGroup(s)
156+
results = g.run(warmup=-5)
157+
assert len(results) == 1
158+
assert len(results[0].durations) == 3
159+
assert counter[0] == 3 # range(-5) == empty, so no warmup calls
160+
127161
def test_run_returns_benchmark_results(self) -> None:
128162
s = make_scenario()
129163
g = ScenarioGroup(s)

0 commit comments

Comments
 (0)