From 562fc930f0e8da57861fe8abe2e05cbe3bffdd55 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 06:42:40 +0000 Subject: [PATCH] Fix Windows-only path asserts failing nightly CI The nightly `test (windows-latest, 1|2)` legs on main fail every run since #1646 and #1658 landed. The Windows test legs run only in the nightly, so PR CI and the merge queue never saw it. All three failures compare a path built one way against a path the code built another: - governing_root: the refusal names the canonical root (`\\?\C:\Users\runneradmin\...`), but the test expected `tmp.path()`, which on runners is the 8.3 short form (`C:\Users\RUNNER~1\...`). Compare against the canonical base, as the sibling test already does. - get_failure_paths: `tmp.join("patches/x.patch")` yields a mixed `\`/`/` path on Windows, while discovery joins per component. Join per component in the test too. - user_patches: the reason and the JSON drift output carry `patches\example+1.0.0.patch` (escaped `\\` in JSON). Normalize the separators before matching the relative path. No assertion is weakened: each one still checks the same path. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ee5yV3PTVTgjSkvahihT31 --- .../tests/get/get_failure_paths.rs | 7 ++++++- crates/socket-patch-cli/tests/user_patches.rs | 14 ++++++++++++-- .../socket-patch-core/src/hosted/governing_root.rs | 12 ++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/socket-patch-cli/tests/get/get_failure_paths.rs b/crates/socket-patch-cli/tests/get/get_failure_paths.rs index b5ceb3656..5744605fb 100644 --- a/crates/socket-patch-cli/tests/get/get_failure_paths.rs +++ b/crates/socket-patch-cli/tests/get/get_failure_paths.rs @@ -1614,7 +1614,12 @@ async fn engine_nested_apply_preserves_conflicts_introduced_during_fetch() { let server = MockServer::start().await; let tmp = tempfile::tempdir().unwrap(); write_project(tmp.path()); - let patch = tmp.path().join(format!("patches/{NAME}+1.0.0.patch")); + // Joined per component: the reason names the path discovery + // built, which uses `\` throughout on Windows. + let patch = tmp + .path() + .join("patches") + .join(format!("{NAME}+1.0.0.patch")); let patch_at_fetch = patch.clone(); let response = view_json( UUID, diff --git a/crates/socket-patch-cli/tests/user_patches.rs b/crates/socket-patch-cli/tests/user_patches.rs index b60e43c40..8eae4117a 100644 --- a/crates/socket-patch-cli/tests/user_patches.rs +++ b/crates/socket-patch-cli/tests/user_patches.rs @@ -16,6 +16,12 @@ fn write(root: &Path, path: &str, bytes: impl AsRef<[u8]>) { std::fs::write(path, bytes).unwrap(); } +/// Reasons name discovered paths with the platform separator, and JSON +/// output escapes Windows' `\` as `\\`; compare with `/` throughout. +fn slashed(text: &str) -> String { + text.replace("\\\\", "/").replace('\\', "/") +} + fn fixture() -> tempfile::TempDir { let tmp = tempfile::tempdir().unwrap(); write(tmp.path(), "package.json", "{}"); @@ -87,7 +93,8 @@ fn apply_skips_and_vendor_refuses_declared_patches_despite_matching_hashes_or_ge .unwrap_or_else(|| panic!("missing skipped event: {value}")); assert_eq!(skip["errorCode"], "user_patch_conflict"); assert_eq!(skip["uuid"], UUID); - assert!(skip["reason"].as_str().unwrap().contains(file)); + let reason = skip["reason"].as_str().unwrap(); + assert!(slashed(reason).contains(file), "{reason}"); assert_eq!(value["summary"]["applied"], 0); assert_eq!(value["summary"]["skipped"], 1); let warning = value["warnings"] @@ -222,7 +229,10 @@ fn environment_and_read_only_checks_do_not_grant_or_require_consent() { assert!(!stdout.contains("user_patch_conflict"), "{stdout}"); // The drift names the project patch, and the remedy names the consent // that clears it: a plain `apply` leaves the package untouched. - assert!(stdout.contains("patches/example+1.0.0.patch"), "{stdout}"); + assert!( + slashed(&stdout).contains("patches/example+1.0.0.patch"), + "{stdout}" + ); let (code, _, stderr) = run_with_env(tmp.path(), &["apply", "--offline", "--check"], &[]); assert_eq!(code, 1, "{stderr}"); assert!( diff --git a/crates/socket-patch-core/src/hosted/governing_root.rs b/crates/socket-patch-core/src/hosted/governing_root.rs index df54df69d..8d3d37075 100644 --- a/crates/socket-patch-core/src/hosted/governing_root.rs +++ b/crates/socket-patch-core/src/hosted/governing_root.rs @@ -3286,6 +3286,10 @@ mod tests { ), ] { let tmp = tempfile::tempdir().unwrap(); + // The refusal names the canonical root; on Windows the temp dir + // can be an 8.3 short path (`RUNNER~1`) that canonicalizes to a + // different string. + let base = std::fs::canonicalize(tmp.path()).unwrap(); write(tmp.path(), "package.json", r#"{"private":true}"#); if let Some(text) = vlt_json { write(tmp.path(), VLT_JSON, text); @@ -3306,11 +3310,7 @@ mod tests { let stray = member_stray_lock(&project) .await .unwrap_or_else(|| panic!("{case}: the lock is stray")); - assert_eq!( - stray.root, - std::fs::canonicalize(tmp.path()).unwrap(), - "{case}" - ); + assert_eq!(stray.root, base, "{case}"); assert_eq!(stray.root_kind, "vlt project root", "{case}"); assert_eq!(stray.ignored, vec![VLT_LOCK], "{case}"); let refused = refusal(&ProjectView::Disk(&project), &[candidate("npm")], true) @@ -3320,7 +3320,7 @@ mod tests { assert!( refused .message - .contains(&tmp.path().join(VLT_LOCK).display().to_string()) + .contains(&base.join(VLT_LOCK).display().to_string()) && refused.message.contains(VLT_JSON) && refused.message.contains("nothing was written"), "{case}: {}",