From 173e5ddc060aae9b1dfa6cd5940fba2d3b60e37d Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Fri, 10 Jul 2026 20:54:40 +0800 Subject: [PATCH] build(make): require cargo-nextest for make test with explicit escape hatch (#4672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build(make): require cargo-nextest for make test with explicit escape hatch (backlog#1153 infra-14) cargo-nextest changes test semantics — it runs each test in its own process (so serial_test's #[serial] mutex does not serialize across tests) and is the only runner that honours .config/nextest.toml [test-groups] (e.g. the ecstore-serial-flaky serialization guard). CI installs and runs nextest, but `make test` only warned when it was missing and then silently fell back to `cargo test`, which runs with different serialization behaviour than CI and can mask or invent flakes. Make cargo-nextest a hard dependency of `make test`: - Missing nextest now fails `make test` with a clear message and install instructions (`cargo install cargo-nextest --locked` or a prebuilt binary via https://nexte.st/docs/installation/). - Set RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1 to opt into the plain `cargo test` fallback anyway; it prints a loud warning that serialization semantics differ from CI and .config/nextest.toml test-groups will NOT apply. - The check stays cheap (command -v). The warn-only test-deps prerequisite is dropped from check.mak in favour of recipe-level gating. Install docs live in the make-layer error message plus a header comment in tests.mak, with a single-line note in CONTRIBUTING.md (kept minimal to avoid conflicting with #4667 / infra-9, which rewrites the verification sections). No CI change: .github/workflows/ci.yml already runs cargo nextest and .github/actions/setup/action.yml already installs it. Refs rustfs/backlog#1153 (infra-14), master plan #1155. Signed-off-by: Zhengchao An --- .config/make/check.mak | 8 ++++--- .config/make/tests.mak | 50 +++++++++++++++++++++++++++++++++++++++--- CONTRIBUTING.md | 3 +++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/.config/make/check.mak b/.config/make/check.mak index d0bbd3925..b9288618e 100644 --- a/.config/make/check.mak +++ b/.config/make/check.mak @@ -11,14 +11,16 @@ check-%: # Warning-only check # Checks for optional dependencies and issues a warning if not found -# (e.g., cargo-nextest for enhanced testing) warn-%: @command -v $* >/dev/null 2>&1 || { \ echo >&2 "⚠️ '$*' is not installed."; \ } # For checking dependencies use check- or warn- -.PHONY: core-deps fmt-deps test-deps +# +# NOTE: cargo-nextest is a HARD dependency of `make test`, gated inside the +# test recipe itself (with a RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1 escape hatch) +# rather than a warn-only prerequisite here — see .config/make/tests.mak. +.PHONY: core-deps fmt-deps core-deps: check-cargo ## Check core dependencies fmt-deps: check-rustfmt ## Check lint and formatting dependencies -test-deps: warn-cargo-nextest ## Check tests dependencies diff --git a/.config/make/tests.mak b/.config/make/tests.mak index 13bfbe5f4..d0424f80c 100644 --- a/.config/make/tests.mak +++ b/.config/make/tests.mak @@ -2,6 +2,25 @@ TEST_THREADS ?= 1 +# cargo-nextest is a HARD dependency of `make test`. +# +# nextest changes test semantics vs plain `cargo test`: it runs every test in +# its own process (so serial_test's #[serial] mutex does not serialize across +# tests) and it is the only runner that honours .config/nextest.toml +# [test-groups] (e.g. the ecstore-serial-flaky serialization guard). CI runs +# nextest (.github/actions/setup/action.yml installs it), so a silent fallback +# to `cargo test` would run with different serialization behaviour than CI and +# mask (or invent) flakes. +# +# Installing cargo-nextest: +# cargo install cargo-nextest --locked # from source +# # or a prebuilt binary (faster) via the taiki-e installer / get.nexte.st: +# # https://nexte.st/docs/installation/ +# +# Escape hatch: set RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1 to run the plain +# `cargo test` fallback anyway. Results are NOT authoritative — semantics +# differ from CI and .config/nextest.toml test-groups will NOT apply. + .PHONY: script-tests script-tests: ## Run shell script tests @echo "Running script tests..." @@ -9,13 +28,38 @@ script-tests: ## Run shell script tests ./scripts/test_entrypoint_credentials.sh .PHONY: test -test: core-deps test-deps script-tests ## Run all tests +test: core-deps script-tests ## Run all tests (needs cargo-nextest; RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1 to override) @echo "🧪 Running tests..." @if command -v cargo-nextest >/dev/null 2>&1; then \ cargo nextest run --all --exclude e2e_test; \ - else \ - echo "ℹ️ cargo-nextest not found; falling back to 'cargo test'"; \ + elif [ "$${RUSTFS_ALLOW_CARGO_TEST_FALLBACK:-0}" = "1" ]; then \ + echo >&2 "⚠️ ============================================================================"; \ + echo >&2 "⚠️ cargo-nextest NOT found — running the 'cargo test' fallback (opt-in)."; \ + echo >&2 "⚠️ TEST SEMANTICS DIFFER FROM CI; results are NOT authoritative:"; \ + echo >&2 "⚠️ * nextest runs each test in its own process; 'cargo test' does not,"; \ + echo >&2 "⚠️ so serial_test #[serial] serialization behaves differently."; \ + echo >&2 "⚠️ * .config/nextest.toml [test-groups] will NOT apply (e.g. the"; \ + echo >&2 "⚠️ ecstore-serial-flaky group), so load-sensitive tests may flake here"; \ + echo >&2 "⚠️ but pass on CI (or vice versa)."; \ + echo >&2 "⚠️ Install cargo-nextest and re-run before trusting these results."; \ + echo >&2 "⚠️ ============================================================================"; \ cargo test --workspace --exclude e2e_test -- --nocapture --test-threads="$(TEST_THREADS)"; \ + else \ + echo >&2 "❌ cargo-nextest is required for 'make test' but was not found."; \ + echo >&2 ""; \ + echo >&2 " RustFS tests run under cargo-nextest (process-per-test isolation)."; \ + echo >&2 " CI runs nextest and .config/nextest.toml [test-groups] only take effect"; \ + echo >&2 " under nextest. Plain 'cargo test' has different serialization semantics"; \ + echo >&2 " and is NOT a faithful substitute."; \ + echo >&2 ""; \ + echo >&2 " Install it with either:"; \ + echo >&2 " cargo install cargo-nextest --locked"; \ + echo >&2 " or a prebuilt binary (faster) — see https://nexte.st/docs/installation/"; \ + echo >&2 ""; \ + echo >&2 " To run the plain 'cargo test' fallback anyway (results NOT authoritative;"; \ + echo >&2 " serialization semantics differ from CI), re-run with:"; \ + echo >&2 " RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1 make test"; \ + exit 1; \ fi cargo test --all --doc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f19f1288..ea2051fe0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,9 @@ make pre-commit make pre-pr ``` +> `make test` requires [cargo-nextest](https://nexte.st) (CI runs it and only nextest honours `.config/nextest.toml` test-groups). Install it with `cargo install cargo-nextest --locked` or a prebuilt binary (see https://nexte.st/docs/installation/). To run the plain `cargo test` fallback anyway (results not authoritative — serialization semantics differ from CI), set `RUSTFS_ALLOW_CARGO_TEST_FALLBACK=1`. + +### 🔒 Automated Pre-commit Hooks #### What `make pre-commit` and `make pre-pr` actually run `make pre-commit` is the **fast** gate. It runs, in order