From 074e286f5f866af83beb1a9ce55de8ac0c3d6d66 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:24:37 +0800 Subject: [PATCH 1/3] test: cover cached cwd contracts --- tests/cached_current_dir.rs | 115 +++++++++++++++++++++++++++- tests/cached_current_dir_failure.rs | 94 +++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 tests/cached_current_dir_failure.rs diff --git a/tests/cached_current_dir.rs b/tests/cached_current_dir.rs index b309054..6622f4e 100644 --- a/tests/cached_current_dir.rs +++ b/tests/cached_current_dir.rs @@ -1,10 +1,21 @@ use std::{ + borrow::Cow, env, fs, path::{Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, }; -use sugar_path::SugarPath; +use sugar_path::{SugarPath, SugarPathBuf}; + +fn assert_owned_relative(target: &Path, base: &Path, expected: &Path, context: &str) { + let strict = target.relative(base); + assert_eq!(strict.as_os_str(), expected.as_os_str(), "{context} strict"); + assert!(matches!(strict, Cow::Owned(_)), "{context} strict should own"); + + let fallible = target.try_relative(base).expect("fixture should resolve against cwd"); + assert_eq!(fallible.as_os_str(), expected.as_os_str(), "{context} try"); + assert!(matches!(fallible, Cow::Owned(_)), "{context} try should own"); +} struct CurrentDirGuard { original: PathBuf, @@ -38,14 +49,99 @@ fn current_directory_is_cached_only_when_requested() { let first = env::current_dir().expect("read the first temporary directory"); let dirty_absolute = first.join("unused").join("..").join("absolute.js"); assert_eq!(dirty_absolute.absolutize(), first.join("absolute.js")); + assert_owned_relative( + Path::new("pkg/assets/./index.js"), + Path::new("pkg/chunks"), + &PathBuf::from("..").join("assets").join("index.js"), + "cwd-independent relative", + ); env::set_current_dir(&second).expect("enter the second temporary directory"); let second = env::current_dir().expect("read the second temporary directory"); + let root = second.parent().expect("temporary directory has a parent"); + let anchor = root.join("anchor"); + let second_name = second.file_name().expect("second directory has a name"); + let second_target = second.join("absolute-target.js"); + let relative_target = Path::new("../second/relative-target.js"); + let relative_base = Path::new("base"); + assert_owned_relative( + relative_target, + relative_base, + &PathBuf::from("..").join("relative-target.js"), + "unequal-parent relative pair initializes cwd", + ); + assert_owned_relative( + Path::new("entry.js"), + &anchor, + &PathBuf::from("..").join(second_name).join("entry.js"), + "relative receiver initializes cwd", + ); + assert_owned_relative( + &second_target, + Path::new("base"), + &PathBuf::from("..").join("absolute-target.js"), + "relative base uses initialized cwd", + ); assert_eq!(Path::new("entry.js").absolutize(), second.join("entry.js")); env::set_current_dir(&third).expect("enter the third temporary directory"); let third = env::current_dir().expect("read the third temporary directory"); let expected_base = if cfg!(feature = "cached_current_dir") { &second } else { &third }; + let expected_base_name = expected_base.file_name().expect("expected base has a name"); + let expected_relative_pair = if cfg!(feature = "cached_current_dir") { + PathBuf::from("..").join("relative-target.js") + } else { + PathBuf::from("..").join("..").join(second_name).join("relative-target.js") + }; + assert_owned_relative( + relative_target, + relative_base, + &expected_relative_pair, + "unequal-parent relative pair observes cwd policy", + ); + let expected_slash = if cfg!(feature = "cached_current_dir") { + "../relative-target.js" + } else { + "../../second/relative-target.js" + }; + assert_eq!( + relative_target.relative(relative_base).into_owned().into_slash(), + expected_slash, + "strict relative-to-slash composition observes cwd policy", + ); + assert_eq!( + relative_target + .try_relative(relative_base) + .expect("fixture should resolve against cwd") + .into_owned() + .into_slash(), + expected_slash, + "fallible relative-to-slash composition observes cwd policy", + ); + let explicit = relative_target.relative_with(relative_base, &third); + assert_eq!( + explicit.as_os_str(), + PathBuf::from("..").join("..").join(second_name).join("relative-target.js").as_os_str(), + "explicit cwd must bypass ambient caching", + ); + assert!(matches!(explicit, Cow::Owned(_)), "explicit unequal-parent result should own"); + assert_owned_relative( + Path::new("entry.js"), + &anchor, + &PathBuf::from("..").join(expected_base_name).join("entry.js"), + "relative receiver observes cwd policy", + ); + let expected_from_relative_base = if cfg!(feature = "cached_current_dir") { + PathBuf::from("..").join("absolute-target.js") + } else { + PathBuf::from("..").join("..").join(second_name).join("absolute-target.js") + }; + assert_owned_relative( + &second_target, + Path::new("base"), + &expected_from_relative_base, + "relative base observes cwd policy", + ); assert_eq!(Path::new("entry.js").absolutize(), expected_base.join("entry.js")); #[cfg(target_family = "windows")] @@ -59,10 +155,27 @@ fn current_directory_is_cached_only_when_requested() { }; let drive_relative = format!("{drive}:drive-entry.js"); let oracle = std::path::absolute(&drive_relative).expect("resolve the drive-relative oracle"); + assert_eq!(oracle, third.join("drive-entry.js")); assert_eq!( Path::new(&drive_relative).absolutize().as_ref(), oracle.as_path(), "drive-relative resolution must bypass the single cached cwd", ); + assert_owned_relative( + Path::new(&drive_relative), + &anchor, + &PathBuf::from("..").join("third").join("drive-entry.js"), + "drive-relative receiver bypasses cached cwd", + ); + + let drive_base = format!("{drive}:drive-base"); + let base_oracle = std::path::absolute(&drive_base).expect("resolve drive-relative base oracle"); + assert_eq!(base_oracle, third.join("drive-base")); + assert_owned_relative( + &second_target, + Path::new(&drive_base), + &PathBuf::from("..").join("..").join("second").join("absolute-target.js"), + "drive-relative base bypasses cached cwd", + ); } } diff --git a/tests/cached_current_dir_failure.rs b/tests/cached_current_dir_failure.rs new file mode 100644 index 0000000..6700145 --- /dev/null +++ b/tests/cached_current_dir_failure.rs @@ -0,0 +1,94 @@ +#![cfg(unix)] + +use std::{ + borrow::Cow, + env, fs, + panic::{AssertUnwindSafe, catch_unwind}, + path::{Path, PathBuf}, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; + +use sugar_path::SugarPath; + +const CHILD_ENV: &str = "SUGAR_PATH_CACHED_CWD_FAILURE_ROOT"; + +fn assert_owned_relative(target: &Path, base: &Path, expected: &Path, context: &str) { + let fallible = target.try_relative(base).expect("fixture should resolve against cwd"); + assert_eq!(fallible.as_os_str(), expected.as_os_str(), "{context} try"); + assert!(matches!(fallible, Cow::Owned(_)), "{context} try should own"); + + let strict = target.relative(base); + assert_eq!(strict.as_os_str(), expected.as_os_str(), "{context} strict"); + assert!(matches!(strict, Cow::Owned(_)), "{context} strict should own"); +} + +#[test] +fn failed_cwd_lookup_does_not_poison_later_relative_calls() { + if let Some(root) = env::var_os(CHILD_ENV) { + let root = PathBuf::from(root); + let doomed = root.join("doomed"); + let recovery = root.join("recovery"); + let later = root.join("later"); + let anchor = root.join("anchor"); + + fs::remove_dir(&doomed).expect("remove the child's current directory"); + assert!(env::current_dir().is_err()); + assert!(Path::new("recovered.js").try_relative(&anchor).is_err()); + assert!( + catch_unwind(AssertUnwindSafe(|| Path::new("recovered.js").relative(&anchor))).is_err(), + "strict relative should panic while cwd is unavailable", + ); + + env::set_current_dir(&recovery).expect("enter the recovery directory"); + let recovery = env::current_dir().expect("read the recovery directory"); + let anchor = recovery.parent().expect("recovery directory has a parent").join("anchor"); + let recovery_name = recovery.file_name().expect("recovery directory has a name"); + assert_owned_relative( + Path::new("recovered.js"), + &anchor, + &PathBuf::from("..").join(recovery_name).join("recovered.js"), + "first successful cwd lookup after failures", + ); + + env::set_current_dir(&later).expect("enter the later directory"); + let later = env::current_dir().expect("read the later directory"); + let expected_base = if cfg!(feature = "cached_current_dir") { &recovery } else { &later }; + let expected_name = expected_base.file_name().expect("expected base has a name"); + assert_owned_relative( + Path::new("recovered.js"), + &anchor, + &PathBuf::from("..").join(expected_name).join("recovered.js"), + "failed lookup does not poison cwd policy", + ); + return; + } + + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock is after the Unix epoch") + .as_nanos(); + let root = + env::temp_dir().join(format!("sugar-path-cached-cwd-failure-{}-{unique}", std::process::id())); + let doomed = root.join("doomed"); + fs::create_dir_all(&doomed).expect("create the child's current directory"); + fs::create_dir(root.join("recovery")).expect("create the recovery directory"); + fs::create_dir(root.join("later")).expect("create the later directory"); + + let output = Command::new(env::current_exe().expect("find the integration-test executable")) + .args(["--exact", "failed_cwd_lookup_does_not_poison_later_relative_calls", "--nocapture"]) + .env(CHILD_ENV, &root) + .current_dir(&doomed) + .output() + .expect("run the integration test in a child process"); + + if root.exists() { + fs::remove_dir_all(&root).expect("clean up the child's temporary directories"); + } + assert!( + output.status.success(), + "child test failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} From 5d551fccf1f8d2d206c5f813fec30b0dfdb1973d Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:32:37 +0800 Subject: [PATCH 2/3] test: isolate cached relative contracts --- .github/workflows/test.yaml | 2 +- tests/cached_current_dir.rs | 53 +------------ tests/cached_current_dir_relative.rs | 107 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 53 deletions(-) create mode 100644 tests/cached_current_dir_relative.rs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 78aeaa7..9ad21b0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,7 +65,7 @@ jobs: - name: Clippy run: cargo clippy --locked --workspace --all-targets --all-features -- -D warnings - name: Test default features - run: cargo test --locked --workspace + run: cargo test --locked -p sugar_path - name: Test all features run: cargo test --locked --workspace --all-features diff --git a/tests/cached_current_dir.rs b/tests/cached_current_dir.rs index 6622f4e..20caefd 100644 --- a/tests/cached_current_dir.rs +++ b/tests/cached_current_dir.rs @@ -5,7 +5,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use sugar_path::{SugarPath, SugarPathBuf}; +use sugar_path::SugarPath; fn assert_owned_relative(target: &Path, base: &Path, expected: &Path, context: &str) { let strict = target.relative(base); @@ -49,12 +49,6 @@ fn current_directory_is_cached_only_when_requested() { let first = env::current_dir().expect("read the first temporary directory"); let dirty_absolute = first.join("unused").join("..").join("absolute.js"); assert_eq!(dirty_absolute.absolutize(), first.join("absolute.js")); - assert_owned_relative( - Path::new("pkg/assets/./index.js"), - Path::new("pkg/chunks"), - &PathBuf::from("..").join("assets").join("index.js"), - "cwd-independent relative", - ); env::set_current_dir(&second).expect("enter the second temporary directory"); let second = env::current_dir().expect("read the second temporary directory"); @@ -62,14 +56,6 @@ fn current_directory_is_cached_only_when_requested() { let anchor = root.join("anchor"); let second_name = second.file_name().expect("second directory has a name"); let second_target = second.join("absolute-target.js"); - let relative_target = Path::new("../second/relative-target.js"); - let relative_base = Path::new("base"); - assert_owned_relative( - relative_target, - relative_base, - &PathBuf::from("..").join("relative-target.js"), - "unequal-parent relative pair initializes cwd", - ); assert_owned_relative( Path::new("entry.js"), &anchor, @@ -88,43 +74,6 @@ fn current_directory_is_cached_only_when_requested() { let third = env::current_dir().expect("read the third temporary directory"); let expected_base = if cfg!(feature = "cached_current_dir") { &second } else { &third }; let expected_base_name = expected_base.file_name().expect("expected base has a name"); - let expected_relative_pair = if cfg!(feature = "cached_current_dir") { - PathBuf::from("..").join("relative-target.js") - } else { - PathBuf::from("..").join("..").join(second_name).join("relative-target.js") - }; - assert_owned_relative( - relative_target, - relative_base, - &expected_relative_pair, - "unequal-parent relative pair observes cwd policy", - ); - let expected_slash = if cfg!(feature = "cached_current_dir") { - "../relative-target.js" - } else { - "../../second/relative-target.js" - }; - assert_eq!( - relative_target.relative(relative_base).into_owned().into_slash(), - expected_slash, - "strict relative-to-slash composition observes cwd policy", - ); - assert_eq!( - relative_target - .try_relative(relative_base) - .expect("fixture should resolve against cwd") - .into_owned() - .into_slash(), - expected_slash, - "fallible relative-to-slash composition observes cwd policy", - ); - let explicit = relative_target.relative_with(relative_base, &third); - assert_eq!( - explicit.as_os_str(), - PathBuf::from("..").join("..").join(second_name).join("relative-target.js").as_os_str(), - "explicit cwd must bypass ambient caching", - ); - assert!(matches!(explicit, Cow::Owned(_)), "explicit unequal-parent result should own"); assert_owned_relative( Path::new("entry.js"), &anchor, diff --git a/tests/cached_current_dir_relative.rs b/tests/cached_current_dir_relative.rs new file mode 100644 index 0000000..3dbc82f --- /dev/null +++ b/tests/cached_current_dir_relative.rs @@ -0,0 +1,107 @@ +use std::{ + borrow::Cow, + env, fs, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use sugar_path::{SugarPath, SugarPathBuf}; + +struct CurrentDirGuard { + original: PathBuf, + cleanup: PathBuf, +} + +impl Drop for CurrentDirGuard { + fn drop(&mut self) { + env::set_current_dir(&self.original).expect("restore the original current directory"); + fs::remove_dir_all(&self.cleanup).expect("remove the temporary directories"); + } +} + +fn assert_owned_relative(target: &Path, base: &Path, expected: &Path, context: &str) { + let strict = target.relative(base); + assert_eq!(strict.as_os_str(), expected.as_os_str(), "{context} strict"); + assert!(matches!(strict, Cow::Owned(_)), "{context} strict should own"); + + let fallible = target.try_relative(base).expect("fixture should resolve against cwd"); + assert_eq!(fallible.as_os_str(), expected.as_os_str(), "{context} try"); + assert!(matches!(fallible, Cow::Owned(_)), "{context} try should own"); +} + +#[test] +fn pure_relative_calls_initialize_and_observe_cwd_policy() { + let original = env::current_dir().expect("read the original current directory"); + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock is after the Unix epoch") + .as_nanos(); + let root = + env::temp_dir().join(format!("sugar-path-relative-cwd-{}-{unique}", std::process::id())); + let first = root.join("first"); + let second = root.join("second"); + let third = root.join("third"); + fs::create_dir_all(&first).expect("create the first temporary directory"); + fs::create_dir_all(&second).expect("create the second temporary directory"); + fs::create_dir_all(&third).expect("create the third temporary directory"); + let _guard = CurrentDirGuard { original, cleanup: root }; + + env::set_current_dir(&first).expect("enter the first temporary directory"); + assert_owned_relative( + Path::new("pkg/assets/./index.js"), + Path::new("pkg/chunks"), + &PathBuf::from("..").join("assets").join("index.js"), + "cwd-independent preflight", + ); + + env::set_current_dir(&second).expect("enter the second temporary directory"); + let relative_target = Path::new("../second/relative-target.js"); + let relative_base = Path::new("base"); + let initialized = relative_target + .try_relative(relative_base) + .expect("unequal-parent fixture should resolve against cwd"); + assert_eq!(initialized.as_os_str(), Path::new("../relative-target.js").as_os_str()); + assert!(matches!(initialized, Cow::Owned(_)), "initial unequal-parent result should own"); + + env::set_current_dir(&third).expect("enter the third temporary directory"); + let third = env::current_dir().expect("read the third temporary directory"); + let expected = if cfg!(feature = "cached_current_dir") { + PathBuf::from("..").join("relative-target.js") + } else { + PathBuf::from("..").join("..").join("second").join("relative-target.js") + }; + assert_owned_relative( + relative_target, + relative_base, + &expected, + "unequal-parent pair observes cwd policy", + ); + + let expected_slash = if cfg!(feature = "cached_current_dir") { + "../relative-target.js" + } else { + "../../second/relative-target.js" + }; + assert_eq!( + relative_target.relative(relative_base).into_owned().into_slash(), + expected_slash, + "strict relative-to-slash composition observes cwd policy", + ); + assert_eq!( + relative_target + .try_relative(relative_base) + .expect("fixture should resolve against cwd") + .into_owned() + .into_slash(), + expected_slash, + "fallible relative-to-slash composition observes cwd policy", + ); + + let explicit = relative_target.relative_with(relative_base, &third); + assert_eq!( + explicit.as_os_str(), + PathBuf::from("..").join("..").join("second").join("relative-target.js").as_os_str(), + "explicit cwd must bypass ambient caching", + ); + assert!(matches!(explicit, Cow::Owned(_)), "explicit unequal-parent result should own"); +} From 24515c654e071799c4e5f241949520de8622db20 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:35:18 +0800 Subject: [PATCH 3/3] test: use native relative separator --- tests/cached_current_dir_relative.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cached_current_dir_relative.rs b/tests/cached_current_dir_relative.rs index 3dbc82f..090cfa1 100644 --- a/tests/cached_current_dir_relative.rs +++ b/tests/cached_current_dir_relative.rs @@ -60,7 +60,7 @@ fn pure_relative_calls_initialize_and_observe_cwd_policy() { let initialized = relative_target .try_relative(relative_base) .expect("unequal-parent fixture should resolve against cwd"); - assert_eq!(initialized.as_os_str(), Path::new("../relative-target.js").as_os_str()); + assert_eq!(initialized.as_os_str(), PathBuf::from("..").join("relative-target.js").as_os_str(),); assert!(matches!(initialized, Cow::Owned(_)), "initial unequal-parent result should own"); env::set_current_dir(&third).expect("enter the third temporary directory");