From db9463b1eed38dca6b0462a176d3bef15b39220e Mon Sep 17 00:00:00 2001 From: jsboige Date: Tue, 1 Sep 2026 01:31:23 +0200 Subject: [PATCH] fix(ci,#13861): document workflow_dispatch on notebook-validation + validation-matrix Cancel-in-progress annule chaque run PR sur le push suivant, laissant l'arbre final d'un lot de merges non revalue (#13861). Les 2 workflows notebook-validation et validation-matrix portaient deja workflow_dispatch; ce fix : - Commente le trigger dans chaque YAML pour expliquer le pourquoi (post-lot-de-merges revalidate, sans commit vide) - Ajoute scripts/tests/test_workflow_dispatch_revalidate.py cliquet 2 tests : 1. workflow_dispatch reste present sur les 2 workflows 2. aucun job-level if: ne filtre github.event_name (sinon le dispatch path serait skipped) Verifie le 2026-09-01 sur origin/main 8ad343570. --- .github/workflows/notebook-validation.yml | 4 + .github/workflows/validation-matrix.yml | 4 + .../test_workflow_dispatch_revalidate.py | 81 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 scripts/tests/test_workflow_dispatch_revalidate.py diff --git a/.github/workflows/notebook-validation.yml b/.github/workflows/notebook-validation.yml index f6edd7ccc6..f9a0a36686 100644 --- a/.github/workflows/notebook-validation.yml +++ b/.github/workflows/notebook-validation.yml @@ -9,6 +9,10 @@ on: branches: [ main ] paths: - '**.ipynb' + # Manual revalidate after a merge batch (issue #13861): + # `cancel-in-progress` cancels each PR's run on the next push, leaving the + # final tree of a merge batch un-revalidated. `workflow_dispatch` is the + # only way to revalidate the final tree without an empty commit. workflow_dispatch: permissions: diff --git a/.github/workflows/validation-matrix.yml b/.github/workflows/validation-matrix.yml index 4cd320dc97..6670a1a35a 100644 --- a/.github/workflows/validation-matrix.yml +++ b/.github/workflows/validation-matrix.yml @@ -11,6 +11,10 @@ on: paths: - '**.ipynb' - 'scripts/validation/**' + # Manual revalidate after a merge batch (issue #13861): + # `cancel-in-progress` cancels each PR's run on the next push, leaving the + # final tree of a merge batch un-revalidated. `workflow_dispatch` is the + # only way to revalidate the final tree without an empty commit. workflow_dispatch: inputs: family: diff --git a/scripts/tests/test_workflow_dispatch_revalidate.py b/scripts/tests/test_workflow_dispatch_revalidate.py new file mode 100644 index 0000000000..900a4c3d02 --- /dev/null +++ b/scripts/tests/test_workflow_dispatch_revalidate.py @@ -0,0 +1,81 @@ +"""#13861 cliquet: notebook-validation and validation-matrix expose workflow_dispatch. + +Cancel-in-progress cancels each PR's run on the next push, leaving the final +tree of a merge batch un-revalidated (issue #13861). Workflow_dispatch is the +only way to revalidate the final tree without an empty commit. This test +ensures the trigger stays present; removing it would silently re-break the +defect the issue was opened against. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[2] +WORKFLOWS = REPO_ROOT / ".github" / "workflows" + +NEEDS_DISPATCH = ( + "notebook-validation.yml", + "validation-matrix.yml", +) + + +def _triggers(path: Path) -> set[str]: + """Return the set of trigger names declared on the workflow. + + YAML parses the bare ``on:`` key as Python True (a YAML 1.1 quirk). Be + defensive about both spellings so the test does not blink if a future + contributor writes ``on:`` as a quoted string. + """ + data = yaml.safe_load(path.read_text(encoding="utf-8")) + if True in data: + return set(data[True].keys()) + if "on" in data: + return set(data["on"].keys()) + return set() + + +def test_revalidate_workflows_expose_workflow_dispatch() -> None: + missing = [] + for name in NEEDS_DISPATCH: + triggers = _triggers(WORKFLOWS / name) + if "workflow_dispatch" not in triggers: + missing.append(name) + assert not missing, ( + f"workflow_dispatch absent on {missing}; revalidation after a merge " + f"batch (#13861) breaks without it." + ) + + +def test_revalidate_workflows_no_job_level_pull_request_filter() -> None: + """A job-level `if: github.event_name != 'pull_request'` would skip + workflow_dispatch-derived runs (event_name == 'workflow_dispatch'). + + Such a guard was never introduced on these two workflows, and this test + pins that absence so a future copy-paste from another workflow does not + silently break revalidation. + """ + import re + + for name in NEEDS_DISPATCH: + text = (WORKFLOWS / name).read_text(encoding="utf-8") + # job-level ifs sit under `jobs:`; pull_request_filter would look like + # `if: github.event_name != 'pull_request'` or similar. We tolerate + # the workflow-level concurrency guard (cancel-in-progress) which is + # not at the job level. + in_jobs = re.search(r"^jobs:.*?(?=^[a-z]|\Z)", text, re.S | re.M) + assert in_jobs, f"{name}: no `jobs:` block found" + jobs_block = in_jobs.group(0) + assert "github.event_name" not in jobs_block or ( + "workflow_dispatch" in jobs_block + ), ( + f"{name}: job-level `if:` filters on `github.event_name` would " + f"skip the workflow_dispatch revalidate path; pin it explicitly " + f"or remove the filter." + ) + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-v"])) # type: ignore[name-defined]