From da4020166014b6b4f7ae71ba6f65985d0b3fe5b2 Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 21 Jul 2026 23:05:18 +0200 Subject: [PATCH] Pass --locked to the cargo-mutants install so the source fallback is pinned (#364) cargo binstall falls back to a source build when no prebuilt binary matches the runner. Without --locked that build resolves cargo-mutants' dependency tree to the newest permitted versions and can pull in a transitive crate whose MSRV exceeds the pinned nightly toolchain (cargo-platform 0.3.3 needing rustc 1.91 broke a run against nightly-2025-06-26), failing the install before any mutant runs. Add --locked to both binstall invocations; it is forwarded to the source build, which then honours cargo-mutants' committed Cargo.lock. This mirrors the cargo-nextest install in the generate-coverage action. Pin a shape-test invariant that the install passes --locked. --- .github/workflows/mutation-cargo.yml | 13 +++++++-- .../tests/test_mutation_workflow_shape.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mutation-cargo.yml b/.github/workflows/mutation-cargo.yml index 4935b7c2..75d542ba 100644 --- a/.github/workflows/mutation-cargo.yml +++ b/.github/workflows/mutation-cargo.yml @@ -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 diff --git a/workflow_scripts/tests/test_mutation_workflow_shape.py b/workflow_scripts/tests/test_mutation_workflow_shape.py index 78618042..162505b7 100644 --- a/workflow_scripts/tests/test_mutation_workflow_shape.py +++ b/workflow_scripts/tests/test_mutation_workflow_shape.py @@ -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,