Skip to content

Commit faaa395

Browse files
Евгений БлиновЕвгений Блинов
authored andcommitted
Tests for CLI
1 parent 1aef2dd commit faaa395

2 files changed

Lines changed: 222 additions & 0 deletions

File tree

tests/cli/test_scenario_cli.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import sys
5+
import textwrap
6+
7+
8+
def run_script(script: str, *args: str, timeout: int = 30) -> subprocess.CompletedProcess[str]:
9+
"""Run an inline Python script as a subprocess and return the completed process."""
10+
return subprocess.run(
11+
[sys.executable, '-c', script, *args],
12+
capture_output=True,
13+
text=True,
14+
encoding='utf-8',
15+
timeout=timeout,
16+
check=False,
17+
)
18+
19+
20+
def scenario_script(extra: str = '') -> str:
21+
"""Return a self-contained script that creates a Scenario and calls cli()."""
22+
return textwrap.dedent(f'''
23+
import sys
24+
sys.path.insert(0, {str(__import__('pathlib').Path(__file__).parent.parent.parent)!r})
25+
from microbenchmark import Scenario
26+
27+
tick = [0.0]
28+
def fake_timer():
29+
tick[0] += 0.001
30+
return tick[0]
31+
32+
s = Scenario(lambda: None, name='bench', number=10, timer=fake_timer)
33+
{extra}
34+
s.cli()
35+
''')
36+
37+
38+
class TestScenarioCliOutput:
39+
def test_cli_outputs_name(self) -> None:
40+
proc = run_script(scenario_script())
41+
assert 'benchmark: bench' in proc.stdout
42+
43+
def test_cli_outputs_mean(self) -> None:
44+
proc = run_script(scenario_script())
45+
assert 'mean:' in proc.stdout
46+
47+
def test_cli_outputs_best(self) -> None:
48+
proc = run_script(scenario_script())
49+
assert 'best:' in proc.stdout
50+
51+
def test_cli_outputs_worst(self) -> None:
52+
proc = run_script(scenario_script())
53+
assert 'worst:' in proc.stdout
54+
55+
def test_cli_output_has_s_suffix(self) -> None:
56+
proc = run_script(scenario_script())
57+
assert 's\n' in proc.stdout or proc.stdout.rstrip().endswith('s')
58+
59+
def test_cli_exit_code_0_by_default(self) -> None:
60+
proc = run_script(scenario_script())
61+
assert proc.returncode == 0
62+
63+
def test_cli_writes_to_stdout(self) -> None:
64+
proc = run_script(scenario_script())
65+
assert proc.stdout.strip() != ''
66+
assert proc.stderr == ''
67+
68+
69+
class TestScenarioCliNumberArg:
70+
def test_number_arg_changes_durations_count(self) -> None:
71+
# We can't directly inspect durations from subprocess output,
72+
# but we can verify the CLI accepts --number without error
73+
proc = run_script(scenario_script(), '--number', '5')
74+
assert proc.returncode == 0
75+
assert 'benchmark: bench' in proc.stdout
76+
77+
def test_number_arg_default_uses_scenario_number(self) -> None:
78+
proc = run_script(scenario_script())
79+
assert proc.returncode == 0
80+
81+
82+
class TestScenarioCliMaxMean:
83+
def test_max_mean_below_threshold_exit_0(self) -> None:
84+
# fake_timer increments by 0.001 each call, so mean per run = 0.001
85+
proc = run_script(scenario_script(), '--max-mean', '1.0')
86+
assert proc.returncode == 0
87+
88+
def test_max_mean_above_threshold_exit_1(self) -> None:
89+
# mean will be ~0.001s; threshold 0.000001 is far below
90+
proc = run_script(scenario_script(), '--max-mean', '0.000001')
91+
assert proc.returncode == 1
92+
93+
def test_max_mean_still_prints_output(self) -> None:
94+
proc = run_script(scenario_script(), '--max-mean', '0.000001')
95+
assert 'benchmark: bench' in proc.stdout
96+
97+
def test_max_mean_equal_threshold_exit_0(self) -> None:
98+
# mean = exactly threshold → should pass (mean <= threshold)
99+
# With fake_timer: each call delta = 0.001, number=10
100+
# mean = 0.001. Set --max-mean = 10 to ensure it passes.
101+
proc = run_script(scenario_script(), '--max-mean', '10')
102+
assert proc.returncode == 0
103+
104+
105+
class TestScenarioCliHelp:
106+
def test_help_exits_0(self) -> None:
107+
proc = run_script(scenario_script(), '--help')
108+
assert proc.returncode == 0
109+
110+
def test_help_output_not_empty(self) -> None:
111+
proc = run_script(scenario_script(), '--help')
112+
combined = proc.stdout + proc.stderr
113+
assert len(combined) > 0
114+
115+
def test_help_mentions_number(self) -> None:
116+
proc = run_script(scenario_script(), '--help')
117+
combined = proc.stdout + proc.stderr
118+
assert 'number' in combined.lower()
119+
120+
def test_help_mentions_max_mean(self) -> None:
121+
proc = run_script(scenario_script(), '--help')
122+
combined = proc.stdout + proc.stderr
123+
assert 'max-mean' in combined.lower() or 'max_mean' in combined.lower()
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import sys
5+
import textwrap
6+
7+
8+
def run_script(script: str, *args: str, timeout: int = 30) -> subprocess.CompletedProcess[str]:
9+
return subprocess.run(
10+
[sys.executable, '-c', script, *args],
11+
capture_output=True,
12+
text=True,
13+
encoding='utf-8',
14+
timeout=timeout,
15+
check=False,
16+
)
17+
18+
19+
def group_script(extra: str = '') -> str:
20+
return textwrap.dedent(f'''
21+
import sys
22+
sys.path.insert(0, {str(__import__('pathlib').Path(__file__).parent.parent.parent)!r})
23+
from microbenchmark import Scenario, ScenarioGroup
24+
25+
tick = [0.0]
26+
def fake_timer():
27+
tick[0] += 0.001
28+
return tick[0]
29+
30+
s1 = Scenario(lambda: None, name='first', number=5, timer=fake_timer)
31+
s2 = Scenario(lambda: None, name='second', number=5, timer=fake_timer)
32+
group = s1 + s2
33+
{extra}
34+
group.cli()
35+
''')
36+
37+
38+
class TestScenarioGroupCliOutput:
39+
def test_outputs_both_scenario_names(self) -> None:
40+
proc = run_script(group_script())
41+
assert 'benchmark: first' in proc.stdout
42+
assert 'benchmark: second' in proc.stdout
43+
44+
def test_results_separated_by_divider(self) -> None:
45+
proc = run_script(group_script())
46+
assert '---' in proc.stdout
47+
48+
def test_divider_between_not_after_last(self) -> None:
49+
proc = run_script(group_script())
50+
lines = proc.stdout.strip().splitlines()
51+
# last line should NOT be ---
52+
assert lines[-1] != '---'
53+
54+
def test_exit_code_0_by_default(self) -> None:
55+
proc = run_script(group_script())
56+
assert proc.returncode == 0
57+
58+
def test_outputs_mean_best_worst_for_each(self) -> None:
59+
proc = run_script(group_script())
60+
assert proc.stdout.count('mean:') == 2
61+
assert proc.stdout.count('best:') == 2
62+
assert proc.stdout.count('worst:') == 2
63+
64+
def test_writes_to_stdout(self) -> None:
65+
proc = run_script(group_script())
66+
assert proc.stdout.strip() != ''
67+
assert proc.stderr == ''
68+
69+
70+
class TestScenarioGroupCliNumberArg:
71+
def test_number_arg_accepted(self) -> None:
72+
proc = run_script(group_script(), '--number', '3')
73+
assert proc.returncode == 0
74+
assert 'benchmark: first' in proc.stdout
75+
76+
77+
class TestScenarioGroupCliMaxMean:
78+
def test_max_mean_passes_when_below(self) -> None:
79+
proc = run_script(group_script(), '--max-mean', '10.0')
80+
assert proc.returncode == 0
81+
82+
def test_max_mean_fails_when_any_exceeds(self) -> None:
83+
proc = run_script(group_script(), '--max-mean', '0.000001')
84+
assert proc.returncode == 1
85+
86+
def test_max_mean_still_prints_output_on_failure(self) -> None:
87+
proc = run_script(group_script(), '--max-mean', '0.000001')
88+
assert 'benchmark:' in proc.stdout
89+
90+
91+
class TestScenarioGroupCliHelp:
92+
def test_help_exits_0(self) -> None:
93+
proc = run_script(group_script(), '--help')
94+
assert proc.returncode == 0
95+
96+
def test_help_mentions_number(self) -> None:
97+
proc = run_script(group_script(), '--help')
98+
combined = proc.stdout + proc.stderr
99+
assert 'number' in combined.lower()

0 commit comments

Comments
 (0)