use super::*;
use std::env;
fn prepare_str(path: &str) -> Result<Wtf16String, PathError> {
prepare(&Wtf16String::from(path)).map(PreparedPath::into_wtf16)
}
fn text(path: &Wtf16Str) -> String {
path.to_string_lossy()
}
#[test]
fn an_empty_path_is_rejected() {
let error = prepare(&Wtf16String::new()).expect_err("an empty path names nothing");
assert_eq!(error.failure(), PathFailure::EmptyPath);
assert_eq!(error.raw_os_error(), None);
}
#[test]
fn an_interior_nul_is_rejected() {
let path = Wtf16String::from_units(&[0x0043, 0x003A, 0x005C, 0x0000, 0x0061]);
let error = prepare(&path).expect_err("an interior NUL truncates the path");
assert_eq!(error.failure(), PathFailure::InteriorNul);
}
#[test]
fn a_verbatim_drive_path_is_kept_exactly() {
let prepared = prepare_str(r"\\?\C:\Windows\System32").expect("fully qualified");
assert_eq!(text(&prepared), r"\\?\C:\Windows\System32");
}
#[test]
fn a_verbatim_path_keeps_its_trailing_separator() {
let prepared = prepare_str(r"\\?\C:\Windows\").expect("fully qualified");
assert_eq!(text(&prepared), r"\\?\C:\Windows\");
}
#[test]
fn a_verbatim_path_keeps_dot_components_verbatim() {
let prepared = prepare_str(r"\\?\C:\a\..\b").expect("fully qualified");
assert_eq!(text(&prepared), r"\\?\C:\a\..\b");
}
#[test]
fn a_verbatim_path_may_exceed_max_path() {
let long = format!(r"\\?\C:\{}", "a".repeat(400));
let prepared = prepare_str(&long).expect("verbatim paths carry no MAX_PATH limit");
assert_eq!(text(&prepared), long);
}
#[test]
fn a_verbatim_unc_path_is_accepted() {
let prepared = prepare_str(r"\\?\UNC\server\share\dir").expect("fully qualified");
assert_eq!(text(&prepared), r"\\?\UNC\server\share\dir");
prepare_str(r"\\?\UNC\server\share").expect("a share root is fully qualified");
}
#[test]
fn a_verbatim_volume_guid_path_is_accepted() {
prepare_str(r"\\?\Volume{12345678-1234-1234-1234-123456789abc}\dir")
.expect("a volume GUID names an absolute root");
}
#[test]
fn a_drive_relative_verbatim_path_is_rejected() {
let error = prepare_str(r"\\?\C:foo").expect_err("not fully qualified");
assert_eq!(error.failure(), PathFailure::NotFullyQualified);
}
#[test]
fn a_rootless_verbatim_path_is_rejected() {
let error = prepare_str(r"\\?\name").expect_err("no root component");
assert_eq!(error.failure(), PathFailure::NotFullyQualified);
}
#[test]
fn a_verbatim_path_with_an_empty_root_is_rejected() {
let error = prepare_str(r"\\?\\dir").expect_err("the root component is empty");
assert_eq!(error.failure(), PathFailure::NotFullyQualified);
}
#[test]
fn an_incomplete_verbatim_unc_path_is_rejected() {
for path in [
r"\\?\UNC\server",
r"\\?\UNC\server\",
r"\\?\UNC\\share",
r"\\?\UNC\",
] {
let error = prepare_str(path).expect_err("a server without a share names no filesystem");
assert_eq!(
error.failure(),
PathFailure::NotFullyQualified,
"for {path}"
);
}
}
#[test]
fn an_ordinary_absolute_path_is_resolved_and_kept() {
let prepared = prepare_str(r"C:\Windows\System32").expect("resolvable");
assert_eq!(text(&prepared), r"C:\Windows\System32");
}
#[test]
fn an_ordinary_path_is_normalised_by_win32() {
let prepared = prepare_str(r"C:\Windows\..\Windows\System32").expect("resolvable");
assert_eq!(text(&prepared), r"C:\Windows\System32");
}
#[test]
fn forward_slashes_are_normalised() {
let prepared = prepare_str("C:/Windows/System32").expect("resolvable");
assert_eq!(text(&prepared), r"C:\Windows\System32");
}
#[test]
fn a_relative_path_is_snapshotted_against_the_current_directory() {
let current = env::current_dir().expect("a current directory");
let prepared = prepare_str("subdir").expect("resolvable");
let expected = current.join("subdir");
assert_eq!(text(&prepared), expected.to_string_lossy());
}
#[test]
fn an_ordinary_path_longer_than_max_path_is_rejected() {
let long = format!(r"C:\{}", "a".repeat(400));
let error = prepare_str(&long).expect_err("beyond the ordinary limit");
assert_eq!(error.failure(), PathFailure::PathTooLong);
}
#[test]
fn a_relative_path_that_resolves_past_max_path_is_rejected() {
let current = env::current_dir().expect("a current directory");
let room = 259usize.saturating_sub(current.to_string_lossy().len());
let error = prepare_str(&"a".repeat(room + 8)).expect_err("resolves past the ordinary limit");
assert_eq!(error.failure(), PathFailure::PathTooLong);
}
#[test]
fn a_reserved_device_name_resolves_into_the_device_namespace() {
let prepared = prepare_str("NUL").expect("resolvable");
assert_eq!(text(&prepared), r"\\.\NUL");
}
#[test]
fn a_device_namespace_path_is_resolved_rather_than_kept_verbatim() {
let prepared = prepare_str(r"\\.\C:\Windows\..\Windows").expect("resolvable");
assert_eq!(text(&prepared), r"\\.\C:\Windows");
}
#[test]
fn a_prepared_path_exposes_its_units_both_ways() {
let prepared = prepare(&Wtf16String::from(r"\\?\C:\Windows")).expect("fully qualified");
assert_eq!(prepared.as_wtf16().to_string_lossy(), r"\\?\C:\Windows");
assert_eq!(
prepared.into_wtf16().to_string_lossy(),
r"\\?\C:\Windows",
"borrowing and taking must agree"
);
}
#[test]
fn a_prepared_path_is_comparable_and_cloneable() {
let first = prepare(&Wtf16String::from(r"\\?\C:\Windows")).expect("fully qualified");
let second = prepare(&Wtf16String::from(r"\\?\C:\Windows")).expect("fully qualified");
let other = prepare(&Wtf16String::from(r"\\?\C:\Users")).expect("fully qualified");
assert_eq!(first, second);
assert_eq!(first, first.clone());
assert_ne!(first, other);
}
#[test]
fn a_prepared_path_moves_across_threads() {
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
assert_send::<PreparedPath>();
assert_sync::<PreparedPath>();
let prepared = prepare(&Wtf16String::from(r"\\?\C:\Windows")).expect("fully qualified");
let observed = std::thread::spawn(move || prepared.as_wtf16().to_string_lossy())
.join()
.expect("the worker did not panic");
assert_eq!(observed, r"\\?\C:\Windows");
}
#[test]
fn every_failure_describes_itself_without_a_raw_code() {
for failure in [
PathFailure::EmptyPath,
PathFailure::InteriorNul,
PathFailure::PathTooLong,
PathFailure::NotFullyQualified,
PathFailure::PathResolution,
] {
assert!(
!failure.description().is_empty(),
"{failure:?} must describe itself"
);
}
}
#[test]
fn an_error_without_an_os_code_renders_only_its_description() {
let error = prepare(&Wtf16String::new()).expect_err("an empty path names nothing");
assert_eq!(error.to_string(), PathFailure::EmptyPath.description());
assert!(std::error::Error::source(&error).is_none());
}
fn absolute_path_of_length(units: usize) -> String {
let prefix = r"C:\";
let prefix_units = prefix.encode_utf16().count();
assert!(
units >= prefix_units,
"asked for a {units}-unit path, but the {prefix} prefix is already \
{prefix_units} units; the subtraction below would underflow and panic \
without saying why"
);
format!("{prefix}{}", "a".repeat(units - prefix_units))
}
#[test]
fn an_ordinary_path_of_exactly_max_path_content_is_accepted() {
let path = absolute_path_of_length(259);
assert_eq!(path.encode_utf16().count(), 259);
let prepared = prepare_str(&path).expect("259 units is within the ordinary limit");
assert_eq!(text(&prepared), path);
}
#[test]
fn an_ordinary_path_one_unit_past_max_path_content_is_rejected() {
let path = absolute_path_of_length(260);
assert_eq!(path.encode_utf16().count(), 260);
let error = prepare_str(&path).expect_err("260 units leaves no room for the terminator");
assert_eq!(error.failure(), PathFailure::PathTooLong);
}
#[test]
fn the_ordinary_limit_is_one_less_than_max_path() {
assert_eq!(MAX_PATH_CONTENT, MAX_PATH - 1);
assert_eq!(MAX_PATH_CONTENT, 259);
}
#[test]
fn a_path_whose_character_count_hides_its_utf16_length_is_still_refused() {
let supplementary = '\u{1F600}';
assert_eq!(supplementary.len_utf16(), 2, "the premise of this test");
let accepted = format!(r"C:\{}", supplementary.to_string().repeat(128));
assert_eq!(accepted.encode_utf16().count(), 259);
assert_eq!(accepted.chars().count(), 131);
let prepared = prepare_str(&accepted).expect("259 UTF-16 units is within the limit");
assert_eq!(text(&prepared), accepted);
let rejected = format!("{accepted}a");
assert_eq!(rejected.encode_utf16().count(), 260);
assert_eq!(rejected.chars().count(), 132);
let error = prepare_str(&rejected).expect_err("260 UTF-16 units is past the limit");
assert_eq!(error.failure(), PathFailure::PathTooLong);
}
#[test]
fn a_verbatim_drive_relative_path_with_a_separator_is_rejected() {
let error = prepare_str(r"\\?\C:foo\bar").expect_err("drive-relative, not fully qualified");
assert_eq!(error.failure(), PathFailure::NotFullyQualified);
}
#[test]
fn a_verbatim_root_needs_a_letter_before_its_colon_not_merely_a_colon() {
for path in [r"\\?\1:\", r"\\?\1:\dir"] {
let error = prepare_str(path).expect_err("a digit is not a drive letter");
assert_eq!(
error.failure(),
PathFailure::NotFullyQualified,
"for {path}"
);
}
let prepared = prepare_str(r"\\?\Ca\dir").expect("a colonless root is not a drive at all");
assert_eq!(text(&prepared), r"\\?\Ca\dir");
}
#[test]
fn every_path_failure_describes_itself_distinctly() {
let cases = [
("EmptyPath", PathFailure::EmptyPath),
("InteriorNul", PathFailure::InteriorNul),
("PathTooLong", PathFailure::PathTooLong),
("NotFullyQualified", PathFailure::NotFullyQualified),
("PathResolution", PathFailure::PathResolution),
];
for (name, failure) in cases {
assert!(
!failure.description().is_empty(),
"{name} has no description, so a reader learns nothing from it"
);
}
for (index, (name, failure)) in cases.iter().enumerate() {
for (other_name, other) in &cases[index + 1..] {
assert_ne!(
failure.description(),
other.description(),
"{name} and {other_name} describe themselves identically, so the \
description cannot tell them apart"
);
}
}
}
#[test]
fn a_failure_decided_here_carries_no_os_error_and_renders_as_its_description() {
let error = prepare_str("").expect_err("an empty path names nothing");
assert_eq!(error.failure(), PathFailure::EmptyPath);
assert_eq!(error.raw_os_error(), None);
assert!(std::error::Error::source(&error).is_none());
assert_eq!(error.to_string(), PathFailure::EmptyPath.description());
}