Add tests for normalize_path and fix some bugs

This commit is contained in:
ridiculousfish 2023-03-12 19:28:17 -07:00
parent 33fd679f68
commit dea18b34aa
2 changed files with 73 additions and 15 deletions

View file

@ -6,6 +6,7 @@ mod wrealpath;
use std::io::Write;
use crate::wchar::{wstr, WString};
pub(crate) use format::printf::sprintf;
pub(crate) use gettext::{wgettext, wgettext_fmt};
pub use normalize_path::*;
@ -27,3 +28,30 @@ pub fn perror(s: &str) {
let _ = stderr.write_all(slice);
let _ = stderr.write_all(b"\n");
}
/// Joins strings with a separator.
pub fn join_strings(strs: &[&wstr], sep: char) -> WString {
if strs.is_empty() {
return WString::new();
}
let capacity = strs.iter().fold(0, |acc, s| acc + s.len()) + strs.len() - 1;
let mut result = WString::with_capacity(capacity);
for (i, s) in strs.iter().enumerate() {
if i > 0 {
result.push(sep);
}
result.push_utfstr(s);
}
result
}
#[test]
fn test_join_strings() {
use crate::wchar::L;
assert_eq!(join_strings(&[], '/'), "");
assert_eq!(join_strings(&[L!("foo")], '/'), "foo");
assert_eq!(
join_strings(&[L!("foo"), L!("bar"), L!("baz")], '/'),
"foo/bar/baz"
);
}

View file

@ -1,16 +1,19 @@
use std::iter::repeat;
use crate::wchar::{wstr, WString, L};
use crate::wutil::join_strings;
/// Given an input path, "normalize" it:
/// 1. Collapse multiple /s into a single /, except maybe at the beginning.
/// 2. .. goes up a level.
/// 3. Remove /./ in the middle.
pub fn normalize_path(path: &wstr, allow_leading_double_slashes: bool) -> WString {
// Count the leading slashes.
let sep = '/';
let mut leading_slashes: usize = 0;
for (i, &c) in path.as_char_slice().iter().enumerate() {
for c in path.chars() {
if c != sep {
leading_slashes = i;
break;
}
leading_slashes += 1;
}
let comps = path
@ -20,11 +23,11 @@ pub fn normalize_path(path: &wstr, allow_leading_double_slashes: bool) -> WStrin
.collect::<Vec<_>>();
let mut new_comps = Vec::new();
for comp in comps {
if comp.is_empty() || comp == L!(".") {
if comp.is_empty() || comp == "." {
continue;
} else if comp != L!("..") {
} else if comp != ".." {
new_comps.push(comp);
} else if !new_comps.is_empty() && new_comps.last().map_or(L!(""), |&s| s) != L!("..") {
} else if !new_comps.is_empty() && new_comps.last().unwrap() != ".." {
// '..' with a real path component, drop that path component.
new_comps.pop();
} else if leading_slashes == 0 {
@ -32,12 +35,7 @@ pub fn normalize_path(path: &wstr, allow_leading_double_slashes: bool) -> WStrin
new_comps.push(L!(".."));
}
}
let mut result = new_comps.into_iter().fold(Vec::new(), |mut acc, x| {
acc.extend_from_slice(x.as_char_slice());
acc.push('/');
acc
});
result.pop();
let mut result = join_strings(&new_comps, sep);
// If we don't allow leading double slashes, collapse them to 1 if there are any.
let mut numslashes = if leading_slashes > 0 { 1 } else { 0 };
// If we do, prepend one or two leading slashes.
@ -45,10 +43,42 @@ pub fn normalize_path(path: &wstr, allow_leading_double_slashes: bool) -> WStrin
if allow_leading_double_slashes && leading_slashes == 2 {
numslashes = 2;
}
result.splice(0..0, repeat(sep).take(numslashes));
for _ in 0..numslashes {
result.insert(0, sep);
}
// Ensure ./ normalizes to . and not empty.
if result.is_empty() {
result.push('.');
}
WString::from_chars(result)
result
}
#[test]
fn test_normalize_path() {
fn norm_path(path: &wstr) -> WString {
normalize_path(path, true)
}
assert_eq!(norm_path(L!("")), ".");
assert_eq!(norm_path(L!("..")), "..");
assert_eq!(norm_path(L!("./")), ".");
assert_eq!(norm_path(L!("./.")), ".");
assert_eq!(norm_path(L!("/")), "/");
assert_eq!(norm_path(L!("//")), "//");
assert_eq!(norm_path(L!("///")), "/");
assert_eq!(norm_path(L!("////")), "/");
assert_eq!(norm_path(L!("/.///")), "/");
assert_eq!(norm_path(L!(".//")), ".");
assert_eq!(norm_path(L!("/.//../")), "/");
assert_eq!(norm_path(L!("////abc")), "/abc");
assert_eq!(norm_path(L!("/abc")), "/abc");
assert_eq!(norm_path(L!("/abc/")), "/abc");
assert_eq!(norm_path(L!("/abc/..def/")), "/abc/..def");
assert_eq!(norm_path(L!("//abc/../def/")), "//def");
assert_eq!(norm_path(L!("abc/../abc/../abc/../abc")), "abc");
assert_eq!(norm_path(L!("../../")), "../..");
assert_eq!(norm_path(L!("foo/./bar")), "foo/bar");
assert_eq!(norm_path(L!("foo/../")), ".");
assert_eq!(norm_path(L!("foo/../foo")), "foo");
assert_eq!(norm_path(L!("foo/../foo/")), "foo");
assert_eq!(norm_path(L!("foo/././bar/.././baz")), "foo/baz");
}