1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
name: CI
on:
push:
branches:
pull_request:
env:
CARGO_TERM_COLOR: always
# Read-only unless a job asks for more. This repository's default token
# is read-write, and a workflow that declares nothing inherits it, so every
# job here would otherwise hold a write token beside actions it does not
# pin.
permissions:
contents: read
jobs:
test:
name: test / ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: verify the packaged family scripts
# A consumer with no checkout of this repository gets these from the
# published crate, through cargo, so the package must carry them.
shell: bash
run: |
files="$(cargo package --locked --list)"
for script in core.sh output-budget.sh test-floor.sh semver-check.sh family-check.sh guest-rust-toolchain.sh stage-siblings.sh guest-rust-run.sh ci-gate.sh package-cli.sh; do
grep -Fqx "scripts/$script" <<<"$files" || {
echo "package omits scripts/$script" >&2
exit 1
}
done
- run: cargo build --locked --all-targets
# AM_FS_CORE_ALLOW_UNPRIVILEGED_SKIP ACKNOWLEDGES A REAL GAP.
# tests/device_node_size.rs covers the Linux device-size probe's
# success path with a loop device, and losetup needs root, which
# this runner is not. Without this the test FAILS rather than
# skipping quietly -- libtest discards a passing test's output, so
# a printed "skipped" line reaches nobody.
#
# What is still covered on this runner: the probe's FAILURE path,
# via /dev/zero, which needs no privileges. What is not: the
# success path, i.e. BLKGETSIZE64 returning a real size. macOS
# covers its own equivalent in full, unprivileged, via hdiutil.
#
# To close this rather than acknowledge it, run the suite as root.
# EXPECT_OVERFLOW_CHECKS ARMS THE ONE CHECK A TEXT SCAN CANNOT DO.
# This run has no `--release`, so overflow-checks are on, and it is
# what makes every arithmetic test in this crate mean anything.
# Measured: a temporary `black_box(250u8) + black_box(10u8)`
# panicked under `cargo test --locked` and passed under `cargo test
# --locked --release`.
#
# The variable tells `overflow_checks::the_build_the_gate_asked_to
# _check_does_check` in src/lib.rs that THIS is the run that must
# trap an overflow -- asking the build directly rather than
# inferring it from which spellings of "disabled" happen to be
# absent from Cargo.toml. It is declared here rather than inline as
# `VAR=1 cargo test` because this job also runs on windows-latest,
# where that prefix is a PowerShell syntax error.
#
# Do not add `--release` to this run, and do not drop the variable:
# tests/ci_profile.rs reads this file and fails if no `cargo test`
# in it runs without `--release` while setting this. See #90.
- name: test (quiet, budgeted, with an executed-test floor)
shell: bash
run: |
bash scripts/tier.sh "test (debug)" debug 750 50000 -- cargo test --locked --all-targets --features cli
bash scripts/core.sh test-floor debug 330
env:
AM_FS_CORE_ALLOW_UNPRIVILEGED_SKIP: "1"
EXPECT_OVERFLOW_CHECKS: "1"
- name: keep the full test transcript
if: always()
uses: actions/upload-artifact@v4
with:
name: tier-logs-test-${{ matrix.os }}
path: tmp/logs/
if-no-files-found: warn
retention-days: 7
# Twice: once as a consumer's static library builds this crate (no
# features), and once with the `cli` feature the tools turn on, so a
# lint in either shape is caught.
- run: cargo clippy --locked --all-targets -- -D warnings
- run: cargo clippy --locked --all-targets --features cli -- -D warnings
# `cargo doc` EXITS 0 ON WARNINGS, so an unresolved intra-doc link
# reaches main unseen unless warnings are denied here. Two did (#88).
# Declared through `env:` rather than an inline `VAR=... cargo doc`
# because this job also runs on windows-latest.
- run: cargo doc --no-deps --locked --features cli
env:
RUSTDOCFLAGS: "-D warnings"
fmt:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
components: rustfmt
- run: cargo fmt --check
- name: the one required check stands for every job
run: bash scripts/core.sh ci-gate
- name: the agent guide carries the shared block
# AGENTS.md embeds agent-core, byte-identical with every sibling. This
# runs the script directly rather than through `chore lint`, because
# CI here does not install chore -- a guard wired only into a task
# nothing runs is a guard that reports protection it is not providing.
run: scripts/agents-core-check.sh
- name: the shell tests
# Every tests/scripts/*.sh, so a new one is gated by being added. Plain
# scripts rather than cargo tests, so they cannot count towards the
# executed-test floor.
shell: bash
run: for t in tests/scripts/*.sh; do bash "$t" || exit 1; done
coverage:
name: coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
components: llvm-tools-preview
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@cargo-llvm-cov
# THE SAME ACKNOWLEDGEMENT AS THE `test` JOB, and it has to be
# here rather than inherited: this job runs the suite too, under
# llvm-cov, and it is not root either. Without it
# tests/device_node_size.rs fails here while `test` passes, which
# is what happened. `tests/ci_acknowledges_the_privilege_gap.rs`
# now fails if a job runs the suite without saying this.
- name: coverage (quiet, budgeted, fail under 90%)
shell: bash
run: |
bash scripts/tier.sh coverage coverage 800 60000 -- cargo llvm-cov --features cli --html --output-dir target/llvm-cov --fail-under-lines 90
bash scripts/core.sh test-floor coverage 330
env:
AM_FS_CORE_ALLOW_UNPRIVILEGED_SKIP: "1"
- name: keep the coverage transcript and report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-evidence
path: |
tmp/logs/
target/llvm-cov/html/
if-no-files-found: warn
retention-days: 14
semver:
# `chore check:semver`, spelled out because CI here does not install
# chore: this tree's public API against the newest version on crates.io,
# refusing a break the version in Cargo.toml does not declare. Why, and
# what it cannot see, is at the top of scripts/semver-check.sh.
name: semver (public API against crates.io)
runs-on: ubuntu-latest
timeout-minutes: 30
env:
# The version scripts/semver-check.sh expects. A prebuilt binary,
# checksum-verified by the action, rather than a ten-minute build.
CARGO_SEMVER_CHECKS_VERSION: "0.50.0"
# A failing tier prints only its verdict by default. Here the tail IS
# the answer -- which items broke -- so it is printed in full.
OUTPUT_BUDGET_FAIL_TAIL: "200"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks@${{ env.CARGO_SEMVER_CHECKS_VERSION }}
- name: semver (quiet, budgeted)
shell: bash
run: bash scripts/tier.sh semver semver 40 4000 -- bash scripts/core.sh semver-check
ci-ok:
name: ci-ok
# THE ONE REQUIRED CHECK (#154). Branch protection used to name five
# contexts by hand -- `fmt`, `coverage` and the three `test / <os>`
# matrix legs. Requiring this gate instead means a runner can be added
# to the matrix, or `coverage` renamed, or a job split in two, without
# leaving a required check that never reports -- which GitHub reads as
# permanently pending, with enforce_admins on and nothing to point at.
# It is also the only shape in which a job-level `if:` is safe.
#
# It runs no tests itself, deliberately: it is a claim about the other
# jobs, so it must not be able to pass work of its own off as theirs.
# That is also why tests/ci_acknowledges_the_privilege_gap.rs does not
# ask it for AM_FS_CORE_ALLOW_UNPRIVILEGED_SKIP -- it never runs the
# suite, so it has no privilege gap to acknowledge.
if: always()
needs:
runs-on: ubuntu-latest
steps:
- name: Every job ran and succeeded
env:
NEEDS: ${{ toJSON(needs) }}
run: |
set -euo pipefail
echo "$NEEDS" | jq -r 'to_entries[] | "\(.key): \(.value.result)"'
bad="$(echo "$NEEDS" | jq -r '[to_entries[] | select(.value.result != "success") | .key] | join(", ")')"
if [ -n "$bad" ]; then
echo "::error::not green: $bad (failed, cancelled or skipped)"
exit 1
fi
echo "all required jobs succeeded"