Fix Rust wdirname and wbasename and port the C++ tests

These functions were rather buggy; add tests and fix the test failures.
This commit is contained in:
ridiculousfish 2023-07-01 12:45:11 -07:00
parent b4570623e9
commit 6b1c2e169c

View File

@ -327,12 +327,12 @@ pub fn wdirname(mut path: WString) -> WString {
// This follows OpenGroup dirname recipe. // This follows OpenGroup dirname recipe.
// 1: Double-slash stays. // 1: Double-slash stays.
if path == "//"L { if path == "//" {
return path; return path;
} }
// 2: All slashes => return slash. // 2: All slashes => return slash.
if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() { if !path.is_empty() && path.chars().all(|c| c == '/') {
return "/"L.to_owned(); return "/"L.to_owned();
} }
@ -342,7 +342,7 @@ pub fn wdirname(mut path: WString) -> WString {
} }
// 4: No slashes left => return period. // 4: No slashes left => return period.
let Some(last_slash) = path.chars().rev().position(|c| c == '/') else { let Some(last_slash) = path.chars().rposition(|c| c == '/') else {
return "."L.to_owned() return "."L.to_owned()
}; };
@ -373,19 +373,18 @@ pub fn wbasename(mut path: WString) -> WString {
// 2: Skip as permitted. // 2: Skip as permitted.
// 3: All slashes => return slash. // 3: All slashes => return slash.
if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() { if !path.is_empty() && path.chars().all(|c| c == '/') {
return "/"L.to_owned(); return "/"L.to_owned();
} }
// 4: Remove trailing slashes. // 4: Remove trailing slashes.
// while (!path.is_empty() && path.back() == '/') path.pop_back();
while path.as_char_slice().last() == Some(&'/') { while path.as_char_slice().last() == Some(&'/') {
path.pop(); path.pop();
} }
// 5: Remove up to and including last slash. // 5: Remove up to and including last slash.
if let Some(last_slash) = path.chars().rev().position(|c| c == '/') { if let Some(last_slash) = path.chars().rposition(|c| c == '/') {
path.truncate(last_slash + 1); path.replace_range(..last_slash + 1, ""L);
}; };
path path
} }
@ -871,3 +870,53 @@ fn test_wstr_offset_in() {
assert_eq!(wstr_offset_in(&base[6..], &base[6..]), 0); assert_eq!(wstr_offset_in(&base[6..], &base[6..]), 0);
assert_eq!(wstr_offset_in(&base[base.len()..], base), base.len()); assert_eq!(wstr_offset_in(&base[base.len()..], base), base.len());
} }
#[test]
#[widestrs]
fn test_wdirname_wbasename() {
// path, dir, base
struct Test(&'static wstr, &'static wstr, &'static wstr);
const testcases: &[Test] = &[
Test(""L, "."L, "."L),
Test("foo//"L, "."L, "foo"L),
Test("foo//////"L, "."L, "foo"L),
Test("/////foo"L, "/"L, "foo"L),
Test("//foo/////bar"L, "//foo"L, "bar"L),
Test("foo/////bar"L, "foo"L, "bar"L),
// Examples given in XPG4.2.
Test("/usr/lib"L, "/usr"L, "lib"L),
Test("usr"L, "."L, "usr"L),
Test("/"L, "/"L, "/"L),
Test("."L, "."L, "."L),
Test(".."L, "."L, ".."L),
];
for tc in testcases {
let Test(path, tc_dir, tc_base) = *tc;
let dir = wdirname(path.to_owned());
assert_eq!(
dir, tc_dir,
"\npath: {:?}, dir: {:?}, tc.dir: {:?}",
path, dir, tc_dir
);
let base = wbasename(path.to_owned());
assert_eq!(
base, tc_base,
"\npath: {:?}, base: {:?}, tc.base: {:?}",
path, base, tc_base
);
}
// Ensure strings which greatly exceed PATH_MAX still work (#7837).
const PATH_MAX: usize = libc::PATH_MAX as usize;
let mut longpath = WString::new();
longpath.reserve(PATH_MAX * 2 + 10);
while longpath.char_count() <= PATH_MAX * 2 {
longpath.push_str("/overlong");
}
let last_slash = longpath.chars().rposition(|c| c == '/').unwrap();
let longpath_dir = &longpath[..last_slash];
assert_eq!(wdirname(longpath.clone()), longpath_dir);
assert_eq!(wbasename(longpath), "overlong"L);
}