From a999d14b9d878b9756d2a970a991bdabfee9ac89 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:01:20 +0800 Subject: [PATCH 1/4] test: cover absolutize contracts --- tests/absolutize_contracts.rs | 150 +++++++++++++++++++++++++++ tests/absolutize_invalid_encoding.rs | 118 +++++++++++++++++++++ tests/absolutize_windows_roots.rs | 43 ++++++++ tests/absolutize_without_cwd.rs | 8 +- 4 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 tests/absolutize_contracts.rs create mode 100644 tests/absolutize_invalid_encoding.rs create mode 100644 tests/absolutize_windows_roots.rs diff --git a/tests/absolutize_contracts.rs b/tests/absolutize_contracts.rs new file mode 100644 index 0000000..ee3536f --- /dev/null +++ b/tests/absolutize_contracts.rs @@ -0,0 +1,150 @@ +#![cfg(any(target_family = "unix", target_family = "windows"))] + +use std::{ + borrow::Cow, + env, + path::Path, +}; +#[cfg(target_family = "windows")] +use std::path::PathBuf; + +use sugar_path::SugarPath; + +#[derive(Clone, Copy)] +enum ExpectedCow { + Borrowed, + Owned, +} + +fn assert_result( + result: Cow<'_, Path>, + input: &Path, + expected: &Path, + expected_cow: ExpectedCow, + context: &str, +) { + assert_eq!(result.as_os_str(), expected.as_os_str(), "{context}"); + match (expected_cow, result) { + (ExpectedCow::Borrowed, Cow::Borrowed(result)) => { + assert!(std::ptr::eq(result, input), "{context}: result did not borrow the receiver"); + } + (ExpectedCow::Owned, Cow::Owned(_)) => {} + (ExpectedCow::Borrowed, Cow::Owned(_)) => panic!("{context}: expected borrowed result"), + (ExpectedCow::Owned, Cow::Borrowed(_)) => panic!("{context}: expected owned result"), + } +} + +fn assert_ambient_case(input: &Path, expected: &Path, expected_cow: ExpectedCow, name: &str) { + assert_result(input.absolutize(), input, expected, expected_cow, &format!("{name} strict")); + assert_result( + input.try_absolutize().expect("fixture should resolve against cwd"), + input, + expected, + expected_cow, + &format!("{name} try"), + ); +} + +fn assert_string_receiver(input: String, expected: &Path) { + let strict = input.absolutize(); + assert_eq!(strict.as_os_str(), expected.as_os_str()); + assert!(matches!(strict, Cow::Owned(_))); + + let fallible = input.try_absolutize().expect("String fixture should resolve against cwd"); + assert_eq!(fallible.as_os_str(), expected.as_os_str()); + assert!(matches!(fallible, Cow::Owned(_))); +} + +#[cfg(target_family = "unix")] +#[test] +fn unix_ambient_absolutize_and_try_contract_matrix() { + let cwd = env::current_dir().expect("read current directory"); + + let clean = Path::new("/some/β/file"); + assert_ambient_case(clean, clean, ExpectedCow::Borrowed, "clean absolute"); + assert_ambient_case( + Path::new("/some/./β/../file/"), + Path::new("/some/file"), + ExpectedCow::Owned, + "dirty absolute", + ); + assert_ambient_case(Path::new(""), &cwd, ExpectedCow::Owned, "empty"); + assert_ambient_case(Path::new("."), &cwd, ExpectedCow::Owned, "dot"); + assert_ambient_case( + Path::new("./pkg//β/../file/"), + &cwd.join("pkg/file"), + ExpectedCow::Owned, + "ordinary relative", + ); + + assert_string_receiver("./owned/../file".to_owned(), &cwd.join("file")); +} + +#[cfg(target_family = "windows")] +fn current_drive(path: &Path) -> u8 { + use std::path::{Component, Prefix}; + + match path.components().next() { + Some(Component::Prefix(prefix)) => match prefix.kind() { + Prefix::Disk(drive) | Prefix::VerbatimDisk(drive) => drive, + other => panic!("expected disk cwd, found {other:?}"), + }, + other => panic!("expected prefixed cwd, found {other:?}"), + } +} + +#[cfg(target_family = "windows")] +fn preserve_drive_spelling(path: &Path, drive: u8) -> PathBuf { + use std::{ + ffi::OsString, + os::windows::ffi::{OsStrExt, OsStringExt}, + }; + + let mut wide: Vec = path.as_os_str().encode_wide().collect(); + if wide.get(1) == Some(&(b':' as u16)) { + wide[0] = drive as u16; + } else if wide.get(5) == Some(&(b':' as u16)) + && wide.get(..4) == Some(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]) + { + wide[4] = drive as u16; + } else { + panic!("expected disk path, found {path:?}"); + } + PathBuf::from(OsString::from_wide(&wide)) +} + +#[cfg(target_family = "windows")] +#[test] +fn windows_ambient_absolutize_and_try_contract_matrix() { + let cwd = env::current_dir().expect("read current directory"); + + let clean = Path::new(r"c:\some\β\file"); + assert_ambient_case(clean, clean, ExpectedCow::Borrowed, "clean absolute"); + assert_ambient_case( + Path::new(r"c:/some/.\β\../file\"), + Path::new(r"c:\some\file"), + ExpectedCow::Owned, + "dirty absolute", + ); + assert_ambient_case(Path::new(""), &cwd, ExpectedCow::Owned, "empty"); + assert_ambient_case(Path::new("."), &cwd, ExpectedCow::Owned, "dot"); + assert_ambient_case( + Path::new(r".\pkg\\β\..\file\"), + &cwd.join(r"pkg\file"), + ExpectedCow::Owned, + "ordinary relative", + ); + + let root_relative = Path::new(r"\pkg\.\β\..\file\"); + let mut expected = cwd.clone(); + expected.push(r"\pkg\file"); + assert_ambient_case(root_relative, &expected, ExpectedCow::Owned, "root relative"); + + let drive = current_drive(&cwd).to_ascii_lowercase(); + let drive_relative = format!("{}:folder\\.\\file\\", drive as char); + let oracle = std::path::absolute(&drive_relative).expect("resolve drive-relative oracle"); + let expected = preserve_drive_spelling(&oracle, drive); + assert_ambient_case(Path::new(&drive_relative), &expected, ExpectedCow::Owned, "drive relative"); + + assert_string_receiver(r".\owned\..\file".to_owned(), &cwd.join("file")); +} diff --git a/tests/absolutize_invalid_encoding.rs b/tests/absolutize_invalid_encoding.rs new file mode 100644 index 0000000..2469413 --- /dev/null +++ b/tests/absolutize_invalid_encoding.rs @@ -0,0 +1,118 @@ +#![cfg(any(target_family = "unix", target_family = "windows"))] + +use std::{borrow::Cow, path::Path}; + +use sugar_path::SugarPath; + +#[cfg(target_family = "unix")] +mod unix { + use std::{ + env, + ffi::OsString, + os::unix::ffi::{OsStrExt, OsStringExt}, + path::PathBuf, + }; + + use super::*; + + fn path(bytes: &[u8]) -> PathBuf { + PathBuf::from(OsString::from_vec(bytes.to_vec())) + } + + fn assert_bytes(actual: &Path, expected: &Path, context: &str) { + assert_eq!(actual.as_os_str().as_bytes(), expected.as_os_str().as_bytes(), "{context}",); + } + + fn assert_ambient(input: &Path, expected: &Path, require_owned: bool, name: &str) { + let strict = input.absolutize(); + assert_bytes(&strict, expected, &format!("{name} strict")); + if require_owned { + assert!(matches!(strict, Cow::Owned(_)), "{name} strict should own"); + } + + let fallible = input.try_absolutize().expect("fixture should resolve against cwd"); + assert_bytes(&fallible, expected, &format!("{name} try")); + if require_owned { + assert!(matches!(fallible, Cow::Owned(_)), "{name} try should own"); + } + } + + #[test] + fn ambient_absolutization_preserves_invalid_unix_bytes() { + let clean = path(b"/sugar-path/invalid-\x80/file"); + assert_ambient(&clean, &clean, false, "clean absolute"); + + let dirty = path(b"/sugar-path/invalid-\x80/./file/"); + let normalized = path(b"/sugar-path/invalid-\x80/file"); + assert_ambient(&dirty, &normalized, true, "dirty absolute"); + + let relative = path(b"pkg/invalid-\x80/./file/"); + let expected = + env::current_dir().expect("read current directory").join(path(b"pkg/invalid-\x80/file")); + assert_ambient(&relative, &expected, true, "relative"); + } +} + +#[cfg(target_family = "windows")] +mod windows { + use std::{ + env, + ffi::OsString, + os::windows::ffi::{OsStrExt, OsStringExt}, + path::PathBuf, + }; + + use super::*; + + const INVALID_UNIT: u16 = 0xd800; + + fn invalid_path(before: &str, after: &str) -> PathBuf { + let mut wide: Vec = before.encode_utf16().collect(); + wide.push(INVALID_UNIT); + wide.extend(after.encode_utf16()); + PathBuf::from(OsString::from_wide(&wide)) + } + + fn assert_wide(actual: &Path, expected: &Path, context: &str) { + assert_eq!( + actual.as_os_str().encode_wide().collect::>(), + expected.as_os_str().encode_wide().collect::>(), + "{context}", + ); + } + + fn assert_ambient(input: &Path, expected: &Path, require_owned: bool, name: &str) { + let strict = input.absolutize(); + assert_wide(&strict, expected, &format!("{name} strict")); + if require_owned { + assert!(matches!(strict, Cow::Owned(_)), "{name} strict should own"); + } + + let fallible = input.try_absolutize().expect("fixture should resolve against cwd"); + assert_wide(&fallible, expected, &format!("{name} try")); + if require_owned { + assert!(matches!(fallible, Cow::Owned(_)), "{name} try should own"); + } + } + + #[test] + fn ambient_absolutization_preserves_invalid_windows_units() { + let clean = invalid_path(r"C:\sugar-path\invalid-", r"\file"); + assert_ambient(&clean, &clean, false, "clean absolute"); + + let dirty = invalid_path(r"C:\sugar-path\invalid-", r"\.\file\"); + let normalized = invalid_path(r"C:\sugar-path\invalid-", r"\file"); + assert_ambient(&dirty, &normalized, true, "dirty absolute"); + + let relative = invalid_path(r"pkg\invalid-", r"\.\file\"); + let expected = env::current_dir() + .expect("read current directory") + .join(invalid_path(r"pkg\invalid-", r"\file")); + assert_ambient(&relative, &expected, true, "relative"); + + let root_relative = invalid_path(r"\pkg\invalid-", r"\.\file\"); + let mut expected = env::current_dir().expect("read current directory"); + expected.push(invalid_path(r"\pkg\invalid-", r"\file")); + assert_ambient(&root_relative, &expected, true, "root relative"); + } +} diff --git a/tests/absolutize_windows_roots.rs b/tests/absolutize_windows_roots.rs new file mode 100644 index 0000000..c5b6f21 --- /dev/null +++ b/tests/absolutize_windows_roots.rs @@ -0,0 +1,43 @@ +#![cfg(target_family = "windows")] + +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; + +use sugar_path::SugarPath; + +fn assert_explicit_case(cwd: &str, input: &str, expected: &str, name: &str) { + assert!(Path::new(cwd).is_absolute(), "{name}: fixture cwd is not absolute"); + + let borrowed = Path::new(input).absolutize_with(Path::new(cwd)); + assert_eq!(borrowed.as_os_str(), Path::new(expected).as_os_str(), "{name} borrowed cwd"); + assert!(matches!(borrowed, Cow::Owned(_)), "{name} borrowed cwd should own"); + + let owned = Path::new(input).absolutize_with(PathBuf::from(cwd)); + assert_eq!(owned.as_os_str(), Path::new(expected).as_os_str(), "{name} owned cwd"); + assert!(matches!(owned, Cow::Owned(_)), "{name} owned cwd should own"); +} + +#[test] +fn windows_root_relative_inputs_use_the_exact_explicit_prefix() { + let cases = [ + ("disk", r"C:\workspace", r"\pkg\.\temp\..\β\file\", r"C:\pkg\β\file"), + ("disk root", r"C:\workspace", r"\", r"C:\"), + ("UNC", r"\\Server\Share\workspace", r"\pkg\..\file\", r"\\Server\Share\file"), + ("UNC root", r"\\Server\Share\workspace", r"\", r"\\Server\Share\"), + ("verbatim disk", r"\\?\c:\workspace", r"\pkg\..\file\", r"\\?\c:\file"), + ("verbatim disk root", r"\\?\c:\workspace", r"\", r"\\?\c:\"), + ( + "verbatim UNC", + r"\\?\UNC\Server\Share\workspace", + r"\pkg\..\file\", + r"\\?\UNC\Server\Share\file", + ), + ("verbatim UNC root", r"\\?\UNC\Server\Share\workspace", r"\", r"\\?\UNC\Server\Share\"), + ]; + + for (name, cwd, input, expected) in cases { + assert_explicit_case(cwd, input, expected, name); + } +} diff --git a/tests/absolutize_without_cwd.rs b/tests/absolutize_without_cwd.rs index a468f5c..9e9f91d 100644 --- a/tests/absolutize_without_cwd.rs +++ b/tests/absolutize_without_cwd.rs @@ -22,13 +22,17 @@ fn absolute_paths_do_not_require_current_directory() { let clean_output = clean.absolutize(); assert!(matches!(clean_output, Cow::Borrowed(_))); assert_eq!(clean_output.as_os_str(), clean.as_os_str()); - assert!(clean.try_absolutize().is_ok()); + let clean_try = clean.try_absolutize().expect("clean absolute path should succeed"); + assert!(matches!(clean_try, Cow::Borrowed(_))); + assert_eq!(clean_try.as_os_str(), clean.as_os_str()); let dirty = Path::new("/sugar-path/../file.js/"); let dirty_output = dirty.absolutize(); assert!(matches!(dirty_output, Cow::Owned(_))); assert_eq!(dirty_output.as_os_str(), Path::new("/file.js").as_os_str()); - assert!(dirty.try_absolutize().is_ok()); + let dirty_try = dirty.try_absolutize().expect("dirty absolute path should succeed"); + assert!(matches!(dirty_try, Cow::Owned(_))); + assert_eq!(dirty_try.as_os_str(), Path::new("/file.js").as_os_str()); assert!(Path::new("relative.js").try_absolutize().is_err()); assert!(std::panic::catch_unwind(|| Path::new("relative.js").absolutize()).is_err()); From e6559b328829f0cc19ad335c8c6c93d70f2b55d8 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:02:54 +0800 Subject: [PATCH 2/4] style: apply current rustfmt --- tests/absolutize_contracts.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/absolutize_contracts.rs b/tests/absolutize_contracts.rs index ee3536f..538f1f0 100644 --- a/tests/absolutize_contracts.rs +++ b/tests/absolutize_contracts.rs @@ -1,12 +1,8 @@ #![cfg(any(target_family = "unix", target_family = "windows"))] -use std::{ - borrow::Cow, - env, - path::Path, -}; #[cfg(target_family = "windows")] use std::path::PathBuf; +use std::{borrow::Cow, env, path::Path}; use sugar_path::SugarPath; From 36a53a9f2dc83bf86f6bee9e9de52009144ba270 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:07:40 +0800 Subject: [PATCH 3/4] test: fix Windows ambient coverage --- tests/absolutize_contracts.rs | 25 ++++++++++++++++--------- tests/absolutize_invalid_encoding.rs | 27 +++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/tests/absolutize_contracts.rs b/tests/absolutize_contracts.rs index 538f1f0..b5c888a 100644 --- a/tests/absolutize_contracts.rs +++ b/tests/absolutize_contracts.rs @@ -77,15 +77,15 @@ fn unix_ambient_absolutize_and_try_contract_matrix() { } #[cfg(target_family = "windows")] -fn current_drive(path: &Path) -> u8 { +fn current_ordinary_disk_drive(path: &Path) -> Option { use std::path::{Component, Prefix}; match path.components().next() { Some(Component::Prefix(prefix)) => match prefix.kind() { - Prefix::Disk(drive) | Prefix::VerbatimDisk(drive) => drive, - other => panic!("expected disk cwd, found {other:?}"), + Prefix::Disk(drive) => Some(drive), + _ => None, }, - other => panic!("expected prefixed cwd, found {other:?}"), + _ => None, } } @@ -136,11 +136,18 @@ fn windows_ambient_absolutize_and_try_contract_matrix() { expected.push(r"\pkg\file"); assert_ambient_case(root_relative, &expected, ExpectedCow::Owned, "root relative"); - let drive = current_drive(&cwd).to_ascii_lowercase(); - let drive_relative = format!("{}:folder\\.\\file\\", drive as char); - let oracle = std::path::absolute(&drive_relative).expect("resolve drive-relative oracle"); - let expected = preserve_drive_spelling(&oracle, drive); - assert_ambient_case(Path::new(&drive_relative), &expected, ExpectedCow::Owned, "drive relative"); + if let Some(drive) = current_ordinary_disk_drive(&cwd) { + let drive = drive.to_ascii_lowercase(); + let drive_relative = format!("{}:folder\\.\\file\\", drive as char); + let mut expected = preserve_drive_spelling(&cwd, drive); + expected.push(r"folder\file"); + assert_ambient_case( + Path::new(&drive_relative), + &expected, + ExpectedCow::Owned, + "drive relative", + ); + } assert_string_receiver(r".\owned\..\file".to_owned(), &cwd.join("file")); } diff --git a/tests/absolutize_invalid_encoding.rs b/tests/absolutize_invalid_encoding.rs index 2469413..51e6921 100644 --- a/tests/absolutize_invalid_encoding.rs +++ b/tests/absolutize_invalid_encoding.rs @@ -81,6 +81,18 @@ mod windows { ); } + fn current_ordinary_disk_drive(path: &Path) -> Option { + use std::path::{Component, Prefix}; + + match path.components().next() { + Some(Component::Prefix(prefix)) => match prefix.kind() { + Prefix::Disk(drive) => Some(drive), + _ => None, + }, + _ => None, + } + } + fn assert_ambient(input: &Path, expected: &Path, require_owned: bool, name: &str) { let strict = input.absolutize(); assert_wide(&strict, expected, &format!("{name} strict")); @@ -97,6 +109,8 @@ mod windows { #[test] fn ambient_absolutization_preserves_invalid_windows_units() { + let cwd = env::current_dir().expect("read current directory"); + let clean = invalid_path(r"C:\sugar-path\invalid-", r"\file"); assert_ambient(&clean, &clean, false, "clean absolute"); @@ -105,14 +119,19 @@ mod windows { assert_ambient(&dirty, &normalized, true, "dirty absolute"); let relative = invalid_path(r"pkg\invalid-", r"\.\file\"); - let expected = env::current_dir() - .expect("read current directory") - .join(invalid_path(r"pkg\invalid-", r"\file")); + let expected = cwd.join(invalid_path(r"pkg\invalid-", r"\file")); assert_ambient(&relative, &expected, true, "relative"); let root_relative = invalid_path(r"\pkg\invalid-", r"\.\file\"); - let mut expected = env::current_dir().expect("read current directory"); + let mut expected = cwd.clone(); expected.push(invalid_path(r"\pkg\invalid-", r"\file")); assert_ambient(&root_relative, &expected, true, "root relative"); + + if let Some(drive) = current_ordinary_disk_drive(&cwd) { + let prefix = format!("{}:pkg\\invalid-", drive as char); + let drive_relative = invalid_path(&prefix, r"\.\file\"); + let expected = cwd.join(invalid_path(r"pkg\invalid-", r"\file")); + assert_ambient(&drive_relative, &expected, false, "drive relative"); + } } } From 11b5a7934ae6f3d878e6de7b06b7d07f228250aa Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Tue, 14 Jul 2026 18:10:19 +0800 Subject: [PATCH 4/4] test: match verbatim UNC root semantics --- tests/absolutize_windows_roots.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/absolutize_windows_roots.rs b/tests/absolutize_windows_roots.rs index c5b6f21..3a56cbb 100644 --- a/tests/absolutize_windows_roots.rs +++ b/tests/absolutize_windows_roots.rs @@ -34,7 +34,9 @@ fn windows_root_relative_inputs_use_the_exact_explicit_prefix() { r"\pkg\..\file\", r"\\?\UNC\Server\Share\file", ), - ("verbatim UNC root", r"\\?\UNC\Server\Share\workspace", r"\", r"\\?\UNC\Server\Share\"), + // A verbatim UNC prefix is already rooted and absolute without an explicit + // RootDir component, so resolution strips the optional trailing separator. + ("verbatim UNC root", r"\\?\UNC\Server\Share\workspace", r"\", r"\\?\UNC\Server\Share"), ]; for (name, cwd, input, expected) in cases {