From 78630198d0dd41235c914e7e948d3d60159e87e3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 16 Dec 2024 15:31:13 -0600 Subject: [PATCH] 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..12a4da59 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 96d6e535..0fc76826 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)?; } }