From 38dd7401fdfbaa4ae98bda3ea901c80f24d78da7 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:46:20 +0100 Subject: [PATCH] feat: keep the campaign timer as an opt-in campaign.py install-timer writes the autorun units and enables nothing unless --enable is passed. The docs say a contributor who wants a schedule on their own machine installs the timer. --- scripts/campaign.py | 12 ++++++++++++ tests/test_campaign.py | 18 ++++++++++++++++++ wiki/tools.md | 14 +++++++++----- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/scripts/campaign.py b/scripts/campaign.py index 79b0f21dc..090f24b83 100644 --- a/scripts/campaign.py +++ b/scripts/campaign.py @@ -46,6 +46,11 @@ Usage: python scripts/campaign.py status [--json] python scripts/campaign.py findings [--all] [--json] python scripts/campaign.py gate + python scripts/campaign.py install-timer [--enable] + +install-timer is opt-in: a contributor who wants a schedule on their own +machine installs the timer. It writes the units and enables nothing unless +--enable is passed. """ from __future__ import annotations @@ -1827,11 +1832,18 @@ def main(argv: Sequence[str] | None = None) -> int: findings_p.add_argument("--all", action="store_true", help="applied and resolved too") findings_p.add_argument("--json", action="store_true", help="the findings as JSON") sub.add_parser("gate", help="tests, schemas, verify and pack of the changed profiles") + timer_p = sub.add_parser("install-timer", + help="opt-in: write the autorun units, the campaign job's timer among them") + timer_p.add_argument("--enable", action="store_true", + help="also reload systemd and enable the timers (never by default)") args = parser.parse_args(argv) paths = Paths.default() if args.command == "gate": return gate(paths.root, changed_profiles(paths.root)) + if args.command == "install-timer": + command = [sys.executable, str(ROOT / "scripts" / "autorun.py"), "install-timers"] + return subprocess.run(command + (["--enable"] if args.enable else []), check=False).returncode if args.command == "findings": lines = open_findings(paths.root, args.all) if args.json: diff --git a/tests/test_campaign.py b/tests/test_campaign.py index 6bfabbb83..5fbd76cb1 100644 --- a/tests/test_campaign.py +++ b/tests/test_campaign.py @@ -882,6 +882,24 @@ class Findings(Workspace): self.assertIn("would commit 'chore: add campaign measurements'", out.getvalue()) +class InstallTimer(unittest.TestCase): + def test_writes_the_units_and_enables_only_when_asked(self) -> None: + calls = [] + real_run = subprocess.run + + def fake_run(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess: + if "install-timers" not in argv: + return real_run(argv, **kwargs) + calls.append(argv) + return subprocess.CompletedProcess(argv, 0) + with unittest.mock.patch.object(campaign.subprocess, "run", fake_run): + self.assertEqual(campaign.main(["install-timer"]), 0) + self.assertEqual(campaign.main(["install-timer", "--enable"]), 0) + self.assertEqual([argv[2:] for argv in calls], + [["install-timers"], ["install-timers", "--enable"]]) + self.assertTrue(calls[0][1].endswith("scripts/autorun.py")) + + class Registry(unittest.TestCase): def test_no_profile_takes_a_campaign_file_name(self) -> None: stems = {p.stem for p in (REPO_ROOT / "emulators").glob("*.yml")} diff --git a/wiki/tools.md b/wiki/tools.md index c389d35f4..19d3ee0ef 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -950,7 +950,7 @@ python scripts/campaign.py run --commit --commit-every 10 python scripts/campaign.py status # done, failed, pending, and why python scripts/campaign.py findings # what waits for review python scripts/campaign.py gate # the checks behind a commit -python scripts/campaign.py install-timer # the autorun units, the campaign's among them +python scripts/campaign.py install-timer # opt-in: writes the autorun units, enables nothing ``` | Step | What happens | @@ -1002,10 +1002,14 @@ the findings it added. Logs are `.cache/campaign/logs/`, kept 30 days. each changed profile in its own, behind `campaign.py gate`: the measurement and campaign tests, `validate_schemas.py --source-only`, then `verify.py --emulator` and `generate_pack.py --emulator` for each changed profile. A file -that had uncommitted changes before the pass is left alone. The nightly -`campaign` autorun job (06:00, after `nightly`) runs a 150-minute pass in the -automation worktree, gates it with `campaign.py gate` and commits with its -own rules, pushing only with `--push`. +that had uncommitted changes before the pass is left alone. + +A maintainer starts each pass. Scheduling is opt-in: a contributor who wants a +schedule on their own machine installs the timer with `campaign.py +install-timer`, which writes the autorun units and enables nothing unless +`--enable` is passed. The `campaign` autorun job then runs a 150-minute pass +in the automation worktree, gates it with `campaign.py gate` and commits with +its own rules, pushing only with `--push`. Schemas: `campaign-finding.schema.json` and `campaign-run.schema.json`.