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
6 changes: 3 additions & 3 deletions .anvil.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version = 1
tool = "anvil"
tool_version = "0.13.0"
catalog_checksum = "sha256:dfaf58166631905ff5990c0dbf4741a15c9db94d33804212faad51d7f9c3790b"
catalog_checksum = "sha256:09e913b54324919fbe67ed027b40c3e7b3e53cd9e0698f72f24cae686332d451"

[[file]]
path = ".anvil/container/Dockerfile.dockerignore"
Expand All @@ -17,7 +17,7 @@ checksum = "sha256:9940d1947482150ac08fcb9b4150da99f5ae60642f4caeea137577ce0e709

[[file]]
path = ".github/actions/anvil-run-group/action.yml"
checksum = "sha256:d564a0ce424cda58c8f5b2476cf0c8b36e759afbdf8227608bd1f3fc74913fdf"
checksum = "sha256:6f61042746607c4afe3fcef1b827461f848cb0ebfc2e69e1eda9f17e889f04cf"

[[file]]
path = ".github/actions/anvil-setup/action.yml"
Expand Down Expand Up @@ -161,7 +161,7 @@ checksum = "sha256:adc3b25f4f0b563695335f85870d9f319606c5ca80fa3d118cf221388f4bb

[[file]]
path = "justfiles/anvil/checks/mutants-diff.just"
checksum = "sha256:b8e32d8c03f3c68a2b4b9eba7998ccabb36d29abff39f21d46005fdc281bb7c3"
checksum = "sha256:fa74227f1c6121d68bb37b054e890e8d7f329e2b15caa624b35da564fec13d7f"

[[file]]
path = "justfiles/anvil/checks/mutants-full.just"
Expand Down
65 changes: 65 additions & 0 deletions .github/actions/anvil-run-group/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,59 @@ runs:
# Capture the group result before propagating failure so the
# best-effort reporter can consume both outputs.
log="$RUNNER_TEMP/anvil-$ANVIL_GROUP.log"
resource_pid=""
if [[ "$ANVIL_GROUP" == "pr-mutants" && "$RUNNER_OS" == "Linux" ]]; then
# Mutants intentionally crash. Core files are not used by this check.
ulimit -c 0
(
sleep_pid=""
trap 'if [[ -n "$sleep_pid" ]]; then kill "$sleep_pid"; fi; exit 143' TERM
while true; do
{
sample_time="$(date -u +%FT%TZ)" || echo "::warning::Mutation clock probe failed"
echo "[anvil-resources] $sample_time"
free -m || echo "::warning::Mutation memory probe failed"
df -m . || echo "::warning::Mutation disk probe failed"
ps -eo pid,ppid,rss,pcpu,comm --sort=-rss | sed -n '1,12p' || echo "::warning::Mutation process probe failed"
if [[ -f mutants.out/debug.log ]]; then
tail -n 1 mutants.out/debug.log || echo "::warning::Mutation debug-log probe failed"
fi
} 2>&1 | tee -a "$RUNNER_TEMP/anvil-$ANVIL_GROUP-resources.log" || echo "::warning::Mutation resource log write failed"
sleep 30 &
sleep_pid=$!
wait "$sleep_pid"
sleep_pid=""
done
) &
resource_pid=$!
stop_resources() {
if ! kill "$resource_pid" 2>/dev/null; then
echo "::warning::Mutation resource sampler exited before cleanup"
fi
wait "$resource_pid" || {
sampler_status=$?
if [[ "$sampler_status" != 143 ]]; then
echo "::warning::Mutation resource sampler exited with status $sampler_status"
fi
}
}
core_pattern=""
restore_core_pattern=false
cleanup_mutation() {
stop_resources
if [[ "$restore_core_pattern" == true ]]; then
sudo -n sysctl -w "kernel.core_pattern=$core_pattern" || echo "::warning::Could not restore mutation core-dump routing"
fi
}
trap cleanup_mutation EXIT
if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
# Linux ignores ulimit for piped handlers such as Ubuntu's apport.
# Only ephemeral hosted runners may change this host-wide setting.
core_pattern="$(sysctl -n kernel.core_pattern)"
restore_core_pattern=true
sudo -n sysctl -w kernel.core_pattern=core
fi
fi
set +e
just "anvil-$ANVIL_GROUP" 2>&1 | tee "$log"
# Preserve Just's status rather than tee's pipeline status.
Expand All @@ -74,6 +127,18 @@ runs:
echo "exit_code=$status" >> "$GITHUB_OUTPUT"
exit "$status"

