From 0e3406ed1ce39a6f272524311d5dd9c9a79917df Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Wed, 15 Jul 2026 15:54:00 +0800 Subject: [PATCH] test: cover string receiver context contracts --- .agents/docs/testing-strategy.md | 2 +- tests/absolutize_without_cwd.rs | 19 +++++++++++ tests/as_path.rs | 9 +++++ tests/cached_current_dir.rs | 4 +++ tests/cached_current_dir_relative.rs | 9 +++++ tests/relative_without_cwd.rs | 14 ++++++++ tests/string_receiver_contracts.rs | 49 ++++++++++++++++++++++++++++ 7 files changed, 105 insertions(+), 1 deletion(-) diff --git a/.agents/docs/testing-strategy.md b/.agents/docs/testing-strategy.md index ccd68b0..e154ef3 100644 --- a/.agents/docs/testing-strategy.md +++ b/.agents/docs/testing-strategy.md @@ -37,7 +37,7 @@ The matrix is interaction-aware. A platform root kind must be crossed with encod - On macOS and Linux, all 224,676 pairs from the bounded short absolute spelling set plus the multibyte set are checked through the production `relative_str` dispatch and the suffix-validation helper against the slow component oracle. - Absolute paths with native-invalid normal components prove that equal raw encoding cancels and distinct encoding with the same lossy rendering does not, through `relative`, `try_relative`, and `relative_with` on Unix and Windows. - Unavailable Unix cwd coverage pins exact successful output and `Cow` variants for cwd-independent fallible calls, preserves the ambient error and panic checks for dependent `Path`, `str`, and `String` calls, and proves that a valid explicit cwd still succeeds. -- Known-UTF-8 `str` and `String` receivers prove that clean normalize, absolutize, and descendant relative results borrow only from the receiver on Unix and Windows. +- Known-UTF-8 `str` and `String` receivers prove exact clean-trailing and dirty normalization contracts, receiver-only borrowing, explicit and ambient relative context forwarding, and cached-cwd behavior on Unix and Windows. Unix deleted-cwd rows also prove cwd-independent success and strict cwd-dependent panics for string receivers. ## Follow-up coverage status diff --git a/tests/absolutize_without_cwd.rs b/tests/absolutize_without_cwd.rs index 3dfeba5..163ba0f 100644 --- a/tests/absolutize_without_cwd.rs +++ b/tests/absolutize_without_cwd.rs @@ -38,6 +38,25 @@ fn absolute_paths_do_not_require_current_directory() { assert!("relative.js".try_absolutize().is_err()); assert!(String::from("relative.js").try_absolutize().is_err()); assert!(std::panic::catch_unwind(|| Path::new("relative.js").absolutize()).is_err()); + assert!( + std::panic::catch_unwind(|| { + let input = String::from("relative.js"); + drop(input.absolutize()); + }) + .is_err(), + ); + + let clean_string = String::from("/sugar-path/string.js"); + let clean_string_output = + clean_string.try_absolutize().expect("clean absolute String should not read cwd"); + assert_eq!(clean_string_output.as_os_str(), Path::new("/sugar-path/string.js").as_os_str()); + assert!(matches!(clean_string_output, Cow::Borrowed(_))); + + let dirty_string = String::from("/sugar-path/../string.js/"); + let dirty_string_output = + dirty_string.try_absolutize().expect("dirty absolute String should not read cwd"); + assert_eq!(dirty_string_output.as_os_str(), Path::new("/string.js").as_os_str()); + assert!(matches!(dirty_string_output, Cow::Owned(_))); return; } diff --git a/tests/as_path.rs b/tests/as_path.rs index 4747ef2..fc059e3 100644 --- a/tests/as_path.rs +++ b/tests/as_path.rs @@ -1,6 +1,15 @@ use std::path::Path; use sugar_path::SugarPath; +#[test] +fn path_receiver_returns_the_same_view() { + let receiver = Path::new("hello/world"); + let viewed = SugarPath::as_path(receiver); + + assert_eq!(viewed, receiver); + assert!(std::ptr::eq(viewed, receiver)); +} + #[test] fn test_as_path_on_str() { // Test that as_path() converts &str to Path correctly diff --git a/tests/cached_current_dir.rs b/tests/cached_current_dir.rs index 20caefd..f5d8138 100644 --- a/tests/cached_current_dir.rs +++ b/tests/cached_current_dir.rs @@ -92,6 +92,10 @@ fn current_directory_is_cached_only_when_requested() { "relative base observes cwd policy", ); assert_eq!(Path::new("entry.js").absolutize(), expected_base.join("entry.js")); + let string_entry = String::from("string-entry.js"); + let string_absolute = string_entry.absolutize(); + assert_eq!(string_absolute.as_os_str(), expected_base.join("string-entry.js").as_os_str()); + assert!(matches!(string_absolute, Cow::Owned(_)), "relative String absolutize should own"); #[cfg(target_family = "windows")] { diff --git a/tests/cached_current_dir_relative.rs b/tests/cached_current_dir_relative.rs index 090cfa1..72d7cda 100644 --- a/tests/cached_current_dir_relative.rs +++ b/tests/cached_current_dir_relative.rs @@ -76,6 +76,15 @@ fn pure_relative_calls_initialize_and_observe_cwd_policy() { &expected, "unequal-parent pair observes cwd policy", ); + let string_target = String::from("../second/string-target.js"); + let string_relative = string_target.relative(relative_base); + let expected_string = if cfg!(feature = "cached_current_dir") { + PathBuf::from("..").join("string-target.js") + } else { + PathBuf::from("..").join("..").join("second").join("string-target.js") + }; + assert_eq!(string_relative.as_os_str(), expected_string.as_os_str()); + assert!(matches!(string_relative, Cow::Owned(_)), "cwd-resolved String relative should own"); let expected_slash = if cfg!(feature = "cached_current_dir") { "../relative-target.js" diff --git a/tests/relative_without_cwd.rs b/tests/relative_without_cwd.rs index 4dc6c6a..ea675bc 100644 --- a/tests/relative_without_cwd.rs +++ b/tests/relative_without_cwd.rs @@ -40,6 +40,12 @@ fn cwd_independent_relative_inputs_do_not_read_the_current_directory() { assert!("../../dist/assets/index.js".try_relative("../dist/chunks").is_err()); assert!(String::from("../../dist/assets/index.js").try_relative("../dist/chunks").is_err(),); + let absolute_string = String::from("/workspace/src"); + let absolute_relative = + absolute_string.try_relative("/workspace").expect("absolute String paths do not need cwd"); + assert_eq!(absolute_relative.as_os_str(), Path::new("src").as_os_str()); + assert!(matches!(absolute_relative, Cow::Borrowed(_))); + let explicit = Path::new("../../dist/assets/index.js") .relative_with(Path::new("../dist/chunks"), Path::new("/")); assert_eq!(explicit.as_os_str(), Path::new("../assets/index.js").as_os_str()); @@ -49,6 +55,14 @@ fn cwd_independent_relative_inputs_do_not_read_the_current_directory() { Path::new("../../dist/assets/index.js").relative(Path::new("../dist/chunks")) })); assert!(cwd_dependent.is_err(), "unequal leading parents must retain the cwd fallback"); + let string_cwd_dependent = catch_unwind(AssertUnwindSafe(|| { + let input = String::from("../../dist/assets/index.js"); + drop(input.relative("../dist/chunks")); + })); + assert!( + string_cwd_dependent.is_err(), + "cwd-dependent String relative must preserve the strict panic contract", + ); return; } diff --git a/tests/string_receiver_contracts.rs b/tests/string_receiver_contracts.rs index 8cf5f86..ac099c4 100644 --- a/tests/string_receiver_contracts.rs +++ b/tests/string_receiver_contracts.rs @@ -27,6 +27,11 @@ fn assert_borrowed_from_receiver( ); } +fn assert_owned_result(result: Cow<'_, Path>, expected: &Path, context: &str) { + assert_eq!(result.as_os_str(), expected.as_os_str(), "{context}"); + assert!(matches!(result, Cow::Owned(_)), "{context}: expected an owned result"); +} + fn assert_receiver_contracts(input: &str, base: &str, expected_relative: &str, label: &str) { let receiver = Path::new(input); assert_borrowed_from_receiver( @@ -131,3 +136,47 @@ fn utf8_string_receivers_borrow_only_from_the_receiver() { "Windows", ); } + +#[test] +fn utf8_string_normalize_covers_trailing_and_dirty_spelling() { + #[cfg(target_family = "unix")] + let (clean, dirty, normalized) = ("workspace/src/", "./workspace/src", "workspace/src"); + #[cfg(target_family = "windows")] + let (clean, dirty, normalized) = (r"workspace\src\", r".\workspace\src", r"workspace\src"); + + assert_borrowed_from_receiver( + Path::new(clean), + clean.normalize(), + Path::new(clean), + "clean trailing str normalize", + ); + let clean_owned = clean.to_owned(); + assert_borrowed_from_receiver( + Path::new(&clean_owned), + clean_owned.normalize(), + Path::new(clean), + "clean trailing String normalize", + ); + + assert_owned_result(dirty.normalize(), Path::new(normalized), "dirty str normalize"); + let dirty_owned = dirty.to_owned(); + assert_owned_result(dirty_owned.normalize(), Path::new(normalized), "dirty String normalize"); +} + +#[test] +fn utf8_string_receivers_forward_explicit_relative_context() { + #[cfg(target_family = "unix")] + let (target, base, cwd, expected) = + ("target", "/workspace/base", "/workspace/project", "../project/target"); + #[cfg(target_family = "windows")] + let (target, base, cwd, expected) = + (r"target", r"C:\workspace\base", r"C:\workspace\project", r"..\project\target"); + + assert_owned_result(target.relative_with(base, cwd), Path::new(expected), "str relative_with"); + let target = target.to_owned(); + assert_owned_result( + target.relative_with(base, cwd.to_owned()), + Path::new(expected), + "String relative_with", + ); +}