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
19 changes: 13 additions & 6 deletions tests/invalid_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@ mod unix {
}

#[test]
fn slash_conversion_reports_and_replaces_invalid_encoding() {
let input = path(b"dir/\x80/file");
fn non_utf8_slash_policies_preserve_non_normalized_spelling() {
let input = path(b"./dir//invalid-\x80/../tail/");
let expected = "./dir//invalid-\u{fffd}/../tail/";

assert!(input.try_to_slash().is_none());
assert!(std::panic::catch_unwind(|| input.to_slash()).is_err());
let lossy = input.to_slash_lossy();
assert_eq!(lossy, "dir/\u{fffd}/file");
assert_eq!(lossy, expected);
assert!(matches!(&lossy, Cow::Owned(_)));
}

Expand Down Expand Up @@ -319,13 +320,19 @@ mod windows {
}

#[test]
fn slash_conversion_reports_and_replaces_invalid_encoding() {
let input = invalid_path(r"dir\", r"\file");
fn invalid_unicode_slash_policies_preserve_non_normalized_spelling() {
let input = invalid_path(r".\dir\\invalid-", r"\..\tail\");
let expected = "./dir//invalid-\u{fffd}/../tail/";

assert!(input.try_to_slash().is_none());
assert!(std::panic::catch_unwind(|| input.to_slash()).is_err());
let lossy = input.to_slash_lossy();
assert_eq!(lossy, "dir/\u{fffd}/file");
assert_eq!(lossy, expected);
assert!(matches!(&lossy, Cow::Owned(_)));

let no_native_separator = invalid_path("invalid-", "/tail");
let lossy = no_native_separator.to_slash_lossy();
assert_eq!(lossy, "invalid-\u{fffd}/tail");
assert!(matches!(&lossy, Cow::Owned(_)));
}

Expand Down
107 changes: 71 additions & 36 deletions tests/owned_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,42 @@ fn into_normalized_matches_borrowed_api_for_dirty_paths() {
#[test]
fn owned_slash_apis_reuse_valid_unicode_buffers() {
#[cfg(target_family = "unix")]
let (input, expected) = (
"/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
"/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
);
let cases = [
(
"/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
"/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
),
(r"/root/./β/../tail//literal\name//", r"/root/./β/../tail//literal\name//"),
];
#[cfg(target_family = "windows")]
let (input, expected) = (
r"C:\workspace\rolldown\crates\rolldown\src\module_loader\module_task.rs",
"C:/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
);
let cases = [
(
r"C:\workspace\rolldown\crates\rolldown\src\module_loader\module_task.rs",
"C:/workspace/rolldown/crates/rolldown/src/module_loader/module_task.rs",
),
(r"\\server\share\.\β\..\tail\\foreign/name\\", "//server/share/./β/../tail//foreign/name//"),
("//server/share/./β/../tail//foreign/name//", "//server/share/./β/../tail//foreign/name//"),
];

let path = owned_path_with_capacity(input);
let identity = buffer_identity(&path);
let slash = path.into_slash();
for (input, expected) in cases {
let path = owned_path_with_capacity(input);
let identity = buffer_identity(&path);
let slash = path.into_slash();
assert_eq!(slash, expected, "strict: input {input:?}");
assert_eq!((slash.as_ptr(), slash.capacity()), identity, "strict: input {input:?}");

assert_eq!(slash, expected);
assert_eq!((slash.as_ptr(), slash.capacity()), identity);
let path = owned_path_with_capacity(input);
let identity = buffer_identity(&path);
let slash = path.try_into_slash().expect("the fixture is valid Unicode");
assert_eq!(slash, expected, "try: input {input:?}");
assert_eq!((slash.as_ptr(), slash.capacity()), identity, "try: input {input:?}");

let path = owned_path_with_capacity(input);
let identity = buffer_identity(&path);
let slash = path.try_into_slash().expect("the fixture is valid Unicode");
assert_eq!(slash, expected);
assert_eq!((slash.as_ptr(), slash.capacity()), identity);
let path = owned_path_with_capacity(input);
let identity = buffer_identity(&path);
let slash = path.into_slash_lossy();
assert_eq!(slash, expected, "lossy: input {input:?}");
assert_eq!((slash.as_ptr(), slash.capacity()), identity, "lossy: input {input:?}");
}
}

#[cfg(target_family = "unix")]
Expand All @@ -101,13 +115,20 @@ fn owned_apis_preserve_and_replace_invalid_unix_encoding() {
os::unix::ffi::{OsStrExt, OsStringExt},
};

let input = PathBuf::from(OsString::from_vec(b"dir/invalid-\x80/./file".to_vec()));
let normalized = input.clone().into_normalized();
let normalize_input = PathBuf::from(OsString::from_vec(b"dir/invalid-\x80/./file".to_vec()));
let normalized = normalize_input.into_normalized();
assert_eq!(normalized.as_os_str().as_bytes(), b"dir/invalid-\x80/file");

let input = PathBuf::from(OsString::from_vec(b"./dir//invalid-\x80/../tail/".to_vec()));

let strict = input.clone();
assert!(std::panic::catch_unwind(move || strict.into_slash()).is_err());

let recoverable = input.clone();
let returned =
input.clone().try_into_slash().expect_err("invalid Unix encoding must be returned unchanged");
recoverable.try_into_slash().expect_err("invalid Unix encoding must be returned unchanged");
assert_eq!(returned.as_os_str().as_bytes(), input.as_os_str().as_bytes());
assert_eq!(input.into_slash_lossy(), "dir/invalid-\u{fffd}/./file");
assert_eq!(input.into_slash_lossy(), "./dir//invalid-\u{fffd}/../tail/");
}

#[cfg(target_family = "windows")]
Expand All @@ -118,22 +139,36 @@ fn owned_apis_preserve_and_replace_invalid_windows_encoding() {
os::windows::ffi::{OsStrExt, OsStringExt},
};

let mut input_wide: Vec<u16> = r"C:\workspace\invalid-".encode_utf16().collect();
input_wide.push(0xd800);
input_wide.extend(r"\.\file".encode_utf16());
let input = PathBuf::from(OsString::from_wide(&input_wide));
let mut normalize_input_wide: Vec<u16> = r"C:\workspace\invalid-".encode_utf16().collect();
normalize_input_wide.push(0xd800);
normalize_input_wide.extend(r"\.\file".encode_utf16());
let normalize_input = PathBuf::from(OsString::from_wide(&normalize_input_wide));

let mut expected_wide: Vec<u16> = r"C:\workspace\invalid-".encode_utf16().collect();
expected_wide.push(0xd800);
expected_wide.extend(r"\file".encode_utf16());
let mut normalized_wide: Vec<u16> = r"C:\workspace\invalid-".encode_utf16().collect();
normalized_wide.push(0xd800);
normalized_wide.extend(r"\file".encode_utf16());
assert_eq!(
input.clone().into_normalized().as_os_str().encode_wide().collect::<Vec<_>>(),
expected_wide,
normalize_input.into_normalized().as_os_str().encode_wide().collect::<Vec<_>>(),
normalized_wide,
);
let returned = input
.clone()
.try_into_slash()
.expect_err("invalid Windows encoding must be returned unchanged");

let mut input_wide: Vec<u16> = r".\dir\\invalid-".encode_utf16().collect();
input_wide.push(0xd800);
input_wide.extend(r"\..\tail\".encode_utf16());
let input = PathBuf::from(OsString::from_wide(&input_wide));

let strict = input.clone();
assert!(std::panic::catch_unwind(move || strict.into_slash()).is_err());

let recoverable = input.clone();
let returned =
recoverable.try_into_slash().expect_err("invalid Windows encoding must be returned unchanged");
assert_eq!(returned.as_os_str().encode_wide().collect::<Vec<_>>(), input_wide);
assert_eq!(input.into_slash_lossy(), "C:/workspace/invalid-\u{fffd}/./file");
assert_eq!(input.into_slash_lossy(), "./dir//invalid-\u{fffd}/../tail/");

let mut no_native_separator_wide: Vec<u16> = "invalid-".encode_utf16().collect();
no_native_separator_wide.push(0xd800);
no_native_separator_wide.extend("/tail".encode_utf16());
let no_native_separator = PathBuf::from(OsString::from_wide(&no_native_separator_wide));
assert_eq!(no_native_separator.into_slash_lossy(), "invalid-\u{fffd}/tail");
}
153 changes: 116 additions & 37 deletions tests/to_slash.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,132 @@
use std::{
borrow::Cow,
path::{Path, PathBuf},
};

use sugar_path::SugarPath;

fn assert_cow_result(
result: Cow<'_, str>,
source: &str,
expected: &str,
expect_borrowed: bool,
receiver: &str,
policy: &str,
) {
assert_eq!(result, expected, "{receiver}/{policy}: input {source:?}");

match result {
Cow::Borrowed(actual) => {
assert!(expect_borrowed, "{receiver}/{policy} unexpectedly borrowed: input {source:?}");
assert_eq!(actual.as_ptr(), source.as_ptr(), "{receiver}/{policy}: input {source:?}");
assert_eq!(actual.len(), source.len(), "{receiver}/{policy}: input {source:?}");
}
Cow::Owned(_) => {
assert!(!expect_borrowed, "{receiver}/{policy} unexpectedly allocated: input {source:?}");
}
}
}

fn assert_policies<'a>(
receiver: &str,
source: &'a str,
strict: Cow<'a, str>,
fallible: Option<Cow<'a, str>>,
lossy: Cow<'a, str>,
expected: &str,
expect_borrowed: bool,
) {
assert_cow_result(strict, source, expected, expect_borrowed, receiver, "strict");
assert_cow_result(
fallible.expect("the fixture is valid UTF-8"),
source,
expected,
expect_borrowed,
receiver,
"try",
);
assert_cow_result(lossy, source, expected, expect_borrowed, receiver, "lossy");
}

fn assert_valid_case(input: &str, expected: &str, expect_borrowed: bool) {
assert_policies(
"str",
input,
input.to_slash(),
input.try_to_slash(),
input.to_slash_lossy(),
expected,
expect_borrowed,
);

let string = input.to_owned();
assert_policies(
"String",
&string,
string.to_slash(),
string.try_to_slash(),
string.to_slash_lossy(),
expected,
expect_borrowed,
);

let path = Path::new(input);
let source = path.to_str().expect("the fixture is valid UTF-8");
assert_policies(
"Path",
source,
path.to_slash(),
path.try_to_slash(),
path.to_slash_lossy(),
expected,
expect_borrowed,
);

let path_buf = PathBuf::from(input);
let source = path_buf.to_str().expect("the fixture is valid UTF-8");
assert_policies(
"PathBuf",
source,
path_buf.to_slash(),
path_buf.try_to_slash(),
path_buf.to_slash_lossy(),
expected,
expect_borrowed,
);
}

#[cfg(target_family = "unix")]
#[test]
fn unix() {
use std::borrow::Cow;
use sugar_path::SugarPath;

fn unix_valid_receiver_and_policy_matrix_is_exact_and_borrowed() {
let cases = [
("hello/world", "hello/world"),
("hello/world/", "hello/world/"),
("/hello/world", "/hello/world"),
("/hello/world/", "/hello/world/"),
("/hello\\world", "/hello\\world"),
(r"/root/./β/../tail//literal\name//", r"/root/./β/../tail//literal\name//"),
];

for (input, right) in cases {
let strict = input.to_slash();
let lossy = input.to_slash_lossy();
assert!(matches!(strict, Cow::Borrowed(_)), "strict result should borrow: {input:#?}");
assert!(matches!(lossy, Cow::Borrowed(_)), "lossy result should borrow: {input:#?}");
assert_eq!(strict, right, "case: {input:#?}");
assert_eq!(lossy, right, "case: {input:#?}");
for (input, expected) in cases {
assert_valid_case(input, expected, true);
}
}

#[cfg(target_family = "windows")]
#[test]
fn windows() {
use std::borrow::Cow;
use sugar_path::SugarPath;

fn windows_valid_receiver_and_policy_matrix_preserves_spelling() {
let cases = [
("hello\\world", "hello/world"),
("hello\\world\\", "hello/world/"),
("c:hello\\world", "c:hello/world"),
("c:hello\\world\\", "c:hello/world/"),
("c:\\hello\\world", "c:/hello/world"),
("c:\\hello\\world/", "c:/hello/world/"),
(r"c:hello\world\", "c:hello/world/", false),
(r"C:\hello\world\", "C:/hello/world/", false),
(
r"\\server\share\.\β\..\tail\\foreign/name\\",
"//server/share/./β/../tail//foreign/name//",
false,
),
(
"//server/share/./β/../tail//foreign/name//",
"//server/share/./β/../tail//foreign/name//",
true,
),
];

for (input, right) in cases {
let strict = input.to_slash();
let lossy = input.to_slash_lossy();
assert!(matches!(strict, Cow::Owned(_)), "strict result should own: {input:#?}");
assert!(matches!(lossy, Cow::Owned(_)), "lossy result should own: {input:#?}");
assert_eq!(strict, right, "case: {input:#?}");
assert_eq!(lossy, right, "case: {input:#?}");
for (input, expected, expect_borrowed) in cases {
assert_valid_case(input, expected, expect_borrowed);
}

let slash_only = "C:/hello/world";
assert!(matches!(slash_only.to_slash(), Cow::Borrowed(_)));
assert!(matches!(slash_only.try_to_slash(), Some(Cow::Borrowed(_))));
assert!(matches!(slash_only.to_slash_lossy(), Cow::Borrowed(_)));
}
Loading