From aab70566daaa29d273a889633e6d432e0fcd73a6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 16 Dec 2024 15:04:46 -0600 Subject: [PATCH 1/2] feat(dir): Accept more path types for template paths --- crates/snapbox/src/dir/fixture.rs | 61 +++++++++++++++++++++++++++++++ crates/snapbox/src/dir/mod.rs | 2 + crates/snapbox/src/dir/root.rs | 10 ++--- 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 crates/snapbox/src/dir/fixture.rs diff --git a/crates/snapbox/src/dir/fixture.rs b/crates/snapbox/src/dir/fixture.rs new file mode 100644 index 00000000..6d5367f7 --- /dev/null +++ b/crates/snapbox/src/dir/fixture.rs @@ -0,0 +1,61 @@ +/// Collection of files +pub trait DirFixture { + /// Initialize a test fixture directory `root` + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error>; +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for std::path::Path { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + super::copy_template(self, root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for &'_ std::path::Path { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for &'_ std::path::PathBuf { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for std::path::PathBuf { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for str { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for &'_ str { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for &'_ String { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} + +#[cfg(feature = "dir")] // for documentation purposes only +impl DirFixture for String { + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + std::path::Path::new(self).write_to_path(root) + } +} diff --git a/crates/snapbox/src/dir/mod.rs b/crates/snapbox/src/dir/mod.rs index 56c84870..18ea24e7 100644 --- a/crates/snapbox/src/dir/mod.rs +++ b/crates/snapbox/src/dir/mod.rs @@ -1,6 +1,7 @@ //! Initialize working directories and assert on how they've changed mod diff; +mod fixture; mod ops; mod root; #[cfg(test)] @@ -8,6 +9,7 @@ mod tests; pub use diff::FileType; pub use diff::PathDiff; +pub use fixture::DirFixture; #[cfg(feature = "dir")] pub use ops::copy_template; pub use ops::resolve_dir; diff --git a/crates/snapbox/src/dir/root.rs b/crates/snapbox/src/dir/root.rs index 1981a700..35dd1653 100644 --- a/crates/snapbox/src/dir/root.rs +++ b/crates/snapbox/src/dir/root.rs @@ -43,10 +43,10 @@ impl DirRoot { } #[cfg(feature = "dir")] - pub fn with_template( - self, - template_root: &std::path::Path, - ) -> Result { + pub fn with_template(self, template: &F) -> Result + where + F: crate::dir::DirFixture + ?Sized, + { match &self.0 { DirRootInner::None | DirRootInner::Immutable(_) => { return Err("Sandboxing is disabled".into()); @@ -57,7 +57,7 @@ impl DirRoot { path.display(), template_root.display() ); - super::copy_template(template_root, path)?; + template.write_to_path(path)?; } } From 99dc7df113d0b31f49f1fce947c4553536409ba1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 16 Dec 2024 15:31:13 -0600 Subject: [PATCH 2/2] feat(dir): Allow in-source dir fixtures --- crates/snapbox/src/dir/fixture.rs | 55 ++++++++++++++++++++++++++++++- crates/snapbox/src/dir/ops.rs | 44 +++++++++++++++++++++++++ crates/snapbox/src/dir/root.rs | 6 +--- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/crates/snapbox/src/dir/fixture.rs b/crates/snapbox/src/dir/fixture.rs index 6d5367f7..9b6b135e 100644 --- a/crates/snapbox/src/dir/fixture.rs +++ b/crates/snapbox/src/dir/fixture.rs @@ -1,5 +1,5 @@ /// Collection of files -pub trait DirFixture { +pub trait DirFixture: std::fmt::Debug { /// Initialize a test fixture directory `root` fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error>; } @@ -59,3 +59,56 @@ impl DirFixture for String { std::path::Path::new(self).write_to_path(root) } } + +impl DirFixture for &[(P, S)] +where + P: AsRef, + P: std::fmt::Debug, + S: AsRef<[u8]>, + S: std::fmt::Debug, +{ + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + let root = super::ops::canonicalize(root) + .map_err(|e| format!("Failed to canonicalize {}: {}", root.display(), e))?; + + for (path, content) in self.iter() { + let rel_path = path.as_ref(); + let path = root.join(rel_path); + let path = super::ops::normalize_path(&path); + if !path.starts_with(&root) { + return Err(crate::assert::Error::new(format!( + "Fixture {} is for outside of the target root", + rel_path.display(), + ))); + } + + let content = content.as_ref(); + + if let Some(dir) = path.parent() { + std::fs::create_dir_all(dir).map_err(|e| { + format!( + "Failed to create fixture directory {}: {}", + dir.display(), + e + ) + })?; + } + std::fs::write(&path, content) + .map_err(|e| format!("Failed to write fixture {}: {}", path.display(), e))?; + } + Ok(()) + } +} + +impl DirFixture for [(P, S); N] +where + P: AsRef, + P: std::fmt::Debug, + S: AsRef<[u8]>, + S: std::fmt::Debug, +{ + fn write_to_path(&self, root: &std::path::Path) -> Result<(), crate::assert::Error> { + let s: &[(P, S)] = self; + s.write_to_path(root) + } +} diff --git a/crates/snapbox/src/dir/ops.rs b/crates/snapbox/src/dir/ops.rs index 036a4524..d37081f6 100644 --- a/crates/snapbox/src/dir/ops.rs +++ b/crates/snapbox/src/dir/ops.rs @@ -172,6 +172,50 @@ pub fn strip_trailing_slash(path: &std::path::Path) -> &std::path::Path { path.components().as_path() } +/// Normalize a path, removing things like `.` and `..`. +/// +/// CAUTION: This does not resolve symlinks (unlike +/// [`std::fs::canonicalize`]). This may cause incorrect or surprising +/// behavior at times. This should be used carefully. Unfortunately, +/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often +/// fail, or on Windows returns annoying device paths. This is a problem Cargo +/// needs to improve on. +pub(crate) fn normalize_path(path: &std::path::Path) -> std::path::PathBuf { + use std::path::Component; + + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + std::path::PathBuf::from(c.as_os_str()) + } else { + std::path::PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(Component::RootDir); + } + Component::CurDir => {} + Component::ParentDir => { + if ret.ends_with(Component::ParentDir) { + ret.push(Component::ParentDir); + } else { + let popped = ret.pop(); + if !popped && !ret.has_root() { + ret.push(Component::ParentDir); + } + } + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + pub(crate) fn display_relpath(path: impl AsRef) -> String { let path = path.as_ref(); let relpath = if let Ok(cwd) = std::env::current_dir() { diff --git a/crates/snapbox/src/dir/root.rs b/crates/snapbox/src/dir/root.rs index 35dd1653..d381a48c 100644 --- a/crates/snapbox/src/dir/root.rs +++ b/crates/snapbox/src/dir/root.rs @@ -52,11 +52,7 @@ impl DirRoot { return Err("Sandboxing is disabled".into()); } DirRootInner::MutablePath(path) | DirRootInner::MutableTemp { path, .. } => { - crate::debug!( - "Initializing {} from {}", - path.display(), - template_root.display() - ); + crate::debug!("Initializing {} from {:?}", path.display(), template); template.write_to_path(path)?; } }