Skip to content

ci: track stable in rust-toolchain.toml, enforce MSRV in its own job #2915

ci: track stable in rust-toolchain.toml, enforce MSRV in its own job

ci: track stable in rust-toolchain.toml, enforce MSRV in its own job #2915

Workflow file for this run

name: Tests
on:
push:
branches:
- main
pull_request:
env:
# Retry dependency downloads on transient network errors.
CARGO_NET_RETRY: 10
# Incremental compilation only pays off for repeated local rebuilds; in CI it
# just bloats the target dir (and the cache) for no benefit.
CARGO_INCREMENTAL: 0
# Smaller debug info => smaller object files => faster linking (the dominant
# cost on Windows MSVC) and smaller caches. line-tables-only keeps file:line
# numbers in backtraces.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
# Link with LLVM's lld-link instead of the slow MSVC link.exe. LLVM ships at
# C:\Program Files\LLVM on the windows-2025 image. Only consumed when building
# the windows-msvc target, so it is a no-op on Linux/macOS.
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: "C:/Program Files/LLVM/bin/lld-link.exe"
defaults:
run:
shell: bash
jobs:
# Skip the (expensive) test jobs on docs-only changes. The heavy jobs gate on
# this output, so a docs-only PR skips them and the required `test:required`
# check passes via the skip.
changes:
runs-on: ubuntu-latest
outputs:
src: ${{ steps.filter.outputs.src }}
permissions:
pull-requests: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
# With 'every', a changed file matches only when it satisfies the
# positive pattern AND every negated pattern.
predicate-quantifier: 'every'
filters: |
src:
- '**'
- '!**.md'
# Everything else in CI runs on `stable` (see rust-toolchain.toml), so the MSRV
# promised to downstream users needs its own gate. This job is the only place
# the MSRV toolchain is used; it reads the version from the workspace
# Cargo.toml so `rust-version` stays the single source of truth.
#
# Scope: build only, no `--all-targets`. The promise is that consumers can
# *build* the published crates with the MSRV, not that our test suite runs
# there — dev-dependencies are free to require a newer compiler.
#
# Linux-only: MSRV regressions almost always come from a dependency raising its
# own `rust-version`, which is platform-independent. Running the full OS matrix
# would triple the cost to catch only platform-gated regressions (e.g. a
# windows-sys bump), which the stable `test` matrix would surface anyway once
# the dependency reaches a release we build.
msrv:
name: MSRV build
needs: changes
if: needs.changes.outputs.src == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Read MSRV from Cargo.toml
id: msrv
run: |
set -euo pipefail
version=$(awk -F'"' '/^rust-version *=/ { print $2; exit }' Cargo.toml)
if [ -z "$version" ]; then
echo "::error file=Cargo.toml::could not parse rust-version"
exit 1
fi
echo "Detected MSRV: $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Remove the runner's bundled Rust toolchain
run: rustup toolchain remove stable 2>/dev/null || true
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
toolchain: ${{ steps.msrv.outputs.version }}
target: wasm32-unknown-unknown
cache-shared-key: ${{ runner.os }}-msrv
cache-bin: false
rustflags: ""
# ref-tests and ic-utils-bindgen-tests are the only `publish = false`
# members; they depend on pocket-ic from the IC monorepo, whose MSRV runs
# far ahead of ours. Excluding (rather than listing the published crates)
# keeps a newly added published crate covered by default.
- name: Build with MSRV
run: |
cargo build --locked --workspace \
--exclude ref-tests --exclude ic-utils-bindgen-tests
cargo build --locked --workspace \
--exclude ref-tests --exclude ic-utils-bindgen-tests --all-features
# Browser consumers build ic-agent for wasm at the MSRV too; mirrors the
# WASM step in lint.yml.
- name: Build with MSRV (WASM)
run: |
CARGO_TARGET_DIR=target/wasm cargo build --locked --target wasm32-unknown-unknown \
-p ic-agent --features wasm-bindgen -p ic-utils
# Workspace tests for every crate except ref-tests, on all three OSes. Because
# each crate is tested from its own directory, the heavy pocket-ic dependency
# (only used by ref-tests) never compiles here, keeping this job's cache small.
test:
name: Run Tests on ${{ matrix.os }}
needs: changes
if: needs.changes.outputs.src == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# windows is pinned to 2025 so the lld-link path above is guaranteed.
os: [ubuntu-latest, macos-latest, windows-2025]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Defender real-time scanning of every object/rlib slows file-heavy Rust
# builds and cache extraction considerably on Windows.
- name: Exclude build dirs from Windows Defender
if: ${{ contains(matrix.os, 'windows') }}
shell: pwsh
run: |
Add-MpPreference -ExclusionPath `
"$env:GITHUB_WORKSPACE", `
"$env:USERPROFILE\.cargo", `
"$env:USERPROFILE\.rustup" -ErrorAction SilentlyContinue
# rust-cache keys on all installed toolchains; the image's bundled `stable`
# drifts as the image updates, moving the key and causing misses. Remove it
# so only the rust-toolchain.toml-pinned version remains.
- name: Remove the runner's bundled Rust toolchain
run: rustup toolchain remove stable 2>/dev/null || true
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache-shared-key: ${{ runner.os }}-test
cache-bin: false
# setup-rust-toolchain defaults RUSTFLAGS to "-D warnings"; the tests
# were never built warnings-as-errors (that is lint's job), so clear it.
rustflags: ""
# The ic-agent doctests spin up pocket-ic via ref-tests' helpers, so they
# need the downloaded assets. Skipped on Windows (the download script and
# those doctests are unix-only), matching the previous single-job setup.
- name: Download test assets
if: ${{ !contains(matrix.os, 'windows') }}
run: ./scripts/download_reftest_assets.sh
- name: Run Tests
run: |
# Test all features and no features for each package.
# ref-tests is excluded here and runs in the ref-tests job with
# --test-threads=1 (pocket-ic spawns many threads per subnet).
for p in $(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name != "ref-tests") | .manifest_path'); do
pushd $(dirname $p)
cargo test --all-features --no-fail-fast
cargo test --no-default-features --no-fail-fast
popd
done
env:
RUST_BACKTRACE: 1
# ref-tests + SoftHSM run together: both need the compiled ref-tests binary,
# so co-locating them avoids compiling it (and pocket-ic) twice. Linux-only:
# the GitHub macOS runner's low per-process thread limit cannot be raised
# without root, and pocket-ic exhausts it; Windows lacks the assets.
ref_tests:
name: ref-tests
needs: changes
if: needs.changes.outputs.src == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Remove the runner's bundled Rust toolchain
run: rustup toolchain remove stable 2>/dev/null || true
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache-shared-key: ${{ runner.os }}-reftests
cache-bin: false
rustflags: ""
- name: Install node for SoftHSM tests
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
- name: Install and Configure SoftHSM
run: |
set -ex
sudo apt-get install -f libsofthsm2 opensc-pkcs11 opensc
sudo usermod -a -G softhsm $USER
echo "SOFTHSM2_CONF=$HOME/softhsm.conf" >>$GITHUB_ENV
echo "directories.tokendir = $HOME/softhsm/tokens/" >$HOME/softhsm.conf
mkdir -p $HOME/softhsm/tokens
- name: Download test assets
run: ./scripts/download_reftest_assets.sh
- name: Run Tests (ref-tests)
run: |
cd ref-tests
cargo test --no-fail-fast -- --test-threads=1
env:
RUST_BACKTRACE: 1
- name: Run Tests (SoftHSM)
run: |
set -ex
softhsm2-util --init-token --slot $HSM_SLOT_INDEX --label "agent-rs-token" --so-pin $HSM_SO_PIN --pin $HSM_PIN
# create key:
pkcs11-tool -k --module $HSM_PKCS11_LIBRARY_PATH --login --slot-index $HSM_SLOT_INDEX -d $HSM_KEY_ID --key-type EC:prime256v1 --pin $HSM_PIN
cd ref-tests
cargo test --all-features --no-fail-fast -- --nocapture --test-threads=1
env:
RUST_BACKTRACE: 1
HSM_PKCS11_LIBRARY_PATH: /usr/lib/softhsm/libsofthsm2.so
HSM_SO_PIN: 123456
HSM_PIN: 1234
HSM_SLOT_INDEX: 0
HSM_KEY_ID: abcdef
wasm:
name: WASM tests
needs: changes
if: needs.changes.outputs.src == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Remove the runner's bundled Rust toolchain
run: rustup toolchain remove stable 2>/dev/null || true
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache-shared-key: ${{ runner.os }}-wasm
cache-bin: false
rustflags: ""
- name: Install wasm-pack and chromedriver
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
sudo apt-get install -y chromium-chromedriver
# CARGO_TARGET_DIR=target/wasm keeps wasm artifacts under ./target, so
# rust-cache (which caches ./target) still picks them up.
#
# --lib restricts this to the lib target's #[wasm_bindgen_test] tests. Newer
# toolchains also run doctests for wasm targets (1.88 skipped them), and
# ic-agent's doctests cannot compile there: they use #[tokio::main], and
# tokio is deliberately a dev-dependency only under
# cfg(not(target_family = "wasm")). Doctests are covered on the host by the
# `test` job above; running them inside a headless browser adds nothing.
- name: Run Tests (WASM)
run: CARGO_TARGET_DIR=target/wasm wasm-pack test --chrome --headless ic-agent --features wasm-bindgen --lib
aggregate:
name: test:required
# Runs only when the test jobs ran; on docs-only changes this skips, and a
# skipped required check counts as passing for branch protection.
if: always() && needs.changes.outputs.src == 'true'
runs-on: ubuntu-latest
needs: [changes, msrv, test, ref_tests, wasm]
steps:
- name: Check MSRV result
if: ${{ needs.msrv.result != 'success' }}
run: exit 1
- name: Check test result
if: ${{ needs.test.result != 'success' }}
run: exit 1
- name: Check ref-tests result
if: ${{ needs.ref_tests.result != 'success' }}
run: exit 1
- name: Check wasm result
if: ${{ needs.wasm.result != 'success' }}
run: exit 1