Skip to content

Commit 5df0b36

Browse files
committed
fix(test): add #[serial] to env-var tests to prevent concurrent access races (#1223)
Tests that manipulate process env vars (VP_SHIM_TOOL, VITE_PLUS_SHIM_TOOL, PATH) without #[serial] can race with other tests running in parallel, causing flaky CI failures. Ensure all such tests are serialized and clear all related env vars before running. Also document env var testing guidelines in CLAUDE.md.
1 parent 487e338 commit 5df0b36

6 files changed

Lines changed: 37 additions & 34 deletions

File tree

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ All user-facing output must go through shared output modules instead of raw prin
106106
- Run `cargo test` to execute all tests
107107
- You never need to run `pnpm install` in the test fixtures dir, vite-plus should able to load and parse the workspace without `pnpm install`.
108108

109+
### Environment Variables in Tests
110+
111+
- **Prefer `EnvConfig::test_scope()`**: For tests needing custom config values (VP_HOME, npm registry, etc.), use thread-local `EnvConfig::test_scope()` or `EnvConfig::test_guard()` from `vite_shared` — no `unsafe`, no `#[serial]`, full parallelism
112+
- **`#[serial]` required for `std::env::set_var`/`remove_var`**: Any test that directly modifies process env vars (PATH, VP_SHIM_TOOL, etc.) MUST have `#[serial_test::serial]` to prevent concurrent access races
113+
- **Clean up ALL related env vars**: When clearing env vars before a test, clear ALL vars that the function under test reads — not just the one being tested
114+
109115
## Build
110116

111117
- Run `pnpm bootstrap-cli` from the project root to build all packages and install the global CLI

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_global_cli/src/shim/mod.rs

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,18 @@ mod tests {
217217
assert!(!is_potential_package_binary("another-fake-tool"));
218218
}
219219

220+
/// Clear both shim env vars to isolate tests.
221+
/// SAFETY: caller must be `#[serial]` since this mutates process-global state.
222+
unsafe fn clear_shim_env_vars() {
223+
unsafe {
224+
std::env::remove_var(SHIM_TOOL_ENV_VAR);
225+
std::env::remove_var(LEGACY_SHIM_TOOL_ENV_VAR);
226+
}
227+
}
228+
220229
#[test]
221230
#[serial]
222231
fn test_detect_shim_tool_from_env_var() {
223-
// SAFETY: We're in a test at startup, no other threads
224232
unsafe {
225233
std::env::set_var(SHIM_TOOL_ENV_VAR, "node");
226234
std::env::remove_var(LEGACY_SHIM_TOOL_ENV_VAR);
@@ -236,7 +244,6 @@ mod tests {
236244
fn test_detect_shim_tool_from_legacy_env_var() {
237245
// When only VITE_PLUS_SHIM_TOOL is set (older trampoline), it should
238246
// fall back to reading the legacy env var.
239-
// SAFETY: We're in a test at startup, no other threads
240247
unsafe {
241248
std::env::remove_var(SHIM_TOOL_ENV_VAR);
242249
std::env::set_var(LEGACY_SHIM_TOOL_ENV_VAR, "npm");
@@ -247,43 +254,28 @@ mod tests {
247254
assert!(std::env::var(LEGACY_SHIM_TOOL_ENV_VAR).is_err());
248255
}
249256

257+
/// Tests that argv0-based tool detection works for a given tool name,
258+
/// including full path and .exe extension variants.
259+
fn assert_detect_shim_tool_from_argv0(tool: &str) {
260+
unsafe { clear_shim_env_vars() };
261+
262+
assert_eq!(detect_shim_tool(tool), Some(tool.to_string()));
263+
assert_eq!(
264+
detect_shim_tool(&vite_str::format!("/home/user/.vite-plus/bin/{tool}")),
265+
Some(tool.to_string()),
266+
);
267+
assert_eq!(detect_shim_tool(&vite_str::format!("{tool}.exe")), Some(tool.to_string()),);
268+
}
269+
250270
#[test]
251271
#[serial]
252272
fn test_detect_shim_tool_vpx() {
253-
// vpx should be detected via the argv0 check, before the env var check
254-
// and before is_shim_tool (which would incorrectly match it as a package binary)
255-
// SAFETY: We're in a test
256-
unsafe {
257-
std::env::remove_var(SHIM_TOOL_ENV_VAR);
258-
}
259-
let result = detect_shim_tool("vpx");
260-
assert_eq!(result, Some("vpx".to_string()));
261-
262-
// Also works with full path
263-
let result = detect_shim_tool("/home/user/.vite-plus/bin/vpx");
264-
assert_eq!(result, Some("vpx".to_string()));
265-
266-
// Also works with .exe extension (Windows)
267-
let result = detect_shim_tool("vpx.exe");
268-
assert_eq!(result, Some("vpx".to_string()));
273+
assert_detect_shim_tool_from_argv0("vpx");
269274
}
270275

271276
#[test]
277+
#[serial]
272278
fn test_detect_shim_tool_vpr() {
273-
// vpr should be detected via the argv0 check, same pattern as vpx
274-
// SAFETY: We're in a test
275-
unsafe {
276-
std::env::remove_var(SHIM_TOOL_ENV_VAR);
277-
}
278-
let result = detect_shim_tool("vpr");
279-
assert_eq!(result, Some("vpr".to_string()));
280-
281-
// Also works with full path
282-
let result = detect_shim_tool("/home/user/.vite-plus/bin/vpr");
283-
assert_eq!(result, Some("vpr".to_string()));
284-
285-
// Also works with .exe extension (Windows)
286-
let result = detect_shim_tool("vpr.exe");
287-
assert_eq!(result, Some("vpr".to_string()));
279+
assert_detect_shim_tool_from_argv0("vpr");
288280
}
289281
}

crates/vite_shared/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@ which = { workspace = true }
2222
[target.'cfg(not(target_os = "windows"))'.dependencies]
2323
rustls = { workspace = true }
2424

25+
[dev-dependencies]
26+
serial_test = { workspace = true }
27+
2528
[lints]
2629
workspace = true

crates/vite_shared/src/home.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ mod tests {
6464
}
6565

6666
#[test]
67+
#[serial_test::serial]
6768
fn test_get_vite_plus_without_home() {
6869
use std::path::PathBuf;
6970

crates/vite_shared/src/path_env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ mod tests {
142142
}
143143

144144
#[test]
145-
#[ignore]
145+
#[serial_test::serial]
146146
fn test_format_path_prepended_always_prepends() {
147147
// Even if the directory exists somewhere in PATH, it should be prepended
148148
let test_dir = "/test/node/bin";

0 commit comments

Comments
 (0)