- name: Upload mutation diagnostics
if: always() && inputs.group == 'pr-mutants'
continue-on-error: true
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: anvil-mutants-${{ runner.os }}-${{ runner.arch }}-${{ github.run_attempt }}
path: |
${{ runner.temp }}/anvil-pr-mutants*.log
${{ github.workspace }}/mutants.out/
retention-days: 3
if-no-files-found: warn

# Reporting is supplemental: run after success or failure, but never let
# an API outage determine the authoritative workflow-job result.
- name: Publish supplemental Anvil commit status
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-anvil/docs/design/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ available.

| Check | Invocation | Source |
|-----------|-----------------------------------------------------------------------------|--------|
| `mutants` | Resolves the base ref, writes `git diff <base>` against the working tree to a temporary unified-diff file, then runs `cargo mutants --in-diff <file> --no-shuffle --jobs 0`. Self-skips on aarch64-pc-windows-msvc where cargo-mutants doesn't build; other ARM legs run normally. | oxidizer-github |
| `mutants` | Resolves the base ref, writes `git diff <base>` against the working tree to a temporary unified-diff file, then runs `cargo mutants --in-diff <file> --no-shuffle --jobs 0 --caught --unviable`. Self-skips on aarch64-pc-windows-msvc where cargo-mutants doesn't build; other ARM legs run normally. | oxidizer-github |

The mutants check requires a base ref: locally the recipe resolves `BASE_REF` (if set), then `origin/main`, then `origin/master`, then errors out. GitHub passes `${{ github.event.pull_request.base.sha }}` as `BASE_REF`; on ADO the shared resolver reads `$(System.PullRequest.TargetBranch)` from the environment.

Expand Down
21 changes: 21 additions & 0 deletions crates/cargo-anvil/docs/design/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,27 @@ workflows. Anvil does not emit or support public per-group action paths.

### Failure attribution and commit statuses

PR mutation jobs print caught and unviable mutations as they complete. On Linux,
the shared action also streams memory, disk, process-resource usage and the
latest cargo-mutants debug record every 30 seconds. It disables core dumps for
the mutation group: intentionally crashing mutations still fail normally, but
do not generate unused core files. On ephemeral GitHub-hosted Linux runners it
also temporarily replaces piped crash-handler routing with file routing:
Linux ignores the core-size limit for piped handlers, and repeated aborts can
exhaust memory with Ubuntu's `apport` processes. The original routing is
restored on exit. Configuration failure stops the group; restoration failure
emits a warning without changing the test result. Self-hosted runners do not
change this host-wide setting. Other groups retain their existing limits.

After success or failure, mutation jobs attempt to upload the group log,
resource samples and `mutants.out` as an OS/architecture-specific artifact with
three-day retention. Artifact publication is supplemental and cannot override
the mutation exit status. A runner that disappears before publication can still
lose its local files; streamed samples improve diagnosis but do not guarantee
recovery after runner loss. The resource sampler is stopped when the run step
exits, including on a failed mutation check. Probe and resource-log write failures
emit warnings without preventing later samples or changing the group's result.

