Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading