Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions .github/actions/linux-packages/scripts/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ def _fail(message: str, *, code: int = 2) -> typ.NoReturn:
class OctalInt(int):
"""Integer subclass that renders as a zero-padded octal literal."""

_octal_width: int

def __new__(cls, value: int, *, width: int = 4) -> OctalInt:
"""Initialize the integer and remember the desired octal width."""
obj = super().__new__(cls, value)
Expand Down
27 changes: 13 additions & 14 deletions .github/actions/release-to-pypi-uv/tests/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,20 @@
if typ.TYPE_CHECKING: # pragma: no cover - imported for annotations only
from types import ModuleType

if _ACTION_PATH := os.environ.get("GITHUB_ACTION_PATH"):
_action_root = Path(_ACTION_PATH).resolve()
scripts_candidate = _action_root / "scripts"
# The action scripts ship alongside the tests in this repository, so the
# local path is authoritative. ``GITHUB_ACTION_PATH`` is only a fallback
# for relocated layouts: the Makefile exports it at the repository root,
# whose own ``scripts/`` directory belongs to a different action and must
# not be used to locate these scripts.
_scripts_dir = Path(__file__).resolve().parents[1] / "scripts"
action_path = os.environ.get("GITHUB_ACTION_PATH")
if not _scripts_dir.is_dir() and action_path:
scripts_candidate = Path(action_path).resolve() / "scripts"
if scripts_candidate.is_dir():
SCRIPTS_DIR = scripts_candidate
try:
REPO_ROOT = _action_root.parents[2]
except IndexError:
REPO_ROOT = scripts_candidate.parents[3]
else:
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
REPO_ROOT = SCRIPTS_DIR.parents[3]
else:
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
REPO_ROOT = SCRIPTS_DIR.parents[3]
_scripts_dir = scripts_candidate

SCRIPTS_DIR = _scripts_dir
REPO_ROOT = SCRIPTS_DIR.parents[3]


def load_script_module(name: str) -> ModuleType:
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/mutation-cargo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,19 @@ jobs:
CARGO_MUTANTS_VERSION: ${{ inputs.cargo-mutants-version }}
run: |
set -euo pipefail
# `--locked` is forwarded to the source-build fallback that
# cargo-binstall runs when no prebuilt binary matches the runner.
# Without it the fallback resolves cargo-mutants' dependency tree
# to the newest permitted versions, which can pull in a transitive
# crate whose MSRV exceeds the pinned nightly toolchain (e.g.
# cargo-platform 0.3.3 requiring rustc 1.91) and fail the install.
# Building against the crate's committed Cargo.lock keeps the
# install reproducible against the pinned toolchain (issue #364).
# Mirrors the cargo-nextest install in the generate-coverage action.
if [[ -n "${CARGO_MUTANTS_VERSION}" ]]; then
cargo binstall --no-confirm "cargo-mutants@${CARGO_MUTANTS_VERSION}"
cargo binstall --no-confirm --locked "cargo-mutants@${CARGO_MUTANTS_VERSION}"
else
cargo binstall --no-confirm cargo-mutants
cargo binstall --no-confirm --locked cargo-mutants
fi

- name: Run setup commands
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ lib-cov
coverage
*.lcov

# pytest-cov coverage database
.coverage

# nyc test coverage
.nyc_output

Expand Down
18 changes: 18 additions & 0 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,24 @@ Internals for maintainers:
`tests/workflows/test_resolve_workflow_source.py`; the OIDC happy
path is validated by every real run of the consuming workflows.

### `cargo-mutants` Install Contract

The `Install cargo-mutants` step in `mutation-cargo.yml` installs
cargo-mutants with `cargo binstall --no-confirm --locked`. `cargo binstall`
falls back to a source build when no prebuilt binary matches the runner,
and it forwards `--locked` to that fallback so the build resolves against
cargo-mutants' committed `Cargo.lock` instead of the newest permitted
versions. Without `--locked` the fallback can pull in a transitive crate
whose MSRV exceeds the pinned nightly toolchain (`cargo-platform 0.3.3`
requiring rustc 1.91 broke a scheduled run against `nightly-2025-06-26`),
failing the install before any mutant runs (issue #364).

Keep `--locked` on both binstall invocations — the version-pinned branch
and the unversioned (latest) branch — and keep the shape-test invariant
(`test_cargo_mutants_install_is_locked`) in sync. This mirrors the
`cargo-nextest` install in the `generate-coverage` action, which already
passes `--locked`.

## Running the Test Suite

```bash
Expand Down
17 changes: 17 additions & 0 deletions docs/execplans/add-mutation-testing-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,23 @@ work proceeds.
every assert in the mutmut and cargo-summary test modules a
descriptive failure message.

- 2026-08-09 (issue #364, review round): the `Install cargo-mutants`
step passes `--locked` to both `cargo binstall` invocations.
Rationale: `cargo binstall` falls back to a source build when no
prebuilt binary matches the runner and forwards `--locked` to that
fallback; without it the build resolves cargo-mutants' dependency
tree to the newest permitted versions, and `cargo-platform 0.3.3`
(MSRV rustc 1.91) broke a scheduled run against the pinned
`nightly-2025-06-26` toolchain. Option (a) add `--locked` was chosen
over option (b) `--disable-strategies compile` to fail fast when no
prebuilt binary exists: (a) keeps the resilient source-build fallback
working while removing the MSRV-drift failure mode and matches the
established `generate-coverage` nextest pattern; (b) would trade a
latent fragility for a hard outage whenever a prebuilt binary is
briefly unavailable. Recorded in the developers guide
(`docs/developers-guide.md` §`cargo-mutants` Install Contract) and
pinned by the shape test `test_cargo_mutants_install_is_locked`.

## Outcomes & Retrospective

Stages A–F implemented 2026-07-04 in five commits (plan hardening plus
Expand Down
26 changes: 14 additions & 12 deletions workflow_scripts/tests/test_mutation_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,24 @@ def test_bucket_files_partitions_without_loss_or_overlap(


@given(
paths=st.lists(
st.lists(SEGMENT, min_size=1, max_size=3).map(
lambda parts: "src/" + "/".join(parts) + ".py"
),
path_specs=st.lists(
st.lists(SEGMENT, min_size=1, max_size=3),
max_size=6,
)
)
def test_module_globs_are_deduplicated_module_patterns(paths: list[str]) -> None:
"""Globs are unique module patterns with no path or suffix residue."""
def test_module_globs_are_deduplicated_module_patterns(
path_specs: list[list[str]],
) -> None:
"""Globs are deduplicated dotted module patterns plus ``.*``.

Reconstructed from the module path parts, so a legitimate module
segment named ``py`` (e.g. ``src/a/py.py`` → ``a.py.*``) is not
mistaken for a stripped-extension residue.
"""
paths = ["src/" + "/".join(parts) + ".py" for parts in path_specs]
expected = [".".join(parts) + ".*" for parts in path_specs]
globs = run_mutmut.files_to_module_globs(" ".join(paths), "src/")
assert len(globs) == len(set(globs))
for glob in globs:
assert glob.endswith(".*")
assert "/" not in glob
assert ".py" not in glob
assert len(globs) <= len(paths)
assert globs == list(dict.fromkeys(expected))


@given(
Expand Down
29 changes: 29 additions & 0 deletions workflow_scripts/tests/test_mutation_workflow_shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,35 @@ def _step_names(steps: list[dict[str, object]]) -> list[object]:
return [step.get("name") for step in steps]


def test_cargo_mutants_install_is_locked() -> None:
"""The cargo-mutants install pins its dependency resolution with --locked.

``cargo binstall`` falls back to a source build when no prebuilt binary
matches the runner. Without ``--locked`` that fallback resolves the
dependency tree to the newest permitted versions and can pull in a
transitive crate whose MSRV exceeds the pinned nightly toolchain, failing
the install before any mutant runs (issue #364).
"""
steps = [
step
for job in _jobs("mutation-cargo.yml").values()
for step in _steps(job)
if step.get("name") == "Install cargo-mutants"
]
assert steps, "mutation-cargo.yml must install cargo-mutants"
for step in steps:
run = step.get("run")
assert isinstance(run, str), "install step must have a run block"
binstall_lines = [line for line in run.splitlines() if "cargo binstall" in line]
assert binstall_lines, "install step must invoke cargo binstall"
for line in binstall_lines:
assert "--locked" in line, (
f"cargo binstall must pass --locked so the source-build "
f"fallback honours the committed Cargo.lock (issue #364): "
f"{line.strip()!r}"
)


@pytest.mark.parametrize("workflow_name", WORKFLOW_NAMES)
def test_every_workflow_checkout_is_followed_by_relocation(
workflow_name: str,
Expand Down