feat(tools): select progress history by UTC date

This commit is contained in:
StoneDC committed 2026-10-29 14:47:54 +08:00
1 parent f88c86bf6c
commit a441b49334
3 files changed
+48 -2

No files matched your search

+3
View File
@@ -192,6 +192,9 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap.py)
add_test(NAME pr_labels COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_labels.py)
add_test(NAME progress_history_date_window COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_history.py HistoryDateWindowTests)
set_tests_properties(progress_history_date_window PROPERTIES TIMEOUT 20)
add_test(NAME progress_history COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_history.py ProgressHistoryTests)
add_test(NAME progress_history_output COMMAND ${Python3_EXECUTABLE}
+17 -2
View File
@@ -113,11 +113,22 @@ def save_cache(path, signature, commits):
temporary.unlink(missing_ok=True)
def collect(ref, cache=None):
def utc_day(value):
try:
if date.fromisoformat(value).isoformat() == value:
return value
except ValueError:
pass
raise argparse.ArgumentTypeError("date bounds must be valid YYYY-MM-DD UTC days")
def collect(ref, cache=None, since=None, until=None):
signature = cache_signature() if cache is not None else None
records = load_cache(cache, signature) if cache is not None else {}
points, changed = [], False
for day, sha in daily_commits(ref):
if (since is not None and day < since) or (until is not None and day > until):
continue
if sha not in records:
records[sha] = measure(sha)
changed = True
@@ -228,6 +239,8 @@ def page(points):
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument("--since", type=utc_day, help="first UTC day to measure, inclusive (YYYY-MM-DD)")
parser.add_argument("--until", type=utc_day, help="last UTC day to measure, inclusive (YYYY-MM-DD)")
parser.add_argument("output", type=Path)
parser.add_argument("--ref", default="HEAD", help="branch or commit whose first-parent history is measured")
parser.add_argument("--cache", type=Path, help="reuse per-commit counts while the scanner, ISA list and Python version match")
@@ -242,7 +255,9 @@ def main(argv=None):
if any(args.cache.resolve() == path.resolve() or
(args.cache.exists() and path.exists() and args.cache.samefile(path)) for path in outputs):
raise ValueError("history cache must differ from generated reports")
points = collect(args.ref, args.cache)
if args.since and args.until and args.since > args.until:
raise ValueError("--since must not be later than --until")
points = collect(args.ref, args.cache, args.since, args.until)
except (OSError, ValueError) as error:
parser.error(str(error))
if not points:
+28
View File
@@ -67,6 +67,34 @@ class HistoryReportOutputTests(unittest.TestCase):
self.assertGreater((output / filename).stat().st_size, 0)
class HistoryDateWindowTests(unittest.TestCase):
def test_window_filters_days_before_measurement_and_reuses_cache(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
subprocess.run(["git", "init", "-q", directory], check=True)
for day, time, name in (("2026-01-01", "23:59:59Z", "A"), ("2026-01-02", "00:00:00Z", "B"), ("2026-01-02", "23:59:59Z", "C"), ("2026-01-03", "00:00:00Z", "D")):
commit(root, day, name, f"int APS5_VABI {name}() {{ return 0; }}\n", time)
with patch.multiple(progress, ROOT=root, PRX=root / "core/libs/prx", OPCODES=root / OPCODES):
all_days = progress_history.daily_commits("HEAD")
expected = [all_days[1][1]]
actual_measure = progress_history.measure
with patch.object(progress_history, "measure", wraps=actual_measure) as measure:
selected = progress_history.collect("HEAD", root / "cache", "2026-01-02", "2026-01-02")
self.assertEqual([c.args[0] for c in measure.call_args_list], expected)
self.assertEqual([p["date"] for p in selected], ["2026-01-02"])
with patch.object(progress_history, "measure", side_effect=AssertionError("cache missed")):
self.assertEqual(progress_history.collect("HEAD", root / "cache", "2026-01-02", "2026-01-02"), selected)
self.assertEqual(len(progress_history.collect("HEAD", root / "cache")), 3)
self.assertEqual(progress_history.collect("HEAD", since="2027-01-01"), [])
def test_invalid_cli_bounds(self):
for options in (("--since", "20260201"), ("--until", "2026-02-30"), ("--since", "2026-02-02", "--until", "2026-02-01")):
with self.subTest(options=options), contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as error:
progress_history.main(["unused-output", *options])
self.assertEqual(error.exception.code, 2)
class ProgressHistoryTests(unittest.TestCase):
@unittest.skipUnless(os.name == "nt", "requires Windows read-only replacement semantics")
def test_readonly_cache_failure_cleans_temporary_and_can_retry(self):