Skip to content

feat(ci): mutation PR-time lane + mutants-pr wrapper (#182 PR 3)#233

Merged
EffortlessSteven merged 2 commits into
mainfrom
feat/mutation-scoping-and-mutants-pr-182
May 12, 2026
Merged

feat(ci): mutation PR-time lane + mutants-pr wrapper (#182 PR 3)#233
EffortlessSteven merged 2 commits into
mainfrom
feat/mutation-scoping-and-mutants-pr-182

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Third and final PR in the #182 sequence. Adds the label-gated PR-time mutation lane and the cargo xtask mutants-pr wrapper. Mutation testing stays explicitly off the default PR hot path — only PRs labeled mutation or full-ci exercise it.

What changed

  • cargo xtask mutants-pr --changed [--base origin/main] [--dry-run] (new) — thin wrapper around cargo-mutants. Computes git diff <base>...HEAD --name-only -- '*.rs', filters tests//benches/, then runs cargo mutants --no-shuffle --file <each>. --dry-run maps to cargo mutants --list. Refuses bare invocation: --changed is currently required so whole-workspace runs cannot accidentally enter the PR-time lane.
  • .github/workflows/mutation.yml — split into two jobs:
    • mutants-weekly (unchanged behaviour) — Sunday 04:00 UTC, workflow_dispatch, covers shipper-duration / shipper-types / shipper-config.
    • mutants-pr (new) — pull_request trigger with the label gate. Only runs when mutation or full-ci is present on the PR.
  • docs/ci/test-evidence-lanes.md — advisory-routed table gets a mutants-pr row; the targeted-mutation section documents local invocation patterns and when a maintainer should apply the label.

Why the weekly job's crate list stays unchanged

The trust-critical surface (shipper-core, shipper-encrypt, shipper-output-sanitizer, shipper-cargo-failure, shipper-sparse-index, shipper-registry, shipper-cli, shipper) is too expensive for a 60-minute job. Expanding coverage is its own rollout step — the policy this PR fixes is routing (label-gated PR + scheduled weekly), not coverage scope. The commit message and docs say this explicitly so the gap is visible.

Why cargo mutants non-zero exits are surfaced (not advisory)

Unlike ripr, which is purely a signal, mutation runs as a runtime backstop. When a maintainer applies the mutation label they've explicitly asked for execution-backed confirmation; if a mutant survives, that should fail the job. The label gate already prevents this from being a PR tax — once you opt in, the result is load-bearing.

Test plan

  • cargo build -p xtask — clean
  • cargo test -p xtask — 24/24 passing (three new: install_hint_mentions_cargo_install, args_defaults_are_explicit, changed_requires_the_flag)
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo fmt --all -- --check — clean
  • cargo xtask mutants-pr --changed --dry-run locally — exits success when no diff vs origin/main
  • cargo xtask check-workflow-surfaces --mode blocking-allowlist — clean (mutation.yml already receipted; no new workflows)
  • cargo xtask check-process-policy --mode blocking-allowlist — clean
  • cargo xtask check-network-policy --mode blocking-allowlist — clean
  • CI green (full nextest matrix; mutation.yml's PR job only fires if this PR carries the mutation label — it doesn't)

Scope explicitly excludes (deferred)

  • Expanding the weekly mutation crate list to the full trust-critical surface.
  • Auto-applying the mutation label based on ripr warning-level findings.
  • A --all mode on the wrapper (whole-workspace mutation has its own job already).

Refs #182. This is the third PR in the #182 sequence: advisory lane in #230, --base + projection in #231, badges in #232, mutation scoping here. #182 can close after this lands.

…PR 3)

Adds a label-gated PR-time mutation lane and the xtask wrapper it
invokes, keeping mutation off every PR's hot path while making it cheap
to opt in when warranted.

  cargo xtask mutants-pr --changed [--base origin/main] [--dry-run]

is a thin wrapper around `cargo-mutants` that:

  1. Refuses to run without `--changed`. Whole-workspace mutation is
     intentionally not part of the PR-time lane — that lives in the
     weekly schedule (see docs/ci/test-evidence-lanes.md).
  2. Computes `git diff <base>...HEAD --name-only -- '*.rs'`, filters
     out `tests/` and `benches/` paths (cargo-mutants only mutates
     production source).
  3. Runs `cargo mutants --no-shuffle --file <each-path>` (or `--list`
     when `--dry-run` is passed).
  4. If `cargo-mutants` is missing locally, prints install instructions
     and exits advisory-success rather than erroring. CI installs the
     tool before invoking.
  5. Surfaces `cargo mutants` non-zero exit codes — surviving mutants
     are load-bearing failures, unlike ripr's purely advisory findings.

`.github/workflows/mutation.yml` adds the `mutants-pr` job alongside
the existing `mutants-weekly` job. The PR job's gate:

  if: github.event_name == 'pull_request' && (
    contains(github.event.pull_request.labels.*.name, 'mutation') ||
    contains(github.event.pull_request.labels.*.name, 'full-ci')
  )

The weekly job's crate list stays unchanged (shipper-duration,
shipper-types, shipper-config). Expanding it to the full trust-critical
surface (shipper-core, shipper-encrypt, etc.) is too expensive for a
60-minute job and is its own future rollout step; the policy stays
about routing, not coverage scope.

  xtask/src/mutants.rs          new — 3 unit tests
  xtask/src/main.rs             `mutants-pr` subcommand wiring
  .github/workflows/mutation.yml   two-job split (mutants-weekly +
                                   mutants-pr)
  docs/ci/test-evidence-lanes.md   advisory-routed table + targeted
                                   mutation section updated
  CHANGELOG.md                  Added entry

