Skip to content

Commit a73900f

Browse files
authored
ci: select affected crates for Rust tests (#70)
* xtask: add affected-crate selector for CI test selection Within the Rust CI jobs a one-line change in a single leaf crate still rebuilds and re-tests the whole 15-member workspace. Add `cargo xtask affected`, which maps changed files (over a `base...HEAD` range) to their owning crate via `cargo metadata`, expands through the reverse-dependency graph, and prints the result as cargo package-selection flags. Selection is conservative: any change that can't be confined to specific crates (Cargo.lock, the toolchain file, the workspace root manifest, CI config, or any unrecognized path) falls back to `--workspace`, so a test that should run is never skipped. Docs-only changes select nothing. A workspace member at the repository root is rejected, since it would own every file and defeat that fallback. Reuses cargo metadata and serde_json (already deps); no new third-party dependency. The pure decision logic is unit-tested without git or cargo. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com> * ci: run affected-only tests via a dedicated nextest job Split unit/integration tests out of build-and-test into a `test` job that runs `cargo nextest run` over only the crates a change can reach — the output of `cargo xtask affected` (changed crates + transitive dependents) — instead of the whole workspace. build-and-test keeps building the runtime binaries its smoke/acceptance steps need (those hard-fail on a missing engine binary), so the test narrowing can't starve them; clippy --all-targets remains the compile check for non-binary targets. Merge-queue safe: narrowing is applied only on pull_request. On push and in the merge queue the selection is forced to --workspace, so main and queued PRs always run the full suite and no required check is starved. The full Windows suite runs the whole workspace on every change, backstopping any crate a graph-based selection could miss. PR and push builds use separate cache lanes so the differing feature resolutions don't thrash. Third-party actions added here are pinned to commit SHAs. docs/testing.md documents the new command. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com> --------- Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
1 parent fc2902d commit a73900f

4 files changed

Lines changed: 599 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ jobs:
118118
- uses: actions-rust-lang/setup-rust-toolchain@v1
119119
if: needs.changes.outputs.heavy == 'true'
120120

121+
# Build the runtime binaries the smoke + acceptance steps below need (every
122+
# engine binary; smoke hard-fails on a missing one). Unit/integration tests
123+
# run in the dedicated `test` job on only the crates a change can affect;
124+
# `clippy --all-targets` is the compile-check for the non-binary targets, so
125+
# dropping `--all-targets` here loses no coverage.
121126
- name: Build
122127
if: needs.changes.outputs.heavy == 'true'
123-
run: cargo build --workspace --all-targets
124-
125-
- name: Test
126-
if: needs.changes.outputs.heavy == 'true'
127-
run: cargo test --workspace --all-targets
128+
run: cargo build --workspace
128129

129130
- name: Local no-fallback smoke
130131
if: needs.changes.outputs.heavy == 'true'
@@ -150,6 +151,76 @@ jobs:
150151
if: needs.changes.outputs.heavy == 'true'
151152
run: ./scripts/acceptance-install-upgrade-tui-uninstall.sh
152153

154+
# Fine-grained test selection (the second half of smarter CI; the path-based
155+
# job skip is the `changes` job above). Runs cargo's unit + integration tests
156+
# for only the crates a change can reach — the changed crates plus their
157+
# transitive dependents, computed by `cargo xtask affected` from the workspace
158+
# dependency graph — instead of the whole workspace.
159+
#
160+
# Merge-queue safety: the narrowing happens ONLY on pull_request. On push and
161+
# in the merge queue the selection is forced to `--workspace`, so post-merge
162+
# `main` and every queued PR run the full suite — a required check is never
163+
# starved and the queue cannot stall. The full Windows suite
164+
# (windows-build-and-test) also runs the whole workspace on every change, so it
165+
# backstops any crate a graph-based selection could miss (e.g. an integration
166+
# test that drives another crate only at runtime).
167+
test:
168+
name: Test (affected crates)
169+
runs-on: ubuntu-latest
170+
# Same lint gate as the OS build jobs: don't spend a test cycle until the
171+
# cheap checks are green.
172+
needs: [changes, clippy, prek]
173+
steps:
174+
- uses: actions/checkout@v6
175+
with:
176+
# `cargo xtask affected` diffs origin/<base>...HEAD, so it needs the
177+
# base history and the real PR head (not the synthetic merge ref).
178+
fetch-depth: 0
179+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
180+
181+
- name: Install native build deps
182+
if: needs.changes.outputs.rust == 'true'
183+
run: sudo apt-get update && sudo apt-get install -y pkg-config libcap-dev
184+
185+
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
186+
if: needs.changes.outputs.rust == 'true'
187+
with:
188+
# Keep the PR (subset) and push/merge (`--workspace`) builds in separate
189+
# cache lanes. A subset build resolves Cargo features differently than
190+
# the whole workspace, so a shared cache would make the two evict and
191+
# recompile each other's artifacts on every alternation.
192+
cache-key: ${{ github.event_name == 'pull_request' && 'affected-pr' || 'full' }}
193+
194+
- name: Install cargo-nextest
195+
if: needs.changes.outputs.rust == 'true'
196+
uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2.82.6
197+
with:
198+
tool: nextest
199+
200+
- name: Select affected crates
201+
id: sel
202+
if: needs.changes.outputs.rust == 'true'
203+
run: |
204+
# The `cargo xtask` alias builds xtask in release (target/release), so
205+
# computing the selection doesn't perturb the debug feature resolution
206+
# the nextest build below uses.
207+
if [ "${{ github.event_name }}" = "pull_request" ]; then
208+
sel=$(cargo xtask affected --base "origin/${{ github.base_ref }}")
209+
else
210+
sel="--workspace"
211+
fi
212+
echo "affected selection: ${sel:-<no rust crates changed>}"
213+
echo "sel=$sel" >> "$GITHUB_OUTPUT"
214+
215+
- name: Test (nextest)
216+
# Skip cleanly when nothing Rust-relevant resolved to a crate, so the
217+
# required check still reports success.
218+
if: needs.changes.outputs.rust == 'true' && steps.sel.outputs.sel != ''
219+
# `sel` is intentionally unquoted: it expands to either `--workspace` or
220+
# several `-p <crate>` flags that must word-split into separate argv
221+
# entries. The values are crate names from our own tool, not user input.
222+
run: cargo nextest run ${{ steps.sel.outputs.sel }}
223+
153224
windows-build-and-test:
154225
runs-on: windows-latest
155226
env:

docs/testing.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ If the workspace is already built:
3434
python scripts/smoke_local.py --skip-build
3535
```
3636

37+
## CI test selection
38+
39+
On pull requests, CI does not test the whole workspace. The `test` job runs
40+
`cargo nextest run` over only the crates a change can reach — the changed crates
41+
plus their transitive dependents, derived from the workspace dependency graph by:
42+
43+
```bash
44+
cargo xtask affected --base origin/main
45+
```
46+
47+
which prints the cargo package flags to test (`--workspace`, or `-p <crate> …`,
48+
or nothing when no crate is affected). It falls back to `--workspace` for any
49+
change that can't be confined to specific crates (the lockfile, the toolchain
50+
file, the workspace root manifest, or CI config). On `main` and in the merge
51+
queue the full workspace always runs. For local verification, keep using
52+
`cargo test --workspace --all-targets` above — `affected` is a CI optimization.
53+
3754
The smoke path is the cross-platform local no-fallback acceptance surface. It
3855
uses an isolated config/data/cache root, does not install TheRock wheels, does
3956
not require a managed runtime, and verifies:

0 commit comments

Comments
 (0)