Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,17 @@ jobs:
# results for just that one benchmark, silently disabling
# regression checks for the other benchmarks until the next
# full run happens to succeed.
if: always() && (github.event.inputs.benchmark_target == 'all' || github.event.inputs.benchmark_target == '')
# success(), not always(): a run that fails the regression check must
# not become the next baseline, or each regression lowers the bar for
# the run after it. To accept a slowdown deliberately, delete the
# perf-baseline-macOS-* caches (`gh cache list --limit 100 --json key --jq '.[] | select(.key | startswith("perf-baseline-macOS-")) | .key' | while read -r key; do gh cache delete "$key"; done`); the next run has no baseline, passes,
# and becomes the new one.
if: success() && (github.event.inputs.benchmark_target == 'all' || github.event.inputs.benchmark_target == '')
run: cp performance_results.json performance_baseline.json

- name: Cache updated baseline for next run
uses: actions/cache/save@v6
if: always() && (github.event.inputs.benchmark_target == 'all' || github.event.inputs.benchmark_target == '')
if: success() && (github.event.inputs.benchmark_target == 'all' || github.event.inputs.benchmark_target == '')
with:
path: performance_baseline.json
key: perf-baseline-${{ runner.os }}-${{ github.run_id }}
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- **Weekly performance benchmark promoted failing runs to baseline**: the `Save new baseline` and
`Cache updated baseline for next run` steps in `.github/workflows/performance.yml` ran under `always()`, so a
run that failed the >20% regression check still cached its slower numbers as the next run's baseline — each
regression lowered the bar for the following week (e.g. the 2026-09-13 run compared `apps` against 2.732s,
the exact figure from the failed 2026-09-06 run). Both steps are now gated on `success()`; to accept a
slowdown deliberately, delete the `perf-baseline-macOS-*` caches and the next run re-baselines. Guarded by
`test_performance_baseline_promoted_only_on_success`.

## [1.2.0] - 2026-08-15

### Security
Expand Down
20 changes: 20 additions & 0 deletions tests/test_project_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,26 @@ def test_ci_python_versions(self):
# Validate all supported versions are tested
self._validate_ci_versions(supported_versions, ci_versions)

def test_performance_baseline_promoted_only_on_success(self):
"""Test that a run failing the regression check never becomes the next baseline.

With ``always()``, each failing run's slower numbers were cached as the
next baseline, so every regression lowered the bar for the following run.
"""
perf_path = get_project_root() / ".github" / "workflows" / "performance.yml"
with open(perf_path, encoding="utf-8") as f:
perf_config = yaml.safe_load(f)

step_names = [step.get("name") for step in perf_config["jobs"]["performance-test"]["steps"]]
steps = dict(zip(step_names, perf_config["jobs"]["performance-test"]["steps"], strict=True))
compare_index = step_names.index("Compare with baseline (fail on >20% regression)")

for name in ("Save new baseline", "Cache updated baseline for next run"):
condition = steps[name]["if"]
assert step_names.index(name) > compare_index, f"'{name}' must run after the regression check"
assert "always()" not in condition, f"'{name}' must not run when the regression check fails"
assert condition.startswith("success()"), f"'{name}' should be gated on success()"

def _assert_version_in_ci(self, version: str, ci_versions: list[str]) -> None:
"""Assert that a specific version is tested in CI."""
error_msg = f"Python {version} is supported but not tested in CI"
Expand Down
Loading