From 76c6aae5c4e22da59ea2d9a7dcb1a2c2af6836c1 Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 20 Jul 2026 00:47:45 +0200 Subject: [PATCH] Document the mutation-testing workflow contract tests Add a developers' guide section describing the mutmut caller workflow and the pytest module that pins its shape, so maintainers know what the contract test covers and how to run it locally without reverse engineering the workflow and test files. --- docs/developers-guide.md | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index dcb34858..19c6f8f1 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -1182,6 +1182,79 @@ If a workflow's behaviour genuinely depends on a feature only present from a particular commit onwards, express that as a comment or a changelog note, not as a test assertion on the SHA string. +## Mutation-testing workflow contract tests + +This repository runs scheduled, informational mutation testing through a thin +caller workflow, [`.github/workflows/mutation-testing.yml`](../.github/workflows/mutation-testing.yml), +which delegates to the shared reusable workflow +`leynos/shared-actions/.github/workflows/mutation-mutmut.yml`. The heavy +lifting — running `mutmut` and summarizing survivors — lives in +`shared-actions`; this repository carries only declarative configuration. The +run is **informational only**: it never gates a pull request. Survivors are +reported through the job summary and downloadable artefacts so they can be +triaged into tests, not enforced as a blocking check. Only the Python package +is enrolled: the pinned `mutation-cargo.yml` reusable workflow always fans a +repository-root `cargo-mutants` target out for `workflow_dispatch` full runs, +and Cuprum has no repository-root `Cargo.toml` (the Rust workspace manifest +lives at `rust/Cargo.toml`), so a Rust job cannot ride the shared workflow at +this pin. The mutation targets and test selection are configured separately in +`[tool.mutmut]` in `pyproject.toml` (`source_paths`, +`pytest_add_cli_args_test_selection`, `do_not_mutate`). + +The workflow runs in two modes. A **daily schedule** fires a change-scoped run +that mutates only the source files touched within the detection window, so +quiet days are cheap no-ops. A **manual dispatch** (the Actions "Run workflow" +control) mutates the whole package; select a branch in that control to +exercise a feature branch. + +The caller passes two configuration inputs: + +- `paths` — `cuprum/`, the change-detection glob that decides whether a + scheduled run has anything to mutate. The flat repository layout puts the + mutable source directly at the repository root, so change detection watches + the package itself. +- `module-prefix-strip` — the empty string, because the flat layout has no + `src/` prefix to strip before module-glob translation. + +The `uses:` reference pins the shared workflow to a full 40-character commit +SHA rather than a branch or tag, so a force-push upstream cannot silently +change what runs here. The contract test asserts only that the pin is a full +commit SHA, not a particular value, so Dependabot bumps it automatically +without any accompanying test edit (see +[Workflow pins and Dependabot](#workflow-pins-and-dependabot) above for the +shape-only rationale). + +### Workflow contract tests + +Because the caller is configuration rather than code, `tests/test_workflow_contract.py` +pins the shape it must uphold, failing the pull request when the caller +drifts — repointing the pin at a branch, widening the token scope, or +dropping a configuration input — rather than letting the breakage surface +only in a scheduled run. Unlike some sibling repositories, this module has no +`skipif` guard: `.github/` is listed in `[tool.mutmut].also_copy`, so the +workflow file is present inside mutmut's sandbox and the contract test runs +there too. Run it locally with: + +```bash +uv run --with pytest --with pyyaml pytest tests/test_workflow_contract.py -q +``` + +There is no dedicated Makefile target for this test; it also falls outside +`PYTEST_TARGETS`, the glob list `make test` uses, so it must be run directly +with the command above (or as part of a full mutmut pass). The test +validates: + +- the `uses:` reference targets `mutation-mutmut.yml` pinned to a full commit + SHA; +- the `with:` block carries exactly `{"paths": "cuprum/", + "module-prefix-strip": ""}`; +- job permissions are least-privilege (`contents: read`, `id-token: write`) + and the workflow-level default token scope is empty; +- `concurrency` serializes runs per ref without cancelling one in progress; + and +- the triggers keep the daily 08:20 UTC schedule and a plain + `workflow_dispatch` with no inputs. + ## Compile-time UI tests (trybuild) The Rust crate at `rust/cuprum-rust/` uses