| 1 | """Golden-fixture tests for the t/perf external cross-check runner.
|
| 2 |
|
| 3 | These tests run WITHOUT a git source tree: they exercise only the parse path
|
| 4 | (--parse-only against a synthetic t/test-results directory), the skip-honesty
|
| 5 | path (missing --git-src), and the sanity_band helper. No t/perf shell is
|
| 6 | copied here — the fixture is hand-written numeric output in the documented
|
| 7 | ``real user sys`` triplet format.
|
| 8 | """
|
| 9 |
|
| 10 | from __future__ import annotations
|
| 11 |
|
| 12 | import json
|
| 13 | import subprocess
|
| 14 | import sys
|
| 15 | import tempfile
|
| 16 | import unittest
|
| 17 | from pathlib import Path
|
| 18 |
|
| 19 | ROOT = Path(__file__).resolve().parents[1]
|
| 20 | sys.path.insert(0, str(ROOT / "scripts"))
|
| 21 |
|
| 22 | from tperf_runner import ( # noqa: E402
|
| 23 | EXIT_OK,
|
| 24 | EXIT_SKIP,
|
| 25 | MEASUREMENT_SOURCE,
|
| 26 | RESULTS_FILENAME,
|
| 27 | parse_tperf_results,
|
| 28 | sanity_band,
|
| 29 | )
|
| 30 |
|
| 31 | SCRIPT = ROOT / "scripts" / "tperf_runner.py"
|
| 32 |
|
| 33 |
|
| 34 | def make_golden_results(root: Path) -> Path:
|
| 35 | """A temp dir mimicking <git-src>/t/test-results for p0001-rev-list."""
|
| 36 | results = root / "test-results"
|
| 37 | results.mkdir()
|
| 38 | (results / "p0001-rev-list.1.result").write_text("0.12 0.10 0.02\n0.15 0.11 0.03\n0.11 0.09 0.02\n")
|
| 39 | (results / "p0001-rev-list.subtests").write_text("1\n")
|
| 40 | (results / "p0001-rev-list.1.descr").write_text("rev-list --all\n")
|
| 41 | return results
|
| 42 |
|
| 43 |
|
| 44 | def run_cli(*argv: str) -> subprocess.CompletedProcess:
|
| 45 | return subprocess.run(
|
| 46 | [sys.executable, str(SCRIPT), *argv],
|
| 47 | capture_output=True,
|
| 48 | text=True,
|
| 49 | cwd=str(ROOT),
|
| 50 | )
|
| 51 |
|
| 52 |
|
| 53 | class ParseTperfResultsTest(unittest.TestCase):
|
| 54 | def test_golden_fixture_repetition_and_min_rows(self) -> None:
|
| 55 | with tempfile.TemporaryDirectory() as tmp:
|
| 56 | results = make_golden_results(Path(tmp))
|
| 57 | rows = parse_tperf_results(results)
|
| 58 |
|
| 59 | repetitions = [row for row in rows if "repetition" in row]
|
| 60 | minima = [row for row in rows if row.get("tperf_statistic") == "min_of_n"]
|
| 61 | self.assertEqual(len(repetitions), 3)
|
| 62 | self.assertEqual(len(minima), 1)
|
| 63 |
|
| 64 | self.assertEqual([row["elapsed_ms"] for row in repetitions], [120.0, 150.0, 110.0])
|
| 65 | self.assertEqual([row["user_ms"] for row in repetitions], [100.0, 110.0, 90.0])
|
| 66 | self.assertEqual([row["sys_ms"] for row in repetitions], [20.0, 30.0, 20.0])
|
| 67 | self.assertEqual([row["repetition"] for row in repetitions], [1, 2, 3])
|
| 68 |
|
| 69 | for row in rows:
|
| 70 | self.assertEqual(row["measurement_source"], "git_tperf_external")
|
| 71 | self.assertEqual(row["measurement_source"], MEASUREMENT_SOURCE)
|
| 72 | self.assertEqual(row["tperf_script"], "p0001-rev-list")
|
| 73 | self.assertEqual(row["tperf_test"], "p0001-rev-list.1")
|
| 74 | self.assertEqual(row["tperf_description"], "rev-list --all")
|
| 75 | self.assertEqual(row["subject"], "git")
|
| 76 | self.assertEqual(row["subject_kind"], "git")
|
| 77 |
|
| 78 | self.assertEqual(minima[0]["elapsed_ms"], 110.0)
|
| 79 | self.assertEqual(minima[0]["n"], 3)
|
| 80 |
|
| 81 | def test_malformed_lines_are_skipped_not_fatal(self) -> None:
|
| 82 | with tempfile.TemporaryDirectory() as tmp:
|
| 83 | results = Path(tmp)
|
| 84 | (results / "p1234-broken.1.result").write_text(
|
| 85 | "not a number line\n0.20 0.10 0.05\n\n0.30 garbage 0.01\n0.25\n"
|
| 86 | )
|
| 87 | rows = parse_tperf_results(results)
|
| 88 |
|
| 89 | repetitions = [row for row in rows if "repetition" in row]
|
| 90 | minima = [row for row in rows if row.get("tperf_statistic") == "min_of_n"]
|
| 91 | self.assertEqual([row["elapsed_ms"] for row in repetitions], [200.0, 250.0])
|
| 92 | # Single-float line is accepted liberally; user/sys are unmeasured.
|
| 93 | self.assertIsNone(repetitions[1]["user_ms"])
|
| 94 | self.assertIsNone(repetitions[1]["sys_ms"])
|
| 95 | self.assertEqual(minima[0]["elapsed_ms"], 200.0)
|
| 96 | self.assertEqual(minima[0]["n"], 2)
|
| 97 |
|
| 98 | def test_missing_descr_yields_null_description(self) -> None:
|
| 99 | with tempfile.TemporaryDirectory() as tmp:
|
| 100 | results = Path(tmp)
|
| 101 | (results / "p0002-read-cache.1.result").write_text("0.05 0.04 0.01\n")
|
| 102 | rows = parse_tperf_results(results)
|
| 103 | self.assertTrue(rows)
|
| 104 | for row in rows:
|
| 105 | self.assertIsNone(row["tperf_description"])
|
| 106 |
|
| 107 |
|
| 108 | class CliParseOnlyTest(unittest.TestCase):
|
| 109 | def test_parse_only_writes_stamped_jsonl(self) -> None:
|
| 110 | with tempfile.TemporaryDirectory() as tmp:
|
| 111 | root = Path(tmp)
|
| 112 | results = make_golden_results(root)
|
| 113 | out_dir = root / "out"
|
| 114 | proc = run_cli("--parse-only", str(results), "--results", str(out_dir))
|
| 115 | self.assertEqual(proc.returncode, EXIT_OK, proc.stderr)
|
| 116 |
|
| 117 | out_path = out_dir / RESULTS_FILENAME
|
| 118 | rows = [json.loads(line) for line in out_path.read_text().splitlines()]
|
| 119 |
|
| 120 | repetitions = [row for row in rows if "repetition" in row]
|
| 121 | minima = [row for row in rows if row.get("tperf_statistic") == "min_of_n"]
|
| 122 | self.assertEqual(len(repetitions), 3)
|
| 123 | self.assertEqual(len(minima), 1)
|
| 124 | self.assertEqual([row["elapsed_ms"] for row in repetitions], [120.0, 150.0, 110.0])
|
| 125 | self.assertEqual(minima[0]["elapsed_ms"], 110.0)
|
| 126 | self.assertEqual(minima[0]["n"], 3)
|
| 127 | for row in rows:
|
| 128 | self.assertEqual(row["measurement_source"], "git_tperf_external")
|
| 129 | # Runner identity is stamped on every row (ADR-0007).
|
| 130 | self.assertIsInstance(row.get("runner_id"), str)
|
| 131 | self.assertTrue(row["runner_id"])
|
| 132 | self.assertIn("runner_class", row)
|
| 133 |
|
| 134 | def test_parse_only_missing_dir_is_skip(self) -> None:
|
| 135 | with tempfile.TemporaryDirectory() as tmp:
|
| 136 | missing = Path(tmp) / "nope"
|
| 137 | proc = run_cli("--parse-only", str(missing), "--results", str(Path(tmp) / "out"))
|
| 138 | self.assertEqual(proc.returncode, EXIT_SKIP)
|
| 139 | payload = json.loads(proc.stdout)
|
| 140 | self.assertEqual(payload["status"], "skipped")
|
| 141 | self.assertEqual(payload["reason"], f"results_dir_missing:{missing}")
|
| 142 |
|
| 143 |
|
| 144 | class CliRunModeSkipTest(unittest.TestCase):
|
| 145 | def test_missing_git_src_flag_exits_3_with_skip_json(self) -> None:
|
| 146 | with tempfile.TemporaryDirectory() as tmp:
|
| 147 | proc = run_cli("--results", str(Path(tmp) / "out"))
|
| 148 | self.assertEqual(proc.returncode, EXIT_SKIP)
|
| 149 | payload = json.loads(proc.stdout)
|
| 150 | self.assertEqual(payload["status"], "skipped")
|
| 151 | self.assertTrue(payload["reason"].startswith("git_src_missing:"))
|
| 152 |
|
| 153 | def test_git_src_without_tperf_run_exits_3_with_skip_json(self) -> None:
|
| 154 | with tempfile.TemporaryDirectory() as tmp:
|
| 155 | not_a_git_tree = Path(tmp) / "src"
|
| 156 | not_a_git_tree.mkdir()
|
| 157 | proc = run_cli("--git-src", str(not_a_git_tree), "--results", str(Path(tmp) / "out"))
|
| 158 | self.assertEqual(proc.returncode, EXIT_SKIP)
|
| 159 | payload = json.loads(proc.stdout)
|
| 160 | self.assertEqual(payload["status"], "skipped")
|
| 161 | self.assertEqual(payload["reason"], f"git_src_missing:{not_a_git_tree}")
|
| 162 |
|
| 163 |
|
| 164 | class SanityBandTest(unittest.TestCase):
|
| 165 | def test_within_band(self) -> None:
|
| 166 | band = sanity_band(100.0, 110.0, tolerance_pct=50)
|
| 167 | self.assertTrue(band["within_band"])
|
| 168 | self.assertAlmostEqual(band["ratio"], 1.1)
|
| 169 | self.assertEqual(band["tolerance_pct"], 50)
|
| 170 |
|
| 171 | def test_outside_band(self) -> None:
|
| 172 | band = sanity_band(100.0, 200.0, tolerance_pct=50)
|
| 173 | self.assertFalse(band["within_band"])
|
| 174 | self.assertAlmostEqual(band["ratio"], 2.0)
|
| 175 |
|
| 176 | def test_boundary_is_inclusive(self) -> None:
|
| 177 | band = sanity_band(100.0, 150.0, tolerance_pct=50)
|
| 178 | self.assertTrue(band["within_band"])
|
| 179 |
|
| 180 | def test_zero_min_guard_ratio_none_within_false(self) -> None:
|
| 181 | # Documented guard: tperf_min_ms <= 0 -> ratio None, within_band False.
|
| 182 | band = sanity_band(0.0, 100.0)
|
| 183 | self.assertIsNone(band["ratio"])
|
| 184 | self.assertFalse(band["within_band"])
|
| 185 |
|
| 186 | def test_none_inputs_guard(self) -> None:
|
| 187 | for tperf_min, our_median in ((None, 100.0), (100.0, None), (None, None)):
|
| 188 | band = sanity_band(tperf_min, our_median)
|
| 189 | self.assertIsNone(band["ratio"])
|
| 190 | self.assertFalse(band["within_band"])
|
| 191 |
|
| 192 |
|
| 193 | if __name__ == "__main__":
|
| 194 | unittest.main()
|