diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32563bc..22ab41e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,8 @@ # make the architecture in docs/adr/ true rather than aspirational. # - A single `ci-required` aggregation gate is the one status context branch protection # references, so the ruleset never desyncs as jobs are added or renamed. +# - Pull requests mutation-test only changed Rust product lines; weekly/manual and release +# gates retain exhaustive, sharded coverage. name: CI @@ -195,6 +197,29 @@ jobs: - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - run: just coverage + mutation-smoke: + name: mutation smoke (changed Rust product lines) + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + with: + tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - run: just mutants-smoke '${{ github.event.pull_request.base.sha }}' + - if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: mutants-smoke + path: mutants.out/ + if-no-files-found: ignore + retention-days: 14 + purity: name: purity (no std, I/O, font, or float in the core) runs-on: ubuntu-latest @@ -430,6 +455,7 @@ jobs: - wasm - fuzz - coverage + - mutation-smoke - purity - design - semver diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index e7e7b9a..91ecc06 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -1,22 +1,14 @@ # SPDX-FileCopyrightText: 2026 jlreq contributors # SPDX-License-Identifier: MIT OR Apache-2.0 -# Pull requests smoke-test changed Rust code. Weekly/manual runs cover both handwritten -# products in four shards. Generated tables and five exact, proof-backed equivalent mutants -# are the only configured exclusions and are pinned in docs/mutation-ledger.toml; a missed -# or timed-out mutant fails the workflow. +# Weekly/manual runs cover both handwritten products in four shards. Pull-request smoke +# mutation belongs to CI so its result feeds the single required aggregation gate there. +# Generated tables and five exact, proof-backed equivalent mutants are the only configured +# exclusions and are pinned in docs/mutation-ledger.toml; a missed or timed-out mutant fails +# the workflow. name: Mutants on: - pull_request: - paths: - - "**/*.rs" - - "**/Cargo.toml" - - "Justfile" - - ".cargo/mutants.toml" - - "docs/mutation-ledger.toml" - - "scripts/verify-mutation-ledger.sh" - - ".github/workflows/mutants.yml" workflow_dispatch: schedule: - cron: "38 4 * * 4" @@ -29,32 +21,8 @@ concurrency: cancel-in-progress: true jobs: - smoke: - name: changed-surface smoke - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 - with: - tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - run: just mutants-smoke '${{ github.event.pull_request.base.sha }}' - - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: mutants-smoke - path: mutants.out/ - if-no-files-found: ignore - retention-days: 14 - full: name: full (${{ matrix.crate }}, ${{ matrix.shard }}) - if: github.event_name != 'pull_request' runs-on: ubuntu-latest timeout-minutes: 90 strategy: diff --git a/Justfile b/Justfile index 7b97527..d92a1c5 100644 --- a/Justfile +++ b/Justfile @@ -305,7 +305,7 @@ mutants crate="" shard="": # workflows run the complete sharded gate above. mutants-smoke base: sh scripts/verify-mutation-ledger.sh - cargo mutants --workspace --in-diff {{base}} --test-tool cargo --minimum-test-timeout 120 --no-times --colors=never -j 4 + sh scripts/run-mutation-smoke.sh {{ quote(base) }} # Hold generated and equivalent-mutant exclusions to their individual source hashes and # require every cargo-mutants regex to have one proof in the reviewable ledger. diff --git a/scripts/run-mutation-smoke.sh b/scripts/run-mutation-smoke.sh new file mode 100644 index 0000000..efb71a0 --- /dev/null +++ b/scripts/run-mutation-smoke.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# SPDX-FileCopyrightText: 2026 jlreq contributors +# SPDX-License-Identifier: MIT OR Apache-2.0 + +set -eu + +if [ "$#" -ne 1 ]; then + echo "usage: $0 BASE_COMMIT" >&2 + exit 2 +fi + +base=$1 +if ! base_commit=$(git rev-parse --verify --end-of-options "${base}^{commit}"); then + echo "mutation smoke: base is not a commit: $base" >&2 + exit 2 +fi + +mkdir -p target +diff_file=target/mutants-smoke.diff +git diff --no-ext-diff --unified=0 --output="$diff_file" \ + "${base_commit}...HEAD" -- '*.rs' + +if [ ! -s "$diff_file" ]; then + echo "mutation smoke: no changed Rust lines" + exit 0 +fi + +cargo mutants -p jlreq -p jlreq-conformance --in-diff "$diff_file" \ + --test-tool cargo \ + --minimum-test-timeout 120 \ + --no-times \ + --colors=never \ + -j 4