Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/docs/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions tests/absolutize_without_cwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
9 changes: 9 additions & 0 deletions tests/as_path.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/cached_current_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
{
Expand Down
9 changes: 9 additions & 0 deletions tests/cached_current_dir_relative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions tests/relative_without_cwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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;
}

Expand Down
49 changes: 49 additions & 0 deletions tests/string_receiver_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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",
);
}