From 610ca33f904d15447b0f13b300c8e7c36801eaca Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 21 Jul 2026 23:05:10 +0200 Subject: [PATCH 1/2] Forward a submodules input to the caller checkout in mutation-cargo (#363) Callers that vendor a build-graph dependency as a git submodule (for example a Cargo path dependency under third_party/) fail the unmutated baseline: the caller checkout defaults to no submodules, so the submodule directory is empty and cargo cannot read its Cargo.toml before any mutant runs. Add a submodules input (default false, preserving current behaviour) and forward it to actions/checkout in every job that checks out the caller repository (detect and mutants, including shards). Document it in the caller guide and pin a shape-test invariant that each caller checkout forwards the input. --- .github/workflows/mutation-cargo.yml | 13 ++++++++ docs/mutation-cargo-workflow.md | 8 +++++ .../tests/test_mutation_workflow_shape.py | 33 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/.github/workflows/mutation-cargo.yml b/.github/workflows/mutation-cargo.yml index ef4d5d02..625f3aeb 100644 --- a/.github/workflows/mutation-cargo.yml +++ b/.github/workflows/mutation-cargo.yml @@ -66,6 +66,17 @@ on: pipefail`; empty means no extra setup. type: string default: "" + submodules: + description: >- + How to check out the caller repository's git submodules, + forwarded verbatim to actions/checkout's `submodules` input: + `false` (default, no submodules), `true` (top-level only) or + `recursive`. Set this for callers that vendor a build-graph + dependency as a git submodule (e.g. a path dependency under + `third_party/`), whose directory must exist for the unmutated + baseline to build. + type: string + default: "false" # Default the token to no scopes; jobs opt in explicitly. permissions: {} @@ -84,6 +95,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + submodules: ${{ inputs.submodules }} persist-credentials: false - name: Resolve workflow source @@ -183,6 +195,7 @@ jobs: - name: Checkout caller repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + submodules: ${{ inputs.submodules }} persist-credentials: false - name: Resolve workflow source diff --git a/docs/mutation-cargo-workflow.md b/docs/mutation-cargo-workflow.md index 3e0d5d6e..925c271b 100644 --- a/docs/mutation-cargo-workflow.md +++ b/docs/mutation-cargo-workflow.md @@ -84,9 +84,17 @@ jobs: | `cargo-mutants-version` | pinned | Tool version; the summary parser is validated against it. | | `extra-args` | (empty) | Extra cargo-mutants arguments (shell-lexed), e.g. `--all-features`. | | `setup-commands` | (empty) | Shell commands run before cargo-mutants in each mutants job (e.g. `sudo apt-get install -y mold` when the repo's `.cargo/config.toml` selects that linker). | +| `submodules` | `false` | How to check out the caller's git submodules, forwarded to `actions/checkout` (`false`, `true` or `recursive`). Set for callers that vendor a build-graph dependency as a submodule. | ## Notes +- Set `submodules: recursive` (or `true`) when the caller vendors a + build-graph dependency as a git submodule — for example a Cargo path + dependency under `third_party/`. The default checkout leaves the + submodule directory empty, so the unmutated baseline fails to build + (`failed to read .../Cargo.toml`) before any mutant runs. The input is + a no-op for callers without a `.gitmodules`. + - The `cargo-mutants-version` default is pinned because the `outcomes.json` format is documented as unstable and the summary parser must match it. Override only alongside a parser check. diff --git a/workflow_scripts/tests/test_mutation_workflow_shape.py b/workflow_scripts/tests/test_mutation_workflow_shape.py index 78618042..d4af59eb 100644 --- a/workflow_scripts/tests/test_mutation_workflow_shape.py +++ b/workflow_scripts/tests/test_mutation_workflow_shape.py @@ -55,6 +55,39 @@ def _step_names(steps: list[dict[str, object]]) -> list[object]: return [step.get("name") for step in steps] +CALLER_CHECKOUT_STEP = "Checkout caller repository" +SUBMODULES_EXPR = "${{ inputs.submodules }}" + + +def test_caller_checkout_forwards_submodules_input() -> None: + """Every caller checkout in mutation-cargo forwards the submodules input. + + A caller that vendors a build-graph dependency as a git submodule (a + Cargo path dependency under ``third_party/``, say) needs the submodule + directory populated for the unmutated baseline to build. The checkout + defaults to no submodules, so each ``Checkout caller repository`` step + must forward the ``submodules`` input to ``actions/checkout`` (issue + #363). + """ + checkouts = [ + (job_name, step) + for job_name, job in _jobs("mutation-cargo.yml").items() + for step in _steps(job) + if step.get("name") == CALLER_CHECKOUT_STEP + ] + assert checkouts, "mutation-cargo.yml must check out the caller repository" + for job_name, step in checkouts: + params = step.get("with") + assert isinstance(params, dict), ( + f"mutation-cargo.yml:{job_name} caller checkout must set `with`" + ) + assert params.get("submodules") == SUBMODULES_EXPR, ( + f"mutation-cargo.yml:{job_name} caller checkout must forward " + f"the submodules input (issue #363), got " + f"{params.get('submodules')!r}" + ) + + @pytest.mark.parametrize("workflow_name", WORKFLOW_NAMES) def test_every_workflow_checkout_is_followed_by_relocation( workflow_name: str, From ffb5260c418d93d3d8202d514ca264755c47a370 Mon Sep 17 00:00:00 2001 From: leynos Date: Mon, 3 Aug 2026 17:02:40 +0200 Subject: [PATCH 2/2] Address mutation checkout review feedback (#363) Quote boolean-like values as strings so callers match the reusable workflow input contract. Document the caller-token boundary for private submodules, and require both caller checkout jobs to use `actions/checkout` while forwarding the input. --- .github/workflows/mutation-cargo.yml | 5 ++- docs/mutation-cargo-workflow.md | 40 ++++++++++--------- .../tests/test_mutation_workflow_shape.py | 13 ++++++ 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/.github/workflows/mutation-cargo.yml b/.github/workflows/mutation-cargo.yml index 625f3aeb..096e8a3e 100644 --- a/.github/workflows/mutation-cargo.yml +++ b/.github/workflows/mutation-cargo.yml @@ -70,11 +70,12 @@ on: description: >- How to check out the caller repository's git submodules, forwarded verbatim to actions/checkout's `submodules` input: - `false` (default, no submodules), `true` (top-level only) or + `'false'` (default, no submodules), `'true'` (top-level only) or `recursive`. Set this for callers that vendor a build-graph dependency as a git submodule (e.g. a path dependency under `third_party/`), whose directory must exist for the unmutated - baseline to build. + baseline to build. Submodules must be public or accessible with + the caller repository's github.token. type: string default: "false" diff --git a/docs/mutation-cargo-workflow.md b/docs/mutation-cargo-workflow.md index 925c271b..84322f1a 100644 --- a/docs/mutation-cargo-workflow.md +++ b/docs/mutation-cargo-workflow.md @@ -71,29 +71,31 @@ jobs: ## Inputs -| Input | Default | Purpose | -| ----------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `paths` | `src/,examples/,benches/` | Path prefixes belonging to the root target. | -| `extra-crate-dirs` | (empty) | Non-workspace crate directories mutated as separate targets. | -| `exclude-globs` | (empty) | Comma-separated `--exclude` globs. | -| `window-hours` | `25` | Detection window; keep one hour wider than the cadence. | -| `base-ref` | `origin/main` | Reference scanned for changes. | -| `timeout-multiplier` | `3` | Per-mutant timeout as a multiple of the baseline. | -| `timeout-minutes` | `300` | Per-job ceiling. | -| `shard-count` | `6` | Fan-out for full dispatch runs (scoped runs stay single-shard). | -| `cargo-mutants-version` | pinned | Tool version; the summary parser is validated against it. | -| `extra-args` | (empty) | Extra cargo-mutants arguments (shell-lexed), e.g. `--all-features`. | -| `setup-commands` | (empty) | Shell commands run before cargo-mutants in each mutants job (e.g. `sudo apt-get install -y mold` when the repo's `.cargo/config.toml` selects that linker). | -| `submodules` | `false` | How to check out the caller's git submodules, forwarded to `actions/checkout` (`false`, `true` or `recursive`). Set for callers that vendor a build-graph dependency as a submodule. | +| Input | Default | Purpose | +| ----------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `paths` | `src/,examples/,benches/` | Path prefixes belonging to the root target. | +| `extra-crate-dirs` | (empty) | Non-workspace crate directories mutated as separate targets. | +| `exclude-globs` | (empty) | Comma-separated `--exclude` globs. | +| `window-hours` | `25` | Detection window; keep one hour wider than the cadence. | +| `base-ref` | `origin/main` | Reference scanned for changes. | +| `timeout-multiplier` | `3` | Per-mutant timeout as a multiple of the baseline. | +| `timeout-minutes` | `300` | Per-job ceiling. | +| `shard-count` | `6` | Fan-out for full dispatch runs (scoped runs stay single-shard). | +| `cargo-mutants-version` | pinned | Tool version; the summary parser is validated against it. | +| `extra-args` | (empty) | Extra cargo-mutants arguments (shell-lexed), e.g. `--all-features`. | +| `setup-commands` | (empty) | Shell commands run before cargo-mutants in each mutants job (e.g. `sudo apt-get install -y mold` when the repo's `.cargo/config.toml` selects that linker). | +| `submodules` | `'false'` | How to check out the caller's git submodules, forwarded to `actions/checkout` (`'false'`, `'true'` or `recursive`). Set for callers that vendor a build-graph dependency as a submodule. | ## Notes -- Set `submodules: recursive` (or `true`) when the caller vendors a +- Set `submodules: recursive` (or `'true'`) when the caller vendors a build-graph dependency as a git submodule — for example a Cargo path - dependency under `third_party/`. The default checkout leaves the - submodule directory empty, so the unmutated baseline fails to build - (`failed to read .../Cargo.toml`) before any mutant runs. The input is - a no-op for callers without a `.gitmodules`. + dependency under `third_party/`. The default checkout leaves the submodule + directory empty, so the unmutated baseline fails to build + (`failed to read .../Cargo.toml`) before any mutant runs. The input is a + no-op for callers without a `.gitmodules`. Submodules must be public or + accessible with the caller repository's `github.token`; private secondary + repositories need their own checkout and least-privilege credentials. - The `cargo-mutants-version` default is pinned because the `outcomes.json` format is documented as unstable and the summary parser must diff --git a/workflow_scripts/tests/test_mutation_workflow_shape.py b/workflow_scripts/tests/test_mutation_workflow_shape.py index d4af59eb..df32c2bb 100644 --- a/workflow_scripts/tests/test_mutation_workflow_shape.py +++ b/workflow_scripts/tests/test_mutation_workflow_shape.py @@ -76,7 +76,20 @@ def test_caller_checkout_forwards_submodules_input() -> None: if step.get("name") == CALLER_CHECKOUT_STEP ] assert checkouts, "mutation-cargo.yml must check out the caller repository" + expected_jobs = {"detect", "mutants"} + actual_jobs = {job_name for job_name, _ in checkouts} + assert actual_jobs == expected_jobs, ( + "mutation-cargo.yml caller checkouts must appear in exactly the detect " + f"and mutants jobs, got {sorted(actual_jobs)!r}" + ) for job_name, step in checkouts: + uses = step.get("uses") + assert isinstance(uses, str), ( + f"mutation-cargo.yml:{job_name} caller checkout must set `uses`" + ) + assert uses.startswith("actions/checkout@"), ( + f"mutation-cargo.yml:{job_name} caller checkout must use actions/checkout" + ) params = step.get("with") assert isinstance(params, dict), ( f"mutation-cargo.yml:{job_name} caller checkout must set `with`"