diff --git a/src/reposteward/benchmark.py b/src/reposteward/benchmark.py index ee664d8..06cbe3a 100644 --- a/src/reposteward/benchmark.py +++ b/src/reposteward/benchmark.py @@ -37,6 +37,10 @@ Scenario = Callable[[], ScenarioResult] +class BenchmarkReportError(ValueError): + pass + + def _canonical_json(value: object) -> str: return json.dumps( value, @@ -933,7 +937,7 @@ def run_benchmark( def load_benchmark_report(path: Path) -> dict[str, Any]: value = json.loads(path.read_text(encoding="utf-8")) if not isinstance(value, dict): - raise TypeError("baseline benchmark report must be an object") + raise BenchmarkReportError("baseline benchmark report must be an object") validate_benchmark_report(value) return value diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 09c8568..e5fed2e 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -4,7 +4,7 @@ import io import json import unittest -from contextlib import redirect_stdout +from contextlib import redirect_stderr, redirect_stdout from copy import deepcopy from pathlib import Path from tempfile import TemporaryDirectory @@ -104,6 +104,58 @@ def test_cli_writes_machine_readable_report_without_project_config(self) -> None self.assertEqual(printed, saved) self.assertEqual(printed["summary"]["scenario_count"], 1) + def test_cli_rejects_non_object_baseline_without_traceback(self) -> None: + with TemporaryDirectory() as directory: + baseline_path = Path(directory) / "baseline.json" + baseline_path.write_text("[]\n", encoding="utf-8") + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + exit_code = main( + [ + "benchmark", + "run", + "--scenario", + "context.utf8_estimate", + "--baseline", + str(baseline_path), + ] + ) + + self.assertEqual(exit_code, 2) + self.assertEqual(stdout.getvalue(), "") + self.assertEqual( + stderr.getvalue(), + "reposteward: baseline benchmark report must be an object\n", + ) + self.assertNotIn("Traceback", stderr.getvalue()) + + def test_cli_accepts_valid_baseline(self) -> None: + with TemporaryDirectory() as directory: + baseline_path = Path(directory) / "baseline.json" + baseline_path.write_text( + json.dumps(run_benchmark(scenario_ids=("context.utf8_estimate",))), + encoding="utf-8", + ) + stdout = io.StringIO() + + with redirect_stdout(stdout): + exit_code = main( + [ + "benchmark", + "run", + "--scenario", + "context.utf8_estimate", + "--baseline", + str(baseline_path), + ] + ) + + report = json.loads(stdout.getvalue()) + self.assertEqual(exit_code, 0) + self.assertEqual(report["baseline_comparison"]["matched_scenarios"], 1) + def test_invalid_repeat_and_empty_selection_fail_before_running(self) -> None: with self.assertRaisesRegex(ValueError, "repeat"): run_benchmark(repeat=1)