pytest-benchmem 0.4.2__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/dependabot.yml +3 -3
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/ci.yaml +7 -7
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/docs.yaml +2 -2
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/nightly.yaml +1 -1
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/release.yaml +1 -1
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.gitignore +0 -3
- pytest_benchmem-0.4.3/.release-please-manifest.json +3 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/CHANGELOG.md +7 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/PKG-INFO +1 -1
- pytest_benchmem-0.4.3/docs/changelog.md +9 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/mkdocs.yml +5 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/pyproject.toml +2 -1
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/cli.py +14 -4
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/compare.py +21 -13
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_cli.py +17 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_compare.py +32 -0
- pytest_benchmem-0.4.3/uv.lock +2441 -0
- pytest_benchmem-0.4.2/.release-please-manifest.json +0 -3
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.github/workflows/pr-title.yaml +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.pre-commit-config.yaml +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.readthedocs.yaml +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/.release-please-config.json +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/LICENSE +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/README.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/compare-plot.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/dims.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/getting-started.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/index.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/javascripts/notebook-prompts.js +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/metrics.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/reference.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/stylesheets/extra.css +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs/sweeps.md +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/docs_macros.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/__init__.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/combined.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/format.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/memray.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/plotting.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/py.typed +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/pytest_plugin.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/snapshot.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/sweep.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/src/pytest_benchmem/tables.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/__init__.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/conftest.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_combined.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_distribution.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_inline_compare.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_memray.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_memray_platform.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_plotting.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_plugin.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_snapshot.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_sweep.py +0 -0
- {pytest_benchmem-0.4.2 → pytest_benchmem-0.4.3}/tests/test_tables.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
version: 2
|
|
2
2
|
|
|
3
3
|
updates:
|
|
4
|
-
- package-ecosystem: "
|
|
4
|
+
- package-ecosystem: "uv"
|
|
5
5
|
directory: "/"
|
|
6
6
|
schedule:
|
|
7
|
-
interval: "
|
|
7
|
+
interval: "monthly"
|
|
8
8
|
labels:
|
|
9
9
|
- "dependencies"
|
|
10
10
|
commit-message:
|
|
@@ -27,7 +27,7 @@ updates:
|
|
|
27
27
|
- package-ecosystem: "github-actions"
|
|
28
28
|
directory: "/"
|
|
29
29
|
schedule:
|
|
30
|
-
interval: "
|
|
30
|
+
interval: "monthly"
|
|
31
31
|
labels:
|
|
32
32
|
- "dependencies"
|
|
33
33
|
commit-message:
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
runs-on: ubuntu-24.04
|
|
17
17
|
timeout-minutes: 5
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
20
|
- uses: astral-sh/setup-uv@v7
|
|
21
21
|
with:
|
|
22
22
|
enable-cache: true
|
|
23
|
-
- run: uv sync --group dev
|
|
23
|
+
- run: uv sync --group dev --frozen
|
|
24
24
|
- run: uv run ruff check --output-format=github
|
|
25
25
|
- run: uv run ruff format --diff
|
|
26
26
|
|
|
@@ -29,11 +29,11 @@ jobs:
|
|
|
29
29
|
runs-on: ubuntu-24.04
|
|
30
30
|
timeout-minutes: 5
|
|
31
31
|
steps:
|
|
32
|
-
- uses: actions/checkout@
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
33
|
- uses: astral-sh/setup-uv@v7
|
|
34
34
|
with:
|
|
35
35
|
enable-cache: true
|
|
36
|
-
- run: uv sync --group dev
|
|
36
|
+
- run: uv sync --group dev --frozen
|
|
37
37
|
- run: uv run mypy src/pytest_benchmem tests
|
|
38
38
|
|
|
39
39
|
test:
|
|
@@ -49,14 +49,14 @@ jobs:
|
|
|
49
49
|
- os: macos-latest
|
|
50
50
|
python-version: "3.13"
|
|
51
51
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
52
|
+
- uses: actions/checkout@v7
|
|
53
53
|
- uses: astral-sh/setup-uv@v7
|
|
54
54
|
with:
|
|
55
55
|
enable-cache: true
|
|
56
56
|
- uses: actions/setup-python@v6
|
|
57
57
|
with:
|
|
58
58
|
python-version: ${{ matrix.python-version }}
|
|
59
|
-
- run: uv sync --group dev
|
|
59
|
+
- run: uv sync --group dev --frozen
|
|
60
60
|
- run: uv run pytest -q
|
|
61
61
|
|
|
62
62
|
smoke-install:
|
|
@@ -64,7 +64,7 @@ jobs:
|
|
|
64
64
|
runs-on: ubuntu-24.04
|
|
65
65
|
timeout-minutes: 10
|
|
66
66
|
steps:
|
|
67
|
-
- uses: actions/checkout@
|
|
67
|
+
- uses: actions/checkout@v7
|
|
68
68
|
- uses: astral-sh/setup-uv@v7
|
|
69
69
|
with:
|
|
70
70
|
enable-cache: true
|
|
@@ -18,11 +18,11 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-24.04
|
|
19
19
|
timeout-minutes: 10
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
22
|
- uses: astral-sh/setup-uv@v7
|
|
23
23
|
with:
|
|
24
24
|
enable-cache: true
|
|
25
25
|
# Build the notebooks from their jupytext sources, then build+execute the site.
|
|
26
|
-
- run: uv sync --group docs
|
|
26
|
+
- run: uv sync --group docs --frozen
|
|
27
27
|
- run: uv run jupytext --to ipynb docs/compare-plot.md
|
|
28
28
|
- run: uv run mkdocs gh-deploy --force
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
name: pypi
|
|
50
50
|
url: https://pypi.org/project/pytest-benchmem
|
|
51
51
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
52
|
+
- uses: actions/checkout@v7
|
|
53
53
|
with:
|
|
54
54
|
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }}
|
|
55
55
|
fetch-depth: 0
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.3](https://github.com/fluxopt/pytest-benchmem/compare/v0.4.2...v0.4.3) (2026-06-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* benchmem compare accepts a single run (plain table) ([#123](https://github.com/fluxopt/pytest-benchmem/issues/123)) ([96275d9](https://github.com/fluxopt/pytest-benchmem/commit/96275d93628323ffd4f3cc74decdebdcb132004e))
|
|
9
|
+
|
|
3
10
|
## [0.4.2](https://github.com/fluxopt/pytest-benchmem/compare/v0.4.1...v0.4.2) (2026-06-18)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-benchmem
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: The memory companion to pytest-benchmark: a memray peak-memory pass on the same test, plus dims-aware plots and cross-version sweeps.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fluxopt/pytest-benchmem
|
|
6
6
|
Project-URL: Documentation, https://fluxopt.github.io/pytest-benchmem/
|
|
@@ -39,6 +39,7 @@ nav:
|
|
|
39
39
|
- Grouping by dims: dims.md
|
|
40
40
|
- Cross-version sweeps: sweeps.md
|
|
41
41
|
- Reference: reference.md
|
|
42
|
+
- Release notes: changelog.md
|
|
42
43
|
|
|
43
44
|
plugins:
|
|
44
45
|
- search
|
|
@@ -93,6 +94,10 @@ markdown_extensions:
|
|
|
93
94
|
anchor_linenums: true
|
|
94
95
|
use_pygments: true
|
|
95
96
|
- pymdownx.inlinehilite
|
|
97
|
+
- pymdownx.snippets:
|
|
98
|
+
# Pull docs/changelog.md's `--8<-- "CHANGELOG.md"` from the repo root.
|
|
99
|
+
base_path: ["."]
|
|
100
|
+
check_paths: true
|
|
96
101
|
- pymdownx.superfences
|
|
97
102
|
- pymdownx.details
|
|
98
103
|
- pymdownx.tabbed:
|
|
@@ -48,7 +48,8 @@ build-backend = "hatchling.build"
|
|
|
48
48
|
source = "vcs"
|
|
49
49
|
|
|
50
50
|
[dependency-groups]
|
|
51
|
-
|
|
51
|
+
# mypy <2.1: 2.1.0 errors on numpy's PEP 695 stub under python_version=3.11. Lift once fixed upstream.
|
|
52
|
+
dev = ["ruff", "mypy<2.2", "pre-commit", "pytest-benchmem[plot]", { include-group = "docs" }]
|
|
52
53
|
docs = [
|
|
53
54
|
"jupytext",
|
|
54
55
|
"ipykernel",
|
|
@@ -188,7 +188,10 @@ def plot(
|
|
|
188
188
|
def compare(
|
|
189
189
|
runs: Annotated[
|
|
190
190
|
list[Path],
|
|
191
|
-
typer.Argument(
|
|
191
|
+
typer.Argument(
|
|
192
|
+
help="One or more pytest-benchmark runs, oldest → newest. One prints a plain "
|
|
193
|
+
"table; two or more compare (a sweep is N)."
|
|
194
|
+
),
|
|
192
195
|
],
|
|
193
196
|
columns: Annotated[
|
|
194
197
|
str | None,
|
|
@@ -233,9 +236,16 @@ def compare(
|
|
|
233
236
|
),
|
|
234
237
|
] = None,
|
|
235
238
|
) -> None:
|
|
236
|
-
"""Print a per-id
|
|
237
|
-
if
|
|
238
|
-
raise _fail("compare needs at least
|
|
239
|
+
"""Print a per-id table for one run, or compare two or more (and optionally gate CI)."""
|
|
240
|
+
if not runs:
|
|
241
|
+
raise _fail("compare needs at least one run", 2)
|
|
242
|
+
if fail_on and len(runs) < 2: # noqa: PLR2004 — a growth gate needs a before and an after
|
|
243
|
+
raise _fail(
|
|
244
|
+
"compare --fail-on needs at least two runs — it gates growth of the first run vs "
|
|
245
|
+
"the last. For an absolute ceiling on a single run, use the "
|
|
246
|
+
"@pytest.mark.benchmem(max_peak=...) marker instead.",
|
|
247
|
+
2,
|
|
248
|
+
)
|
|
239
249
|
_require_runs_exist(runs, suggest=False)
|
|
240
250
|
from pytest_benchmem.compare import compare_runs, find_regressions, parse_threshold
|
|
241
251
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
"""Text
|
|
1
|
+
"""Text table for one or more pytest-benchmark runs — keyed on the benchmark id.
|
|
2
2
|
|
|
3
|
-
:func:`compare_runs` prints the
|
|
3
|
+
:func:`compare_runs` prints the table, modelled on pytest-benchmark's own:
|
|
4
4
|
rows are one per ``(benchmark × run)``, columns are ``metric × stat`` (``--columns``
|
|
5
5
|
picks the metrics, default ``time,peak``; ``--stat`` picks the stat, default ``all`` for
|
|
6
|
-
the full min/max/mean/median/stddev spread),
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
the full min/max/mean/median/stddev spread), and ``group_by`` splits the rows into
|
|
7
|
+
sub-tables. With **two or more** runs every cell carries a relative ``(N.NN)`` multiplier
|
|
8
|
+
vs its column's best (best green, worst red); a **single** run is a plain readout — there's
|
|
9
|
+
no other run to rank against, so the multiplier and colour are dropped.
|
|
9
10
|
|
|
10
11
|
The rest is the regression gate behind ``benchmem compare --fail-on``: parse a threshold
|
|
11
12
|
like ``peak:10%`` / ``peak:5MiB`` / ``allocations:5%`` / ``time:5%``, find the ids whose
|
|
@@ -227,15 +228,15 @@ def compare_runs(
|
|
|
227
228
|
csv: Path | None = None,
|
|
228
229
|
out: TextIO | None = None,
|
|
229
230
|
) -> None:
|
|
230
|
-
"""Print a per-(benchmark, run)
|
|
231
|
+
"""Print a per-(benchmark, run) table for one or more runs.
|
|
231
232
|
|
|
232
233
|
Mirrors pytest-benchmark's table model: rows are one per ``(benchmark × run)``,
|
|
233
234
|
``columns`` selects which metric columns appear (``time`` / ``peak`` / ``allocated``
|
|
234
|
-
/ ``allocations``)
|
|
235
|
-
vs the group's best (best green, worst red)
|
|
236
|
-
|
|
237
|
-
``param:NAME``, comma-composable). With
|
|
238
|
-
table.
|
|
235
|
+
/ ``allocations``). With two or more runs each cell also carries a relative ``(N.NN)``
|
|
236
|
+
multiplier vs the group's best (best green, worst red); a single run is a plain readout
|
|
237
|
+
with neither. ``group_by`` splits the rows into sub-tables (``fullname`` / ``name`` /
|
|
238
|
+
``func`` / ``group`` / ``module`` / ``class`` / ``param:NAME``, comma-composable). With
|
|
239
|
+
no ``group_by`` the whole comparison is one table.
|
|
239
240
|
|
|
240
241
|
Every column is a ``metric × stat`` pair. ``columns`` is a comma list of metric names
|
|
241
242
|
(default ``time,peak``); ``stat`` is one of ``min`` / ``max`` / ``mean`` / ``median`` /
|
|
@@ -254,11 +255,16 @@ def compare_runs(
|
|
|
254
255
|
raise ValueError(f"unknown --sort {sort!r}; use one of {', '.join(_SORTS)}")
|
|
255
256
|
metrics, stats = _resolve_columns(columns), _resolve_stats(stat)
|
|
256
257
|
labels, values, units, dims = _load_columns(runs, metrics, stats)
|
|
257
|
-
if
|
|
258
|
+
if not labels:
|
|
259
|
+
raise ValueError("no benchmarks found in the given run(s)")
|
|
260
|
+
if len(labels) < 2 and len(runs) > 1: # noqa: PLR2004 — distinct paths collapsed to one series
|
|
258
261
|
raise ValueError(
|
|
259
262
|
f"compare needs at least two distinct runs; got {labels!r} "
|
|
260
263
|
f"(the same file more than once?). Pass distinct files."
|
|
261
264
|
)
|
|
265
|
+
# One run is a plain readout: with no other run to rank against, the (N.NN) multiplier and
|
|
266
|
+
# the best/worst colour are meaningless (best == worst == the value), so they're dropped.
|
|
267
|
+
single = len(labels) == 1
|
|
262
268
|
with_data = {col_id for col_id, _i, _lab in values}
|
|
263
269
|
all_cols = [(m, s) for m in metrics for s in stats]
|
|
264
270
|
cols = [(m, s) for m, s in all_cols if f"{m}:{s}" in with_data] or all_cols # drop empties
|
|
@@ -338,7 +344,9 @@ def compare_runs(
|
|
|
338
344
|
if unit == ""
|
|
339
345
|
else f"{v:.4g}"
|
|
340
346
|
)
|
|
341
|
-
|
|
347
|
+
mult = "" if single else _mult(v, best)
|
|
348
|
+
style = "" if single else (rank_style(v, best, worst) or "")
|
|
349
|
+
cells.append(Text(body + mult, style=style))
|
|
342
350
|
table.add_row(*cells)
|
|
343
351
|
console.print(table)
|
|
344
352
|
console.print() # one blank line between sub-tables
|
|
@@ -60,6 +60,23 @@ def test_compare_prints_table(tmp_path):
|
|
|
60
60
|
assert "(2.00)" in result.output and "time (s)" in result.output
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def test_compare_single_run_prints_table(tmp_path):
|
|
64
|
+
# one run is allowed now: a plain table, no comparison multiplier
|
|
65
|
+
a = _run(tmp_path, "run.json", [_bm("test_x", t=1.0, peak=10 * 1024**2)])
|
|
66
|
+
result = runner.invoke(app, ["compare", str(a), "--columns", "time,peak"])
|
|
67
|
+
assert result.exit_code == 0, _text(result)
|
|
68
|
+
assert "time (s)" in result.output and "peak (MiB)" in result.output
|
|
69
|
+
assert "(1.0)" not in result.output # nothing to rank against
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_compare_single_run_with_fail_on_exits_2(tmp_path):
|
|
73
|
+
# --fail-on gates growth of one run vs another, so it needs two; refuse, don't silently pass
|
|
74
|
+
a = _run(tmp_path, "run.json", [_bm("test_x", peak=100)])
|
|
75
|
+
result = runner.invoke(app, ["compare", str(a), "--fail-on", "peak:10%"])
|
|
76
|
+
assert result.exit_code == 2
|
|
77
|
+
assert "at least two runs" in _text(result)
|
|
78
|
+
|
|
79
|
+
|
|
63
80
|
def test_compare_columns_selects_time_and_peak(tmp_path):
|
|
64
81
|
a = _run(tmp_path, "base.json", [_bm("test_x", t=1.0, peak=10 * 1024**2)])
|
|
65
82
|
b = _run(tmp_path, "head.json", [_bm("test_x", t=1.0, peak=20 * 1024**2)])
|
|
@@ -57,6 +57,38 @@ def test_same_file_twice_errors_cleanly(tmp_path):
|
|
|
57
57
|
compare_runs([a, a], out=StringIO())
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def test_single_run_prints_plain_table(tmp_path):
|
|
61
|
+
# one run is a valid input: a plain readout, no cross-run multiplier or ranking colour
|
|
62
|
+
a = _write(tmp_path / "run.json", [_bm("test_x", t=1.0, peak=10 * 1024**2)])
|
|
63
|
+
out = StringIO()
|
|
64
|
+
compare_runs([a], columns="time,peak", out=out)
|
|
65
|
+
text = out.getvalue()
|
|
66
|
+
assert "time (s)" in text and "peak (MiB)" in text # the columns still render
|
|
67
|
+
rows = _lines(text, "test_x")
|
|
68
|
+
assert "(run)" in rows # the single run's label still names the row
|
|
69
|
+
assert "(1.0)" not in rows # nothing to compare against → no relative multiplier
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_single_run_multi_id_has_no_within_run_ranking(tmp_path):
|
|
73
|
+
# a single run with several ids in one group is still plain — no (N.NN) ranking the
|
|
74
|
+
# ids against each other (that's reserved for the cross-run comparison).
|
|
75
|
+
a = _write(
|
|
76
|
+
tmp_path / "run.json",
|
|
77
|
+
[_bm("pkg::test_op[n=1]", peak=1024), _bm("pkg::test_op[n=2]", peak=10 * 1024**2)],
|
|
78
|
+
)
|
|
79
|
+
out = StringIO()
|
|
80
|
+
compare_runs([a], columns="peak", group_by="func", out=out)
|
|
81
|
+
text = out.getvalue()
|
|
82
|
+
assert "test_op[n=1]" in text and "test_op[n=2]" in text
|
|
83
|
+
assert "(1.0)" not in text and "(10240.00)" not in text # no within-run multiplier
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_single_run_empty_file_errors(tmp_path):
|
|
87
|
+
a = _write(tmp_path / "run.json", [])
|
|
88
|
+
with pytest.raises(ValueError, match="no benchmarks"):
|
|
89
|
+
compare_runs([a], out=StringIO())
|
|
90
|
+
|
|
91
|
+
|
|
60
92
|
def test_distinct_files_same_stem_disambiguate(tmp_path):
|
|
61
93
|
# two real runs that happen to share a stem (v1/bench.json vs v2/bench.json)
|
|
62
94
|
# is a legit comparison and must not collapse to one series.
|