#[cfg(windows)]
use crate::soft_canonicalize;
#[cfg(all(windows, feature = "anchored"))]
use crate::anchored_canonicalize;
#[cfg(windows)]
#[test]
fn test_superscript_reserved_names_documentation() {
use tempfile::TempDir;
let exotic_names = [
("COM¹", "COM with superscript 1"),
("COM²", "COM with superscript 2"),
("COM³", "COM with superscript 3"),
("LPT¹", "LPT with superscript 1"),
("LPT²", "LPT with superscript 2"),
("LPT³", "LPT with superscript 3"),
];
let tmp = TempDir::new().expect("create tempdir");
let _base = soft_canonicalize(tmp.path()).expect("canonicalize tempdir");
for (name, desc) in exotic_names {
let path = tmp.path().join(name).join("file.txt");
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Currently allowing {} ({}): path = {:?}",
desc,
name,
path
);
if let Ok(resolved) = result {
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.contains(name),
"Path should contain exotic name {}: {}",
name,
resolved_str
);
}
}
let exotic_with_ext = ["COM¹.txt", "LPT².exe", "COM³.tar.gz"];
for name in exotic_with_ext {
let path = tmp.path().join(name);
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Currently allowing exotic name with extension: {}",
name
);
}
}
#[cfg(windows)]
#[test]
fn test_long_paths_with_multibyte_characters() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let emoji_component = "🧐".repeat(50); let emoji_path = tmp.path().join(emoji_component).join("file.txt");
let result = soft_canonicalize(&emoji_path);
assert!(
result.is_ok(),
"Extended-length format should handle emoji paths: {:?}",
emoji_path
);
if let Ok(resolved) = result {
#[cfg(not(feature = "dunce"))]
{
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.starts_with(r"\\?\"),
"Should use extended-length format for absolute result: {}",
resolved_str
);
}
#[cfg(feature = "dunce")]
{
assert!(resolved.is_absolute(), "Result should be absolute");
}
}
let cjk_component = "日本語".repeat(50); let cjk_path = tmp.path().join(cjk_component).join("测试.txt");
let result = soft_canonicalize(&cjk_path);
assert!(
result.is_ok(),
"Extended-length format should handle CJK paths: {:?}",
cjk_path
);
let mixed = format!(
"{}_{}_{}_{}",
"🎃".repeat(20),
"®".repeat(30),
"日".repeat(40),
"test"
);
let mixed_path = tmp.path().join(mixed).join("file.exe");
let result = soft_canonicalize(&mixed_path);
assert!(
result.is_ok(),
"Extended-length format should handle mixed multibyte chars: {:?}",
mixed_path
);
}
#[cfg(windows)]
#[test]
fn test_long_component_names() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let component_250 = "a".repeat(250);
let path_250 = tmp.path().join(component_250).join("file.txt");
let result = soft_canonicalize(path_250);
assert!(result.is_ok(), "Should handle 250-char component");
let component_255 = "b".repeat(255);
let path_255 = tmp.path().join(component_255).join("file.txt");
let result = soft_canonicalize(path_255);
assert!(result.is_ok(), "Should handle 255-char component");
let component_300 = "c".repeat(300);
let path_300 = tmp.path().join(component_300).join("file.txt");
let result = soft_canonicalize(path_300);
match result {
Ok(_) => {
}
Err(e) => {
assert!(
e.kind() == std::io::ErrorKind::InvalidInput
|| e.kind() == std::io::ErrorKind::NotFound,
"Expected InvalidInput or NotFound, got: {:?}",
e.kind()
);
}
}
}
#[cfg(windows)]
#[test]
fn test_reserved_names_with_extensions() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let reserved_with_ext = [
"CON.txt",
"PRN.exe",
"AUX.dll",
"NUL.tar.gz",
"COM1.log",
"COM9.dat",
"LPT1.cfg",
"LPT9.ini",
];
for name in reserved_with_ext {
let path = tmp.path().join(name);
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Should canonicalize reserved name with extension: {}",
name
);
}
}
#[cfg(windows)]
#[test]
fn test_reserved_names_with_trailing_chars() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let reserved_variants = [
"CON ", "PRN.", "AUX ", "NUL.....", "COM1 .", "LPT1. ", "COM9 . ", ];
for name in reserved_variants {
let path = tmp.path().join(name);
let result = soft_canonicalize(&path);
assert!(
result.is_ok() || result.is_err(),
"Should not panic on reserved name variant: '{}'",
name
);
}
}
#[cfg(windows)]
#[test]
fn test_leading_space_not_reserved() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let non_reserved = [
" CON", " PRN", " AUX", " NUL", " COM1", " LPT1", " CON", ];
for name in non_reserved {
let path = tmp.path().join(name).join("file.txt");
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Leading space should make non-reserved: '{}'",
name
);
if let Ok(resolved) = result {
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.contains(name),
"Leading space should be preserved: expected '{}' in '{}'",
name,
resolved_str
);
}
}
}
#[cfg(windows)]
#[test]
fn test_dot_prefix_not_reserved() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let non_reserved = [".CON", ".PRN", ".AUX", ".NUL", ".COM1", ".LPT1"];
for name in non_reserved {
let path = tmp.path().join(name).join("file.txt");
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Dot prefix should make non-reserved: '{}'",
name
);
if let Ok(resolved) = result {
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.contains(name),
"Dot prefix should be preserved: expected '{}' in '{}'",
name,
resolved_str
);
}
}
}
#[cfg(windows)]
#[test]
fn test_invalid_device_numbers_not_reserved() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let non_reserved = [
"COM0", "COM10", "COM77", "LPT0", "LPT10", "LPT99",
];
for name in non_reserved {
let path = tmp.path().join(name).join("file.txt");
let result = soft_canonicalize(&path);
assert!(
result.is_ok(),
"Invalid device number should not be reserved: '{}'",
name
);
if let Ok(resolved) = result {
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.contains(name),
"Non-reserved name should be preserved: expected '{}' in '{}'",
name,
resolved_str
);
}
}
}
#[cfg(windows)]
#[test]
fn test_filename_component_validation() {
let invalid_components = [
"file<name.txt", "file>name.txt", "file:name.txt", "file\"name.txt", "file|name.txt", "file?name.txt", "file*name.txt", "file\0name.txt", "file\x1Fname.txt", ];
for component in invalid_components {
let result = soft_canonicalize(component);
match result {
Ok(p) => {
let _ = p;
}
Err(e) => {
assert!(
e.kind() == std::io::ErrorKind::InvalidInput
|| e.kind() == std::io::ErrorKind::NotFound,
"Expected InvalidInput or NotFound for '{}', got: {:?}",
component,
e.kind()
);
}
}
}
}
#[cfg(all(windows, feature = "anchored"))]
#[test]
fn test_anchored_exotic_cases() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let base = soft_canonicalize(tmp.path()).expect("canonicalize tempdir");
let emoji_path = "🧐/file.txt";
let result = anchored_canonicalize(&base, emoji_path);
assert!(
result.is_ok(),
"Anchored should handle emoji: {}",
emoji_path
);
if let Ok(resolved) = result {
assert_eq!(
resolved,
base.join("🧐").join("file.txt"),
"Anchored emoji path should resolve correctly"
);
}
let reserved_path = "subdir/CON.txt";
let result = anchored_canonicalize(&base, reserved_path);
assert!(
result.is_ok(),
"Anchored should handle reserved names: {}",
reserved_path
);
let long_component = "a".repeat(200);
let long_path = format!("{}/file.txt", long_component);
let result = anchored_canonicalize(&base, long_path);
assert!(
result.is_ok(),
"Anchored should handle long components: {} chars",
long_component.len()
);
}
#[cfg(windows)]
#[test]
fn test_control_characters_context_sensitive() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let control_in_name = "file\x01name.txt".to_string();
let result = soft_canonicalize(tmp.path().join(control_in_name));
match result {
Ok(_) => {
}
Err(e) => {
assert!(
e.kind() == std::io::ErrorKind::InvalidInput
|| e.kind() == std::io::ErrorKind::NotFound,
"Control char in filename: expected InvalidInput or NotFound, got: {:?}",
e.kind()
);
}
}
}
#[cfg(windows)]
#[test]
fn test_unicode_normalization() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let composed = "café"; let path_composed = tmp.path().join(composed).join("file.txt");
let result_composed = soft_canonicalize(path_composed);
assert!(
result_composed.is_ok(),
"Should handle composed Unicode: {}",
composed
);
let decomposed = "café"; let path_decomposed = tmp.path().join(decomposed).join("file.txt");
let result_decomposed = soft_canonicalize(path_decomposed);
assert!(
result_decomposed.is_ok(),
"Should handle decomposed Unicode: {}",
decomposed
);
}
#[cfg(windows)]
#[test]
fn test_maximum_path_depth() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let mut current = tmp.path().to_path_buf();
for i in 0..100 {
current = current.join(format!("dir{}", i));
}
current = current.join("file.txt");
let result = soft_canonicalize(¤t);
assert!(
result.is_ok(),
"Extended-length format should handle deeply nested paths"
);
if let Ok(resolved) = result {
let resolved_str = resolved.to_string_lossy();
assert!(
resolved_str.starts_with(r"\\?\"),
"Deep path should use extended-length format: {}",
resolved_str
);
}
}
#[cfg(windows)]
#[test]
fn test_rust_string_edge_cases() {
let result = soft_canonicalize("");
assert!(result.is_err(), "Empty path should be rejected");
let result = soft_canonicalize(" ");
let _ = result;
let null_path = "test\0file.txt";
let result = soft_canonicalize(null_path);
assert!(result.is_err(), "Null bytes should be rejected");
}
#[cfg(windows)]
#[test]
fn test_dunce_regression_patterns() {
use tempfile::TempDir;
let tmp = TempDir::new().expect("create tempdir");
let patterns = [
("con . .txt", "reserved with spaces and dots"),
("con.....txt", "reserved with many dots"),
("PRN.....", "reserved with trailing dots"),
("COM4 .txt", "reserved with space before extension"),
("a........a", "valid filename with many dots"),
(" b", "valid filename with leading spaces"),
];
for (pattern, desc) in patterns {
let path = tmp.path().join(pattern);
let result = soft_canonicalize(&path);
assert!(
result.is_ok() || result.is_err(),
"Should not panic on {}: {}",
desc,
pattern
);
}
}