Files
rustfs/scripts/test_ci_timing_report.py
T
Zhengchao An eb96b402b1 feat(ci): measure queue and execution time by run attempt (#7689)
* feat(ci): measure queue and execution time by run attempt

* fix(ci): bound timing samples and recognize the old workspace lane

* fix(ci): sample completed runs for stable timing comparisons
2026-09-12 11:46:05 +08:00

75 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Timing reports must preserve missing evidence and non-successful runs."""
import copy
from datetime import datetime, timezone
import unittest
from unittest import mock
from ci_timing_report import collect, summarize
class TimingTests(unittest.TestCase):
def setUp(self):
self.job = {"id": 2, "name": "Workspace Test and Lint", "status": "completed", "conclusion": "success",
"created_at": "2026-09-01T00:10:00Z", "started_at": "2026-09-01T00:15:00Z", "completed_at": "2026-09-01T00:25:00Z",
"steps": [{"name": "Run nextest tests", "status": "completed", "conclusion": "success", "started_at": "2026-09-01T00:16:00Z", "completed_at": "2026-09-01T00:24:00Z"}]}
self.run = {"id": 1, "run_attempt": 2, "head_sha": "a" * 40, "status": "completed", "conclusion": "success",
"created_at": "2026-09-01T00:00:00Z", "run_started_at": "2026-09-01T00:00:00Z", "jobs": [self.job]}
def test_parallel_minutes_are_not_wall_time(self):
self.run["jobs"].append({**self.job, "id": 3, "name": "Other"})
result = summarize([self.run])
self.assertEqual(result["successful_code_wall"]["median_minutes"], 25)
self.assertEqual(result["successful_code_job_sum"]["median_minutes"], 20)
self.assertEqual(result["jobs"][self.job["name"]]["queue"]["median_minutes"], 5)
self.assertEqual(result["jobs"][self.job["name"]]["steps"]["Run nextest tests"]["median_minutes"], 8)
def test_cancelled_incomplete_and_docs_runs_are_not_success_samples(self):
cancelled = {**self.run, "status": "completed", "conclusion": "cancelled"}
pending = {**self.run, "status": "queued", "conclusion": None, "jobs": []}
docs = {**self.run, "jobs": [{**self.job, "name": "Quick Checks"}]}
result = summarize([self.run, cancelled, pending, docs])
self.assertEqual(result["successful_code_runs"], 1)
self.assertEqual(result["cancelled_fraction"], 0.25)
self.assertEqual(result["completed_fraction"], 0.75)
def test_rerun_wall_time_excludes_time_before_the_attempt(self):
self.run["created_at"] = "2026-08-01T00:00:00Z"
self.assertEqual(summarize([self.run])["successful_code_wall"]["median_minutes"], 25)
del self.run["run_started_at"]
self.assertIsNone(summarize([self.run])["successful_code_wall"]["median_minutes"])
def test_old_workspace_job_name_requires_an_executed_test_step(self):
self.job["name"] = "Test and Lint"
self.assertEqual(summarize([self.run])["successful_code_runs"], 1)
self.job["steps"] = []
self.assertEqual(summarize([self.run])["successful_code_runs"], 0)
def test_collection_rejects_out_of_range_api_results(self):
with mock.patch("ci_timing_report.api", return_value={"workflow_runs": [self.run]}):
with self.assertRaisesRegex(ValueError, "outside the requested date range"):
collect("owner/repo", 1, datetime(2026, 9, 2, tzinfo=timezone.utc))
def test_missing_times_are_unknown_not_zero(self):
self.job["created_at"] = None
self.job["completed_at"] = None
result = summarize([self.run])
self.assertIsNone(result["successful_code_wall"]["median_minutes"])
self.assertIsNone(result["successful_code_job_sum"]["median_minutes"])
self.assertEqual(result["jobs"][self.job["name"]]["queue"]["missing"], 1)
def test_collection_binds_the_attempt_and_rejects_reruns(self):
source = copy.deepcopy(self.run)
del source["jobs"]
with mock.patch("ci_timing_report.api", side_effect=[{"workflow_runs": [source]}, {"jobs": [self.job]}, source]) as api:
snapshot = collect("owner/repo", 1, datetime(2026, 8, 1, tzinfo=timezone.utc))
self.assertIn("/attempts/2/jobs?", api.call_args_list[1].args[0])
self.assertEqual(snapshot["runs"][0]["run_attempt"], 2)
with mock.patch("ci_timing_report.api", side_effect=[{"workflow_runs": [source]}, {"jobs": [self.job]}, {**source, "run_attempt": 3}]):
with self.assertRaisesRegex(ValueError, "changed during collection"):
collect("owner/repo", 1, datetime(2026, 8, 1, tzinfo=timezone.utc))
if __name__ == "__main__":
unittest.main()