Skip to content

Commit 20a4cbf

Browse files
committed
feat(dir): Allow in-source dir fixtures
1 parent 5de5c1f commit 20a4cbf

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

crates/snapbox/src/dir/fixture.rs

+49
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,52 @@ impl DirFixture for String {
5959
std::path::Path::new(self).write_to_path(root)
6060
}
6161
}
62+
63+
impl<P, S> DirFixture for &[(P, S)]
64+
where
65+
P: AsRef<std::path::Path>,
66+
S: AsRef<[u8]>,
67+
{
68+
fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> {
69+
let root = super::ops::canonicalize(root)
70+
.map_err(|e| format!("Failed to canonicalize {}: {}", root.display(), e))?;
71+
72+
for (path, content) in self.iter() {
73+
let rel_path = path.as_ref();
74+
let path = root.join(rel_path);
75+
let path = super::ops::normalize_path(&path);
76+
if !path.starts_with(&root) {
77+
return Err(crate::assert::Error::new(format!(
78+
"Fixture {} is for outside of the target root",
79+
rel_path.display(),
80+
)));
81+
}
82+
83+
let content = content.as_ref();
84+
85+
if let Some(dir) = path.parent() {
86+
std::fs::create_dir_all(dir).map_err(|e| {
87+
format!(
88+
"Failed to create fixture directory {}: {}",
89+
dir.display(),
90+
e
91+
)
92+
})?;
93+
}
94+
std::fs::write(&path, &content)
95+
.map_err(|e| format!("Failed to write fixture {}: {}", path.display(), e))?;
96+
}
97+
Ok(())
98+
}
99+
}
100+
101+
impl<const N: usize, P, S> DirFixture for [(P, S); N]
102+
where
103+
P: AsRef<std::path::Path>,
104+
S: AsRef<[u8]>,
105+
{
106+
fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> {
107+
let s: &[(P, S)] = self;
108+
s.write_to_path(root)
109+
}
110+
}

crates/snapbox/src/dir/ops.rs

+44
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,50 @@ pub fn strip_trailing_slash(path: &std::path::Path) -> &std::path::Path {
172172
path.components().as_path()
173173
}
174174

175+
/// Normalize a path, removing things like `.` and `..`.
176+
///
177+
/// CAUTION: This does not resolve symlinks (unlike
178+
/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
179+
/// behavior at times. This should be used carefully. Unfortunately,
180+
/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
181+
/// fail, or on Windows returns annoying device paths. This is a problem Cargo
182+
/// needs to improve on.
183+
pub(crate) fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
184+
use std::path::Component;
185+
186+
let mut components = path.components().peekable();
187+
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
188+
components.next();
189+
std::path::PathBuf::from(c.as_os_str())
190+
} else {
191+
std::path::PathBuf::new()
192+
};
193+
194+
for component in components {
195+
match component {
196+
Component::Prefix(..) => unreachable!(),
197+
Component::RootDir => {
198+
ret.push(Component::RootDir);
199+
}
200+
Component::CurDir => {}
201+
Component::ParentDir => {
202+
if ret.ends_with(Component::ParentDir) {
203+
ret.push(Component::ParentDir);
204+
} else {
205+
let popped = ret.pop();
206+
if !popped && !ret.has_root() {
207+
ret.push(Component::ParentDir);
208+
}
209+
}
210+
}
211+
Component::Normal(c) => {
212+
ret.push(c);
213+
}
214+
}
215+
}
216+
ret
217+
}
218+
175219
pub(crate) fn display_relpath(path: impl AsRef<std::path::Path>) -> String {
176220
let path = path.as_ref();
177221
let relpath = if let Ok(cwd) = std::env::current_dir() {

0 commit comments

Comments
 (0)