Tests:

  mutants::tests::install_hint_mentions_cargo_install    (new)
  mutants::tests::args_defaults_are_explicit             (new)
  mutants::tests::changed_requires_the_flag              (new)

24/24 tests passing.

Refs #182 (this is the third PR in the #182 sequence: advisory lane in
#230, --base + projection in #231, badges in #232, mutation scoping
here).
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@EffortlessSteven has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 45 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ac1ab92b-791c-4985-9035-01fd97d14f73

📥 Commits

Reviewing files that changed from the base of the PR and between 9598b14 and 1c59e40.

📒 Files selected for processing (5)
  • .github/workflows/mutation.yml
  • CHANGELOG.md
  • docs/ci/test-evidence-lanes.md
  • xtask/src/main.rs
  • xtask/src/mutants.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mutation-scoping-and-mutants-pr-182

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@factory-droid

factory-droid Bot commented May 12, 2026

Copy link
Copy Markdown

Droid finished @EffortlessSteven's task —— View job


Droid Review — PR #233 ✅ Approved

Validation: 0 candidate comments validated; 0 approved; 0 rejected.

Summary: LGTM — the PR cleanly adds a label-gated PR-time mutation lane and the corresponding xtask wrapper. The --changed guard prevents accidental whole-workspace runs, the workflow label gate keeps mutation off the default PR hot path, and the advisory-success exit when cargo-mutants is missing is intentional for local development. No correctness, logic, or security issues found in the diff.

Inspected surfaces:

  • .github/workflows/mutation.yml — new mutants-pr job with label gate
  • xtask/src/mutants.rs — new wrapper around cargo-mutants with --changed scoping
  • docs/ci/test-evidence-lanes.md — updated lane map and mutation documentation
  • CHANGELOG.md — entry for mutation testing PR-time lane

Checks performed:

  • Reviewed workflow trigger conditions (pull_request types, label gate)
  • Verified --changed guard prevents bare whole-workspace invocation
  • Confirmed cargo_mutants_available() advisory-success behavior for local dev
  • Verified git diff <base>...HEAD three-dot form for correct divergence detection
  • Checked test coverage for new xtask module

@factory-droid factory-droid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No inline comments — validation found no candidate comments to post.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7a809bbd3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread xtask/src/mutants.rs
Comment on lines +83 to +84
let mut cmd = Command::new("cargo");
cmd.arg("mutants").arg("--no-shuffle");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Select workspace packages before applying file filters

