From 8c98dfda2d33d8a1e10bf6a12ac6b5b58b24e80f Mon Sep 17 00:00:00 2001 From: stephenleo Date: Sun, 6 Sep 2026 17:37:32 +0800 Subject: [PATCH 1/3] test(explain): merge racing CSHIP_ACCOUNT tests into one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `std::env::set_var` is process-global, so the malformed-env and empty-env tests clobbered each other when cargo scheduled them concurrently — the empty test's " " (or its remove_var) could land between the malformed test's set_var and its assertion, dropping CSHIP_ACCOUNT from the hint. Failed on Windows CI for main (run 34024827695); passes on reruns. Merging both cases into one test makes them sequential by construction, no mutex or serial-test dependency needed. Co-Authored-By: Claude Opus 5 (1M context) --- src/explain.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/explain.rs b/src/explain.rs index 41f7647..dbe5e17 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -784,22 +784,21 @@ mod tests { let _ = asserted_at_least_once; } + /// Both `CSHIP_ACCOUNT` cases live in one test: `set_var` is process-global, + /// so two tests mutating it run concurrently on cargo's worker threads and + /// clobber each other (this raced on Windows CI). One test = sequential. + /// ponytail: unsafe because `std::env::set_var` is process-global; scoped + /// tightly around the two `error_hint_for` calls that need it. #[test] - fn test_error_hint_account_malformed_env_names_env_as_cause() { - // No other test in this file touches CSHIP_ACCOUNT, so set/unset here - // doesn't race with a sibling test's own use of the var. - // ponytail: unsafe because std::env::set_var is process-global; scoped - // tightly around the single assertion this test needs. - unsafe { - std::env::set_var("CSHIP_ACCOUNT", "{not json"); - } + fn test_error_hint_account_env_var_cases() { let ctx = crate::context::Context::default(); let cfg = crate::config::CshipConfig::default(); - let (error, remediation) = error_hint_for("account", &ctx, &cfg); + + // Set-but-unparseable CSHIP_ACCOUNT is named as the root cause. unsafe { - std::env::remove_var("CSHIP_ACCOUNT"); + std::env::set_var("CSHIP_ACCOUNT", "{not json"); } - + let (error, remediation) = error_hint_for("account", &ctx, &cfg); assert!( error.contains("CSHIP_ACCOUNT"), "expected CSHIP_ACCOUNT named as the cause, got: {error}" @@ -808,22 +807,16 @@ mod tests { remediation.contains("CSHIP_ACCOUNT"), "expected remediation to mention CSHIP_ACCOUNT, got: {remediation}" ); - } - #[test] - fn test_error_hint_account_empty_env_falls_back_to_credential_probe() { // Empty CSHIP_ACCOUNT must not trigger the malformed-env hint; it // falls through to the normal credential probe. unsafe { std::env::set_var("CSHIP_ACCOUNT", " "); } - let ctx = crate::context::Context::default(); - let cfg = crate::config::CshipConfig::default(); let (error, _) = error_hint_for("account", &ctx, &cfg); unsafe { std::env::remove_var("CSHIP_ACCOUNT"); } - assert!( !error.contains("CSHIP_ACCOUNT"), "empty CSHIP_ACCOUNT should not be reported as malformed, got: {error}" From a41cf634baa52685d2ff28ebbc2ad7a4e5035d88 Mon Sep 17 00:00:00 2001 From: stephenleo Date: Sun, 6 Sep 2026 18:07:24 +0800 Subject: [PATCH 2/3] test(passthrough): guard nonexistent-module test with PATH_MUTEX test_render_passthrough_returns_none_for_nonexistent_module resolves `starship` from PATH but did not hold PATH_MUTEX. Sibling tests point PATH at mock starship scripts that print output, so an overlap made render_passthrough return Some(...) and the assert fail (ubuntu CI). Co-Authored-By: Claude Opus 5 (1M context) --- src/passthrough.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/passthrough.rs b/src/passthrough.rs index 8e2b58a..4f644ce 100644 --- a/src/passthrough.rs +++ b/src/passthrough.rs @@ -381,7 +381,10 @@ mod tests { #[test] fn test_render_passthrough_returns_none_for_nonexistent_module() { - // starship exits non-zero for unknown module names → None + // starship exits non-zero for unknown module names → None. + // Resolves `starship` from PATH, so it must hold the mutex: sibling tests + // point PATH at mock starship scripts that print output. + let _guard = PATH_MUTEX.lock().unwrap(); let result = render_passthrough("__cship_nonexistent_xyz__", &Context::default()); assert!(result.is_none()); } From 43c804ce53cc748d2eaa9a9597d7c98cdd51e76e Mon Sep 17 00:00:00 2001 From: stephenleo Date: Sun, 6 Sep 2026 18:11:31 +0800 Subject: [PATCH 3/3] chore(release): v1.8.3 Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 5 +++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70d90e1..88ed2ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.8.3] - 2026-09-06 + +### Fixed +- Fixed two test-suite race conditions that made CI intermittently red. The `CSHIP_ACCOUNT` error-hint tests mutated the same process-global env var from parallel worker threads and were merged into one sequential test; `test_render_passthrough_returns_none_for_nonexistent_module` resolved `starship` from `PATH` without holding `PATH_MUTEX`, so sibling tests pointing `PATH` at mock `starship` scripts could make it observe real output ([#206](https://github.com/stephenleo/cship/pull/206)) + ## [1.8.2] - 2026-09-06 ### Added diff --git a/Cargo.lock b/Cargo.lock index 77b02b2..74155c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,7 +257,7 @@ dependencies = [ [[package]] name = "cship" -version = "1.8.2" +version = "1.8.3" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index d16f983..bd7ca5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cship" -version = "1.8.2" +version = "1.8.3" edition = "2024" description = "A beautiful, fully customizable statusline for Claude Code — Starship-style TOML config, themeable colours, Nerd Font glyphs, and tunable cost/context/usage thresholds" license = "Apache-2.0"