GitHub fixes workflow job names before a job runs, so a matrix check named
`PR Job / Check Group: Fast Checks (linux)` cannot rename itself after
discovering that `anvil-license-headers` failed. The checks UI groups that name
Expand Down
65 changes: 65 additions & 0 deletions crates/cargo-anvil/templates/github/run-group-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,59 @@ runs:
# Capture the group result before propagating failure so the
# best-effort reporter can consume both outputs.
log="$RUNNER_TEMP/anvil-$ANVIL_GROUP.log"
resource_pid=""
if [[ "$ANVIL_GROUP" == "pr-mutants" && "$RUNNER_OS" == "Linux" ]]; then
# Mutants intentionally crash. Core files are not used by this check.
ulimit -c 0
(
sleep_pid=""
trap 'if [[ -n "$sleep_pid" ]]; then kill "$sleep_pid"; fi; exit 143' TERM
while true; do
{
sample_time="$(date -u +%FT%TZ)" || echo "::warning::Mutation clock probe failed"
echo "[anvil-resources] $sample_time"
free -m || echo "::warning::Mutation memory probe failed"
df -m . || echo "::warning::Mutation disk probe failed"
ps -eo pid,ppid,rss,pcpu,comm --sort=-rss | sed -n '1,12p' || echo "::warning::Mutation process probe failed"
if [[ -f mutants.out/debug.log ]]; then
tail -n 1 mutants.out/debug.log || echo "::warning::Mutation debug-log probe failed"
fi
} 2>&1 | tee -a "$RUNNER_TEMP/anvil-$ANVIL_GROUP-resources.log" || echo "::warning::Mutation resource log write failed"
sleep 30 &
sleep_pid=$!
wait "$sleep_pid"
sleep_pid=""
done
) &
resource_pid=$!
stop_resources() {
if ! kill "$resource_pid" 2>/dev/null; then
echo "::warning::Mutation resource sampler exited before cleanup"
fi
wait "$resource_pid" || {
sampler_status=$?
if [[ "$sampler_status" != 143 ]]; then
echo "::warning::Mutation resource sampler exited with status $sampler_status"
fi
}
}
core_pattern=""
restore_core_pattern=false
cleanup_mutation() {
stop_resources
if [[ "$restore_core_pattern" == true ]]; then
sudo -n sysctl -w "kernel.core_pattern=$core_pattern" || echo "::warning::Could not restore mutation core-dump routing"
fi
}
trap cleanup_mutation EXIT
if [[ "${RUNNER_ENVIRONMENT:-}" == "github-hosted" ]]; then
# Linux ignores ulimit for piped handlers such as Ubuntu's apport.
# Only ephemeral hosted runners may change this host-wide setting.
core_pattern="$(sysctl -n kernel.core_pattern)"
restore_core_pattern=true
sudo -n sysctl -w kernel.core_pattern=core
fi
fi
set +e
just "anvil-$ANVIL_GROUP" 2>&1 | tee "$log"
# Preserve Just's status rather than tee's pipeline status.
Expand All @@ -74,6 +127,18 @@ runs:
echo "exit_code=$status" >> "$GITHUB_OUTPUT"
exit "$status"

- name: Upload mutation diagnostics
if: always() && inputs.group == 'pr-mutants'
continue-on-error: true
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: anvil-mutants-${{ runner.os }}-${{ runner.arch }}-${{ github.run_attempt }}
path: |
${{ runner.temp }}/anvil-pr-mutants*.log
${{ github.workspace }}/mutants.out/
retention-days: 3
if-no-files-found: warn

# Reporting is supplemental: run after success or failure, but never let
# an API outage determine the authoritative workflow-job result.
- name: Publish supplemental Anvil commit status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ anvil-mutants-diff: anvil-mutants-diff-validate-prereqs anvil-impact
# not a behaviour change for it.
git diff "$base" --output=$diff_path
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& cargo {{_anvil_stable_toolchain_args}} mutants --in-diff $diff_path --no-shuffle --jobs 0 {{_anvil_mutants_config_arg}}
& cargo {{_anvil_stable_toolchain_args}} mutants --in-diff $diff_path --no-shuffle --jobs 0 --caught --unviable {{_anvil_mutants_config_arg}}
Comment thread
ralfbiedert marked this conversation as resolved.
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# --- pr-mutants members ---
Expand Down
Loading
Loading