When the label-gated lane runs from the repository root, this command does not pass --workspace or package selectors, so cargo-mutants uses the workspace defaults before applying these --file filters. Observed in Cargo.toml, this workspace has default-members = ["crates/shipper", "crates/shipper-cli"], while the documented targeted lane is meant to cover changes in crates like shipper-core, shipper-encrypt, and other trust-critical non-default members. The cargo-mutants workspace docs state that package selection in a virtual workspace uses default-members when present and that --file is combined with that selection, so a labeled PR that changes only crates/shipper-core/src/... can list the changed file but generate no mutants for it. Add --workspace or infer/pass the changed packages so the PR-time lane actually exercises non-default crates. Validation: cargo xtask mutants-pr --changed --dry-run --base <base> on a branch touching only crates/shipper-core/src/..., then confirm mutants are listed after the package-selection fix. Reference: https://mutants.rs/workspaces.html

Useful? React with 👍 / 👎.

`<REF>` inside a doc comment is parsed by rustdoc as an invalid HTML tag
under `-D rustdoc::invalid-html-tags` (implied by RUSTDOCFLAGS=-D
warnings on the CI Documentation job). Wrap the whole Behaviour block in
a ```text fence so the angle brackets are literal.
@factory-droid

factory-droid Bot commented May 12, 2026

Copy link
Copy Markdown

Droid finished @EffortlessSteven's task —— View job


Droid Review — PR #233 ✅ Approved

Validation: 0 candidate comments validated; 0 approved; 0 rejected.

Summary: LGTM — the PR cleanly adds a label-gated PR-time mutation lane and the corresponding xtask wrapper. The --changed guard prevents accidental whole-workspace runs, the workflow label gate keeps mutation off the default PR hot path, and the advisory-success exit when cargo-mutants is missing is intentional for local development. No correctness, logic, or security issues found in the diff.

Inspected surfaces:

  • .github/workflows/mutation.yml — new mutants-pr job with label gate
  • xtask/src/mutants.rs — new wrapper around cargo-mutants with --changed scoping
  • docs/ci/test-evidence-lanes.md — updated lane map and mutation documentation
  • CHANGELOG.md — entry for mutation testing PR-time lane

@EffortlessSteven EffortlessSteven merged commit 9cbde66 into main May 12, 2026
23 checks passed
@EffortlessSteven EffortlessSteven deleted the feat/mutation-scoping-and-mutants-pr-182 branch May 12, 2026 13:22
EffortlessSteven added a commit that referenced this pull request May 12, 2026
…) (#237)

Stabilises `concurrent_version_exists_checks` (and any other test using
`with_multi_server`) on slow macOS CI runners. Hit three times in a
single rollout session (#233, #234, #236) as `version_exists: registry
request failed -> operation timed out` against the local tiny_http
mock.

Root cause: the helper's accept loop blocked on `handler(req)` until
each response was fully written before returning to `recv_timeout`.
With 5 concurrent reqwest clients hitting the same loopback socket,
the remaining clients sat in the kernel's TCP backlog long enough to
exceed reqwest's default OS-level connect timeout. Windows and Linux
runners process the queue fast enough to mask the bug; macOS does
not.

Fix: spawn one worker thread per accepted request and let the accept
loop return to `recv_timeout` immediately. The accept loop still
serialises on `recv_timeout` (tiny_http requires that), but handlers
run in parallel, so the kernel's listen queue drains as fast as
connections arrive.

Other changes:
  - `recv_timeout` bumped from 30s to 60s for additional headroom.
  - Trait bound on the handler closure goes from `Fn + Send + 'static`
    to `Fn + Send + Sync + 'static` (required to wrap the handler in
    `Arc` for clone-into-workers). All existing call sites use
    closures that already satisfy `Sync`.
  - The accept thread joins worker threads before returning so any
    panic in a handler surfaces in CI.

cargo test -p shipper-registry --lib passes 258/258 locally (Windows).
The fix targets a macOS-specific timing bug, so CI is the real
verification.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant