Skip to content

Commit 99dc7df

Browse files
committed
feat(dir): Allow in-source dir fixtures
1 parent aab7056 commit 99dc7df

File tree

3 files changed

+99
-6
lines changed

3 files changed

+99
-6
lines changed

crates/snapbox/src/dir/fixture.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// Collection of files
2-
pub trait DirFixture {
2+
pub trait DirFixture: std::fmt::Debug {
33
/// Initialize a test fixture directory `root`
44
fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error>;
55
}
@@ -59,3 +59,56 @@ 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+
P: std::fmt::Debug,
67+
S: AsRef<[u8]>,
68+
S: std::fmt::Debug,
69+
{
70+
fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> {
71+
let root = super::ops::canonicalize(root)
72+
.map_err(|e| format!("Failed to canonicalize {}: {}", root.display(), e))?;
73+
74+
for (path, content) in self.iter() {
75+
let rel_path = path.as_ref();
76+
let path = root.join(rel_path);
77+
let path = super::ops::normalize_path(&path);
78+
if !path.starts_with(&root) {
79+
return Err(crate::assert::Error::new(format!(
80+
"Fixture {} is for outside of the target root",
81+
rel_path.display(),
82+
)));
83+
}
84+
85+
let content = content.as_ref();
86+
87+
if let Some(dir) = path.parent() {
88+
std::fs::create_dir_all(dir).map_err(|e| {
89+
format!(
90+
"Failed to create fixture directory {}: {}",
91+
dir.display(),
92+
e
93+
)
94+
})?;
95+
}
96+
std::fs::write(&path, content)
97+
.map_err(|e| format!("Failed to write fixture {}: {}", path.display(), e))?;
98+
}
99+
Ok(())
100+
}
101+
}
102+
103+
impl<const N: usize, P, S> DirFixture for [(P, S); N]
104+
where
105+
P: AsRef<std::path::Path>,
106+
P: std::fmt::Debug,
107+
S: AsRef<[u8]>,
108+
S: std::fmt::Debug,
109+
{
110+
fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> {
111+
let s: &[(P, S)] = self;
112+
s.write_to_path(root)
113+
}
114+
}

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() {

crates/snapbox/src/dir/root.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ impl DirRoot {
5252
return Err("Sandboxing is disabled".into());
5353
}
5454
DirRootInner::MutablePath(path) | DirRootInner::MutableTemp { path, .. } => {
55-
crate::debug!(
56-
"Initializing {} from {}",
57-
path.display(),
58-
template_root.display()
59-
);
55+
crate::debug!("Initializing {} from {:?}", path.display(), template);
6056
template.write_to_path(path)?;
6157
}
6258
}

0 commit comments

Comments
 (0)