1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Edge case tests for soft_canonicalize
//!
//! Tests edge cases, boundary detection, and performance characteristics
//! to ensure robust behavior.
use crate::soft_canonicalize;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_boundary_detection() -> std::io::Result<()> {
let temp_dir = tempdir()?;
let base = temp_dir.path();
// Create nested directory structure
let level1 = base.join("level1");
let level2 = level1.join("level2");
let level3 = level2.join("level3");
std::fs::create_dir_all(&level2)?;
// level3 doesn't exist - this is our boundary
// Test exact boundary detection
let path_at_boundary = level3.join("file.txt");
let result = soft_canonicalize(path_at_boundary)?;
// Result should have canonical prefix up to level2, lexical suffix from level3
let canonical_prefix = soft_canonicalize(&level2)?;
assert!(result.starts_with(canonical_prefix));
Ok(())
}
#[test]
fn test_performance_characteristics() -> std::io::Result<()> {
// Test that validates reasonable performance characteristics
// This ensures the function doesn't have pathological behavior with deep paths
// Focus on correctness rather than absolute timing to avoid CI flakiness
let temp_dir = tempdir()?;
// Test with progressively deeper paths to ensure no exponential behavior
let depths = [10, 20, 50];
let mut all_succeeded = true;
for depth in depths {
// Create path with specified depth
let deep_components = vec!["component"; depth];
let deep_path: std::path::PathBuf = deep_components.iter().collect();
let test_path = temp_dir.path().join(&deep_path).join("file.txt");
// Test that canonicalization completes successfully
let result = soft_canonicalize(&test_path);
match result {
Ok(canonical_path) => {
// Verify the result is correct
let expected = fs::canonicalize(temp_dir.path())?
.join(deep_path)
.join("file.txt");
#[cfg(not(feature = "dunce"))]
{
assert_eq!(canonical_path, expected, "Without dunce: exact match");
}
#[cfg(feature = "dunce")]
{
#[cfg(windows)]
{
let result_str = canonical_path.to_string_lossy();
let expected_str = expected.to_string_lossy();
// Deep paths (>260 chars) are NOT safe to simplify, so dunce preserves UNC
// Both should be in UNC format for deep paths
if result_str.len() > 260 || expected_str.len() > 260 {
assert!(
result_str.starts_with(r"\\?\"),
"dunce preserves UNC for long paths"
);
assert!(
expected_str.starts_with(r"\\?\"),
"expected has UNC from std"
);
} else {
// Short paths can be simplified
assert!(
!result_str.starts_with(r"\\?\"),
"dunce simplifies short paths"
);
assert!(
expected_str.starts_with(r"\\?\"),
"expected has UNC from std"
);
}
}
#[cfg(not(windows))]
{
assert_eq!(canonical_path, expected);
}
}
}
Err(e) => {
all_succeeded = false;
eprintln!("Canonicalization failed for depth {depth}: {e}");
}
}
}
// The main assertion: all deep paths should canonicalize successfully
// This validates that we don't have stack overflow or other pathological behavior
assert!(all_succeeded, "Some deep path canonicalizations failed");
// Optional: Basic timing sanity check (generous limit to avoid CI flakes)
let very_deep_path = temp_dir
.path()
.join(
vec!["component"; 100]
.iter()
.collect::<std::path::PathBuf>(),
)
.join("file.txt");
let start = std::time::Instant::now();
let _result = soft_canonicalize(very_deep_path)?;
let elapsed = start.elapsed();
// Very generous limit - just ensure we don't hang or take extremely long
assert!(
elapsed.as_secs() < 1,
"Even very deep paths should complete within 1 second, took: {elapsed:?}"
);
Ok(())
}