|
3 | 3 | import math |
4 | 4 | from functools import partial |
5 | 5 |
|
| 6 | +import pytest |
| 7 | + |
6 | 8 | from microbenchmark import BenchmarkResult, Scenario, ScenarioGroup |
7 | 9 |
|
8 | 10 |
|
@@ -147,6 +149,71 @@ def test_p99_cached(self) -> None: |
147 | 149 | assert result.p99 is result.p99 |
148 | 150 |
|
149 | 151 |
|
| 152 | +class TestScenarioConstructorValidation: |
| 153 | + def test_number_zero_raises(self) -> None: |
| 154 | + # README: "passing 0 or a negative value raises ValueError" |
| 155 | + with pytest.raises(ValueError): |
| 156 | + Scenario(lambda: None, name='s', number=0) |
| 157 | + |
| 158 | + def test_number_negative_raises(self) -> None: |
| 159 | + with pytest.raises(ValueError): |
| 160 | + Scenario(lambda: None, name='s', number=-1) |
| 161 | + |
| 162 | + def test_args_none_and_empty_list_equivalent(self) -> None: |
| 163 | + # README: "None (the default) and [] both mean the function is called with no arguments" |
| 164 | + calls_none: list[tuple[object, ...]] = [] |
| 165 | + calls_empty: list[tuple[object, ...]] = [] |
| 166 | + |
| 167 | + def fn_none(*a: object) -> None: |
| 168 | + calls_none.append(a) |
| 169 | + |
| 170 | + def fn_empty(*a: object) -> None: |
| 171 | + calls_empty.append(a) |
| 172 | + |
| 173 | + Scenario(fn_none, args=None, name='s', number=1).run() |
| 174 | + Scenario(fn_empty, args=[], name='s', number=1).run() |
| 175 | + assert calls_none == [()] |
| 176 | + assert calls_empty == [()] |
| 177 | + |
| 178 | + def test_args_shallow_copied(self) -> None: |
| 179 | + # README: "The list is shallow-copied on construction" |
| 180 | + original = [1, 2] |
| 181 | + s = Scenario(lambda *_: None, args=original, name='s', number=1) |
| 182 | + original.append(3) |
| 183 | + call_log: list[tuple[object, ...]] = [] |
| 184 | + |
| 185 | + def fn(*a: object) -> None: |
| 186 | + call_log.append(a) |
| 187 | + |
| 188 | + Scenario(fn, args=[1, 2], name='s', number=1).run() |
| 189 | + assert call_log == [(1, 2)] # mutation after construction has no effect |
| 190 | + |
| 191 | + def test_custom_timer(self) -> None: |
| 192 | + # README: "Supply a custom clock to get deterministic measurements in tests" |
| 193 | + tick = [0.0] |
| 194 | + |
| 195 | + def fake_timer() -> float: |
| 196 | + tick[0] += 0.001 |
| 197 | + return tick[0] |
| 198 | + |
| 199 | + result = Scenario(lambda: None, name='s', number=5, timer=fake_timer).run() |
| 200 | + assert len(result.durations) == 5 |
| 201 | + assert all(d > 0 for d in result.durations) |
| 202 | + |
| 203 | + |
| 204 | +class TestPercentileValidation: |
| 205 | + def test_percentile_zero_raises(self) -> None: |
| 206 | + # README: "passing 0 or a value above 100 raises ValueError" |
| 207 | + result = Scenario(lambda: None, name='noop', number=10).run() |
| 208 | + with pytest.raises(ValueError): |
| 209 | + result.percentile(0) |
| 210 | + |
| 211 | + def test_percentile_above_100_raises(self) -> None: |
| 212 | + result = Scenario(lambda: None, name='noop', number=10).run() |
| 213 | + with pytest.raises(ValueError): |
| 214 | + result.percentile(101) |
| 215 | + |
| 216 | + |
150 | 217 | class TestJsonRoundTrip: |
151 | 218 | def test_json_round_trip(self) -> None: |
152 | 219 | result = Scenario(lambda: None, name='noop', number=100).run() |
|
0 commit comments