diff --git a/crates/snapbox/src/assert/mod.rs b/crates/snapbox/src/assert/mod.rs index 8031595e..621f4de2 100644 --- a/crates/snapbox/src/assert/mod.rs +++ b/crates/snapbox/src/assert/mod.rs @@ -220,7 +220,7 @@ impl Assert { } let checks: Vec<_> = - crate::path::PathDiff::subset_eq_iter_inner(expected_root, actual_root).collect(); + crate::dir::PathDiff::subset_eq_iter_inner(expected_root, actual_root).collect(); self.verify(checks); } @@ -248,7 +248,7 @@ impl Assert { Action::Ignore | Action::Verify | Action::Overwrite => {} } - let checks: Vec<_> = crate::path::PathDiff::subset_matches_iter_inner( + let checks: Vec<_> = crate::dir::PathDiff::subset_matches_iter_inner( expected_root, actual_root, &self.substitutions, @@ -261,7 +261,7 @@ impl Assert { #[track_caller] fn verify( &self, - mut checks: Vec>, + mut checks: Vec>, ) { if checks.iter().all(Result::is_ok) { for check in checks { diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 6fa86862..90326549 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -174,12 +174,12 @@ macro_rules! file { $crate::Data::read_from(&path, Some($crate::data::DataFormat:: $type)) }}; [$path:literal] => {{ - let mut path = $crate::current_dir!(); + let mut path = $crate::utils::current_dir!(); path.push($path); $crate::Data::read_from(&path, None) }}; [$path:literal : $type:ident] => {{ - let mut path = $crate::current_dir!(); + let mut path = $crate::utils::current_dir!(); path.push($path); $crate::Data::read_from(&path, Some($crate::data::DataFormat:: $type)) }}; @@ -201,7 +201,7 @@ macro_rules! str { [$data:literal] => { $crate::str![[$data]] }; [[$data:literal]] => {{ let position = $crate::data::Position { - file: $crate::path::current_rs!(), + file: $crate::utils::current_rs!(), line: line!(), column: column!(), }; diff --git a/crates/snapbox/src/data/source.rs b/crates/snapbox/src/data/source.rs index 10e9b9d9..b72ec41f 100644 --- a/crates/snapbox/src/data/source.rs +++ b/crates/snapbox/src/data/source.rs @@ -63,7 +63,7 @@ impl From for DataSource { impl std::fmt::Display for DataSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.inner { - DataSourceInner::Path(value) => crate::path::display_relpath(value).fmt(f), + DataSourceInner::Path(value) => crate::dir::display_relpath(value).fmt(f), DataSourceInner::Inline(value) => value.fmt(f), } } diff --git a/crates/snapbox/src/path.rs b/crates/snapbox/src/dir/diff.rs similarity index 54% rename from crates/snapbox/src/path.rs rename to crates/snapbox/src/dir/diff.rs index 5869c1e1..fd6c652f 100644 --- a/crates/snapbox/src/path.rs +++ b/crates/snapbox/src/dir/diff.rs @@ -1,120 +1,6 @@ -//! Initialize working directories and assert on how they've changed - -#[doc(inline)] -pub use crate::cargo_rustc_current_dir; -#[doc(inline)] -pub use crate::current_dir; -#[doc(inline)] -pub use crate::current_rs; - #[cfg(feature = "path")] use crate::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; -/// Working directory for tests -#[derive(Debug)] -pub struct PathFixture(PathFixtureInner); - -#[derive(Debug)] -enum PathFixtureInner { - None, - Immutable(std::path::PathBuf), - #[cfg(feature = "path")] - MutablePath(std::path::PathBuf), - #[cfg(feature = "path")] - MutableTemp { - temp: tempfile::TempDir, - path: std::path::PathBuf, - }, -} - -impl PathFixture { - pub fn none() -> Self { - Self(PathFixtureInner::None) - } - - pub fn immutable(target: &std::path::Path) -> Self { - Self(PathFixtureInner::Immutable(target.to_owned())) - } - - #[cfg(feature = "path")] - pub fn mutable_temp() -> Result { - let temp = tempfile::tempdir().map_err(|e| e.to_string())?; - // We need to get the `/private` prefix on Mac so variable substitutions work - // correctly - let path = canonicalize(temp.path()) - .map_err(|e| format!("Failed to canonicalize {}: {}", temp.path().display(), e))?; - Ok(Self(PathFixtureInner::MutableTemp { temp, path })) - } - - #[cfg(feature = "path")] - pub fn mutable_at(target: &std::path::Path) -> Result { - let _ = std::fs::remove_dir_all(target); - std::fs::create_dir_all(target) - .map_err(|e| format!("Failed to create {}: {}", target.display(), e))?; - Ok(Self(PathFixtureInner::MutablePath(target.to_owned()))) - } - - #[cfg(feature = "path")] - pub fn with_template( - self, - template_root: &std::path::Path, - ) -> Result { - match &self.0 { - PathFixtureInner::None | PathFixtureInner::Immutable(_) => { - return Err("Sandboxing is disabled".into()); - } - PathFixtureInner::MutablePath(path) | PathFixtureInner::MutableTemp { path, .. } => { - crate::debug!( - "Initializing {} from {}", - path.display(), - template_root.display() - ); - copy_template(template_root, path)?; - } - } - - Ok(self) - } - - pub fn is_mutable(&self) -> bool { - match &self.0 { - PathFixtureInner::None | PathFixtureInner::Immutable(_) => false, - #[cfg(feature = "path")] - PathFixtureInner::MutablePath(_) => true, - #[cfg(feature = "path")] - PathFixtureInner::MutableTemp { .. } => true, - } - } - - pub fn path(&self) -> Option<&std::path::Path> { - match &self.0 { - PathFixtureInner::None => None, - PathFixtureInner::Immutable(path) => Some(path.as_path()), - #[cfg(feature = "path")] - PathFixtureInner::MutablePath(path) => Some(path.as_path()), - #[cfg(feature = "path")] - PathFixtureInner::MutableTemp { path, .. } => Some(path.as_path()), - } - } - - /// Explicitly close to report errors - pub fn close(self) -> Result<(), std::io::Error> { - match self.0 { - PathFixtureInner::None | PathFixtureInner::Immutable(_) => Ok(()), - #[cfg(feature = "path")] - PathFixtureInner::MutablePath(_) => Ok(()), - #[cfg(feature = "path")] - PathFixtureInner::MutableTemp { temp, .. } => temp.close(), - } - } -} - -impl Default for PathFixture { - fn default() -> Self { - Self::none() - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum PathDiff { Failure(crate::assert::Error), @@ -157,7 +43,7 @@ impl PathDiff { expected_root: std::path::PathBuf, actual_root: std::path::PathBuf, ) -> impl Iterator> { - let walker = Walk::new(&expected_root); + let walker = crate::dir::Walk::new(&expected_root); walker.map(move |r| { let expected_path = r.map_err(|e| Self::Failure(e.to_string().into()))?; let rel = expected_path.strip_prefix(&expected_root).unwrap(); @@ -233,7 +119,7 @@ impl PathDiff { substitutions: &crate::Redactions, normalize_paths: bool, ) -> impl Iterator> + '_ { - let walker = Walk::new(&expected_root); + let walker = crate::dir::Walk::new(&expected_root); walker.map(move |r| { let expected_path = r.map_err(|e| Self::Failure(e.to_string().into()))?; let rel = expected_path.strip_prefix(&expected_root).unwrap(); @@ -400,14 +286,14 @@ impl PathDiff { } FileType::Unknown | FileType::Missing => {} } - shallow_copy(expected_path, actual_path) + super::shallow_copy(expected_path, actual_path) } Self::LinkMismatch { expected_path, actual_path, expected_target: _, actual_target: _, - } => shallow_copy(expected_path, actual_path), + } => super::shallow_copy(expected_path, actual_path), Self::ContentMismatch { expected_path: _, actual_path: _, @@ -470,230 +356,3 @@ impl std::fmt::Display for FileType { self.as_str().fmt(f) } } - -/// Recursively walk a path -/// -/// Note: Ignores `.keep` files -#[cfg(feature = "path")] -pub struct Walk { - inner: walkdir::IntoIter, -} - -#[cfg(feature = "path")] -impl Walk { - pub fn new(path: &std::path::Path) -> Self { - Self { - inner: walkdir::WalkDir::new(path).into_iter(), - } - } -} - -#[cfg(feature = "path")] -impl Iterator for Walk { - type Item = Result; - - fn next(&mut self) -> Option { - while let Some(entry) = self.inner.next().map(|e| { - e.map(walkdir::DirEntry::into_path) - .map_err(std::io::Error::from) - }) { - if entry.as_ref().ok().and_then(|e| e.file_name()) - != Some(std::ffi::OsStr::new(".keep")) - { - return Some(entry); - } - } - None - } -} - -/// Copy a template into a [`PathFixture`] -/// -/// Note: Generally you'll use [`PathFixture::with_template`] instead. -/// -/// Note: Ignores `.keep` files -#[cfg(feature = "path")] -pub fn copy_template( - source: impl AsRef, - dest: impl AsRef, -) -> Result<(), crate::assert::Error> { - let source = source.as_ref(); - let dest = dest.as_ref(); - let source = canonicalize(source) - .map_err(|e| format!("Failed to canonicalize {}: {}", source.display(), e))?; - std::fs::create_dir_all(dest) - .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?; - let dest = canonicalize(dest) - .map_err(|e| format!("Failed to canonicalize {}: {}", dest.display(), e))?; - - for current in Walk::new(&source) { - let current = current.map_err(|e| e.to_string())?; - let rel = current.strip_prefix(&source).unwrap(); - let target = dest.join(rel); - - shallow_copy(¤t, &target)?; - } - - Ok(()) -} - -/// Copy a file system entry, without recursing -fn shallow_copy( - source: &std::path::Path, - dest: &std::path::Path, -) -> Result<(), crate::assert::Error> { - let meta = source - .symlink_metadata() - .map_err(|e| format!("Failed to read metadata from {}: {}", source.display(), e))?; - if meta.is_dir() { - std::fs::create_dir_all(dest) - .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?; - } else if meta.is_file() { - std::fs::copy(source, dest).map_err(|e| { - format!( - "Failed to copy {} to {}: {}", - source.display(), - dest.display(), - e - ) - })?; - // Avoid a mtime check race where: - // - Copy files - // - Test checks mtime - // - Test writes - // - Test checks mtime - // - // If all of this happens too close to each other, then the second mtime check will think - // nothing was written by the test. - // - // Instead of just setting 1s in the past, we'll just respect the existing mtime. - copy_stats(&meta, dest).map_err(|e| { - format!( - "Failed to copy {} metadata to {}: {}", - source.display(), - dest.display(), - e - ) - })?; - } else if let Ok(target) = std::fs::read_link(source) { - symlink_to_file(dest, &target) - .map_err(|e| format!("Failed to create symlink {}: {}", dest.display(), e))?; - } - - Ok(()) -} - -#[cfg(feature = "path")] -fn copy_stats( - source_meta: &std::fs::Metadata, - dest: &std::path::Path, -) -> Result<(), std::io::Error> { - let src_mtime = filetime::FileTime::from_last_modification_time(source_meta); - filetime::set_file_mtime(dest, src_mtime)?; - - Ok(()) -} - -#[cfg(not(feature = "path"))] -fn copy_stats( - _source_meta: &std::fs::Metadata, - _dest: &std::path::Path, -) -> Result<(), std::io::Error> { - Ok(()) -} - -#[cfg(windows)] -fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> { - std::os::windows::fs::symlink_file(target, link) -} - -#[cfg(not(windows))] -fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> { - std::os::unix::fs::symlink(target, link) -} - -pub fn resolve_dir( - path: impl AsRef, -) -> Result { - let path = path.as_ref(); - let meta = std::fs::symlink_metadata(path)?; - if meta.is_dir() { - canonicalize(path) - } else if meta.is_file() { - // Git might checkout symlinks as files - let target = std::fs::read_to_string(path)?; - let target_path = path.parent().unwrap().join(target); - resolve_dir(target_path) - } else { - canonicalize(path) - } -} - -fn canonicalize(path: &std::path::Path) -> Result { - #[cfg(feature = "path")] - { - dunce::canonicalize(path) - } - #[cfg(not(feature = "path"))] - { - // Hope for the best - Ok(strip_trailing_slash(path).to_owned()) - } -} - -pub fn strip_trailing_slash(path: &std::path::Path) -> &std::path::Path { - path.components().as_path() -} - -pub(crate) fn display_relpath(path: impl AsRef) -> String { - let path = path.as_ref(); - let relpath = if let Ok(cwd) = std::env::current_dir() { - match path.strip_prefix(cwd) { - Ok(path) => path, - Err(_) => path, - } - } else { - path - }; - relpath.display().to_string() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn strips_trailing_slash() { - let path = std::path::Path::new("/foo/bar/"); - let rendered = path.display().to_string(); - assert_eq!(rendered.as_bytes()[rendered.len() - 1], b'/'); - - let stripped = strip_trailing_slash(path); - let rendered = stripped.display().to_string(); - assert_eq!(rendered.as_bytes()[rendered.len() - 1], b'r'); - } - - #[test] - fn file_type_detect_file() { - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"); - dbg!(&path); - let actual = FileType::from_path(&path); - assert_eq!(actual, FileType::File); - } - - #[test] - fn file_type_detect_dir() { - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); - dbg!(path); - let actual = FileType::from_path(path); - assert_eq!(actual, FileType::Dir); - } - - #[test] - fn file_type_detect_missing() { - let path = std::path::Path::new("this-should-never-exist"); - dbg!(path); - let actual = FileType::from_path(path); - assert_eq!(actual, FileType::Missing); - } -} diff --git a/crates/snapbox/src/dir/fixture.rs b/crates/snapbox/src/dir/fixture.rs new file mode 100644 index 00000000..a1ae0ced --- /dev/null +++ b/crates/snapbox/src/dir/fixture.rs @@ -0,0 +1,104 @@ +/// Working directory for tests +#[derive(Debug)] +pub struct PathFixture(PathFixtureInner); + +#[derive(Debug)] +enum PathFixtureInner { + None, + Immutable(std::path::PathBuf), + #[cfg(feature = "path")] + MutablePath(std::path::PathBuf), + #[cfg(feature = "path")] + MutableTemp { + temp: tempfile::TempDir, + path: std::path::PathBuf, + }, +} + +impl PathFixture { + pub fn none() -> Self { + Self(PathFixtureInner::None) + } + + pub fn immutable(target: &std::path::Path) -> Self { + Self(PathFixtureInner::Immutable(target.to_owned())) + } + + #[cfg(feature = "path")] + pub fn mutable_temp() -> Result { + let temp = tempfile::tempdir().map_err(|e| e.to_string())?; + // We need to get the `/private` prefix on Mac so variable substitutions work + // correctly + let path = crate::dir::canonicalize(temp.path()) + .map_err(|e| format!("Failed to canonicalize {}: {}", temp.path().display(), e))?; + Ok(Self(PathFixtureInner::MutableTemp { temp, path })) + } + + #[cfg(feature = "path")] + pub fn mutable_at(target: &std::path::Path) -> Result { + let _ = std::fs::remove_dir_all(target); + std::fs::create_dir_all(target) + .map_err(|e| format!("Failed to create {}: {}", target.display(), e))?; + Ok(Self(PathFixtureInner::MutablePath(target.to_owned()))) + } + + #[cfg(feature = "path")] + pub fn with_template( + self, + template_root: &std::path::Path, + ) -> Result { + match &self.0 { + PathFixtureInner::None | PathFixtureInner::Immutable(_) => { + return Err("Sandboxing is disabled".into()); + } + PathFixtureInner::MutablePath(path) | PathFixtureInner::MutableTemp { path, .. } => { + crate::debug!( + "Initializing {} from {}", + path.display(), + template_root.display() + ); + super::copy_template(template_root, path)?; + } + } + + Ok(self) + } + + pub fn is_mutable(&self) -> bool { + match &self.0 { + PathFixtureInner::None | PathFixtureInner::Immutable(_) => false, + #[cfg(feature = "path")] + PathFixtureInner::MutablePath(_) => true, + #[cfg(feature = "path")] + PathFixtureInner::MutableTemp { .. } => true, + } + } + + pub fn path(&self) -> Option<&std::path::Path> { + match &self.0 { + PathFixtureInner::None => None, + PathFixtureInner::Immutable(path) => Some(path.as_path()), + #[cfg(feature = "path")] + PathFixtureInner::MutablePath(path) => Some(path.as_path()), + #[cfg(feature = "path")] + PathFixtureInner::MutableTemp { path, .. } => Some(path.as_path()), + } + } + + /// Explicitly close to report errors + pub fn close(self) -> Result<(), std::io::Error> { + match self.0 { + PathFixtureInner::None | PathFixtureInner::Immutable(_) => Ok(()), + #[cfg(feature = "path")] + PathFixtureInner::MutablePath(_) => Ok(()), + #[cfg(feature = "path")] + PathFixtureInner::MutableTemp { temp, .. } => temp.close(), + } + } +} + +impl Default for PathFixture { + fn default() -> Self { + Self::none() + } +} diff --git a/crates/snapbox/src/dir/mod.rs b/crates/snapbox/src/dir/mod.rs new file mode 100644 index 00000000..bfd01979 --- /dev/null +++ b/crates/snapbox/src/dir/mod.rs @@ -0,0 +1,22 @@ +//! Initialize working directories and assert on how they've changed + +mod diff; +mod fixture; +mod ops; +#[cfg(test)] +mod tests; + +pub use diff::FileType; +pub use diff::PathDiff; +pub use fixture::PathFixture; +#[cfg(feature = "path")] +pub use ops::copy_template; +pub use ops::resolve_dir; +pub use ops::strip_trailing_slash; +#[cfg(feature = "path")] +pub use ops::Walk; + +#[cfg(feature = "path")] +pub(crate) use ops::canonicalize; +pub(crate) use ops::display_relpath; +pub(crate) use ops::shallow_copy; diff --git a/crates/snapbox/src/dir/ops.rs b/crates/snapbox/src/dir/ops.rs new file mode 100644 index 00000000..a209f279 --- /dev/null +++ b/crates/snapbox/src/dir/ops.rs @@ -0,0 +1,186 @@ +/// Recursively walk a path +/// +/// Note: Ignores `.keep` files +#[cfg(feature = "path")] +pub struct Walk { + inner: walkdir::IntoIter, +} + +#[cfg(feature = "path")] +impl Walk { + pub fn new(path: &std::path::Path) -> Self { + Self { + inner: walkdir::WalkDir::new(path).into_iter(), + } + } +} + +#[cfg(feature = "path")] +impl Iterator for Walk { + type Item = Result; + + fn next(&mut self) -> Option { + while let Some(entry) = self.inner.next().map(|e| { + e.map(walkdir::DirEntry::into_path) + .map_err(std::io::Error::from) + }) { + if entry.as_ref().ok().and_then(|e| e.file_name()) + != Some(std::ffi::OsStr::new(".keep")) + { + return Some(entry); + } + } + None + } +} + +/// Copy a template into a [`PathFixture`][super::PathFixture] +/// +/// Note: Generally you'll use [`PathFixture::with_template`][super::PathFixture::with_template] instead. +/// +/// Note: Ignores `.keep` files +#[cfg(feature = "path")] +pub fn copy_template( + source: impl AsRef, + dest: impl AsRef, +) -> Result<(), crate::assert::Error> { + let source = source.as_ref(); + let dest = dest.as_ref(); + let source = canonicalize(source) + .map_err(|e| format!("Failed to canonicalize {}: {}", source.display(), e))?; + std::fs::create_dir_all(dest) + .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?; + let dest = canonicalize(dest) + .map_err(|e| format!("Failed to canonicalize {}: {}", dest.display(), e))?; + + for current in Walk::new(&source) { + let current = current.map_err(|e| e.to_string())?; + let rel = current.strip_prefix(&source).unwrap(); + let target = dest.join(rel); + + shallow_copy(¤t, &target)?; + } + + Ok(()) +} + +/// Copy a file system entry, without recursing +pub(crate) fn shallow_copy( + source: &std::path::Path, + dest: &std::path::Path, +) -> Result<(), crate::assert::Error> { + let meta = source + .symlink_metadata() + .map_err(|e| format!("Failed to read metadata from {}: {}", source.display(), e))?; + if meta.is_dir() { + std::fs::create_dir_all(dest) + .map_err(|e| format!("Failed to create {}: {}", dest.display(), e))?; + } else if meta.is_file() { + std::fs::copy(source, dest).map_err(|e| { + format!( + "Failed to copy {} to {}: {}", + source.display(), + dest.display(), + e + ) + })?; + // Avoid a mtime check race where: + // - Copy files + // - Test checks mtime + // - Test writes + // - Test checks mtime + // + // If all of this happens too close to each other, then the second mtime check will think + // nothing was written by the test. + // + // Instead of just setting 1s in the past, we'll just respect the existing mtime. + copy_stats(&meta, dest).map_err(|e| { + format!( + "Failed to copy {} metadata to {}: {}", + source.display(), + dest.display(), + e + ) + })?; + } else if let Ok(target) = std::fs::read_link(source) { + symlink_to_file(dest, &target) + .map_err(|e| format!("Failed to create symlink {}: {}", dest.display(), e))?; + } + + Ok(()) +} + +#[cfg(feature = "path")] +fn copy_stats( + source_meta: &std::fs::Metadata, + dest: &std::path::Path, +) -> Result<(), std::io::Error> { + let src_mtime = filetime::FileTime::from_last_modification_time(source_meta); + filetime::set_file_mtime(dest, src_mtime)?; + + Ok(()) +} + +#[cfg(not(feature = "path"))] +fn copy_stats( + _source_meta: &std::fs::Metadata, + _dest: &std::path::Path, +) -> Result<(), std::io::Error> { + Ok(()) +} + +#[cfg(windows)] +fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> { + std::os::windows::fs::symlink_file(target, link) +} + +#[cfg(not(windows))] +fn symlink_to_file(link: &std::path::Path, target: &std::path::Path) -> Result<(), std::io::Error> { + std::os::unix::fs::symlink(target, link) +} + +pub fn resolve_dir( + path: impl AsRef, +) -> Result { + let path = path.as_ref(); + let meta = std::fs::symlink_metadata(path)?; + if meta.is_dir() { + canonicalize(path) + } else if meta.is_file() { + // Git might checkout symlinks as files + let target = std::fs::read_to_string(path)?; + let target_path = path.parent().unwrap().join(target); + resolve_dir(target_path) + } else { + canonicalize(path) + } +} + +pub(crate) fn canonicalize(path: &std::path::Path) -> Result { + #[cfg(feature = "path")] + { + dunce::canonicalize(path) + } + #[cfg(not(feature = "path"))] + { + // Hope for the best + Ok(strip_trailing_slash(path).to_owned()) + } +} + +pub fn strip_trailing_slash(path: &std::path::Path) -> &std::path::Path { + path.components().as_path() +} + +pub(crate) fn display_relpath(path: impl AsRef) -> String { + let path = path.as_ref(); + let relpath = if let Ok(cwd) = std::env::current_dir() { + match path.strip_prefix(cwd) { + Ok(path) => path, + Err(_) => path, + } + } else { + path + }; + relpath.display().to_string() +} diff --git a/crates/snapbox/src/dir/tests.rs b/crates/snapbox/src/dir/tests.rs new file mode 100644 index 00000000..f235eae2 --- /dev/null +++ b/crates/snapbox/src/dir/tests.rs @@ -0,0 +1,36 @@ +use super::*; + +#[test] +fn strips_trailing_slash() { + let path = std::path::Path::new("/foo/bar/"); + let rendered = path.display().to_string(); + assert_eq!(rendered.as_bytes()[rendered.len() - 1], b'/'); + + let stripped = strip_trailing_slash(path); + let rendered = stripped.display().to_string(); + assert_eq!(rendered.as_bytes()[rendered.len() - 1], b'r'); +} + +#[test] +fn file_type_detect_file() { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"); + dbg!(&path); + let actual = FileType::from_path(&path); + assert_eq!(actual, FileType::File); +} + +#[test] +fn file_type_detect_dir() { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + dbg!(path); + let actual = FileType::from_path(path); + assert_eq!(actual, FileType::Dir); +} + +#[test] +fn file_type_detect_missing() { + let path = std::path::Path::new("this-should-never-exist"); + dbg!(path); + let actual = FileType::from_path(path); + assert_eq!(actual, FileType::Missing); +} diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index e63ac638..bf56493d 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -34,7 +34,7 @@ //! [`Output`][std::process::Output]. //! //! Testing Filesystem Interactions: -//! - [`path::PathFixture`]: Working directory for tests +//! - [`dir::PathFixture`]: Working directory for tests //! - [`Assert`]: Diff a directory against files present in a pattern directory //! //! You can also build your own version of these with the lower-level building blocks these are @@ -66,8 +66,8 @@ mod macros; pub mod assert; pub mod cmd; pub mod data; +pub mod dir; pub mod filters; -pub mod path; pub mod report; pub mod utils; diff --git a/crates/snapbox/src/macros.rs b/crates/snapbox/src/macros.rs index 897b32a7..1df64a4d 100644 --- a/crates/snapbox/src/macros.rs +++ b/crates/snapbox/src/macros.rs @@ -50,7 +50,7 @@ macro_rules! assert_data_eq { #[macro_export] macro_rules! current_dir { () => {{ - let root = $crate::path::cargo_rustc_current_dir!(); + let root = $crate::utils::cargo_rustc_current_dir!(); let file = ::std::file!(); let rel_path = ::std::path::Path::new(file).parent().unwrap(); root.join(rel_path) @@ -62,7 +62,7 @@ macro_rules! current_dir { #[macro_export] macro_rules! current_rs { () => {{ - let root = $crate::path::cargo_rustc_current_dir!(); + let root = $crate::utils::cargo_rustc_current_dir!(); let file = ::std::file!(); let rel_path = ::std::path::Path::new(file); root.join(rel_path) diff --git a/crates/snapbox/src/utils/mod.rs b/crates/snapbox/src/utils/mod.rs index 8212959f..8f3a65af 100644 --- a/crates/snapbox/src/utils/mod.rs +++ b/crates/snapbox/src/utils/mod.rs @@ -1,3 +1,10 @@ mod lines; pub use lines::LinesWithTerminator; + +#[doc(inline)] +pub use crate::cargo_rustc_current_dir; +#[doc(inline)] +pub use crate::current_dir; +#[doc(inline)] +pub use crate::current_rs; diff --git a/crates/trycmd/src/runner.rs b/crates/trycmd/src/runner.rs index 5449bbbb..86de15e8 100644 --- a/crates/trycmd/src/runner.rs +++ b/crates/trycmd/src/runner.rs @@ -13,8 +13,8 @@ use std::io::stderr; use rayon::prelude::*; use snapbox::data::DataFormat; +use snapbox::dir::FileType; use snapbox::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; -use snapbox::path::FileType; use snapbox::IntoData; #[derive(Debug)] @@ -188,7 +188,7 @@ impl Case { .map(|p| { sequence.fs.rel_cwd().map(|rel| { let p = p.join(rel); - snapbox::path::strip_trailing_slash(&p).to_owned() + snapbox::dir::strip_trailing_slash(&p).to_owned() }) }) .transpose() @@ -504,7 +504,7 @@ impl Case { } else { let fixture_root = self.path.with_extension("out"); if fixture_root.exists() { - for status in snapbox::path::PathDiff::subset_matches_iter( + for status in snapbox::dir::PathDiff::subset_matches_iter( fixture_root, actual_root, substitutions, @@ -878,11 +878,11 @@ impl FileStatus { } } -impl From for FileStatus { - fn from(other: snapbox::path::PathDiff) -> Self { +impl From for FileStatus { + fn from(other: snapbox::dir::PathDiff) -> Self { match other { - snapbox::path::PathDiff::Failure(err) => FileStatus::Failure(err), - snapbox::path::PathDiff::TypeMismatch { + snapbox::dir::PathDiff::Failure(err) => FileStatus::Failure(err), + snapbox::dir::PathDiff::TypeMismatch { expected_path, actual_path, expected_type, @@ -893,7 +893,7 @@ impl From for FileStatus { actual_type, expected_type, }, - snapbox::path::PathDiff::LinkMismatch { + snapbox::dir::PathDiff::LinkMismatch { expected_path, actual_path, expected_target, @@ -904,7 +904,7 @@ impl From for FileStatus { actual_target, expected_target, }, - snapbox::path::PathDiff::ContentMismatch { + snapbox::dir::PathDiff::ContentMismatch { expected_path, actual_path, expected_content, @@ -1016,20 +1016,20 @@ fn fs_context( cwd: Option<&std::path::Path>, sandbox: bool, mode: &crate::Mode, -) -> Result { +) -> Result { if sandbox { #[cfg(feature = "filesystem")] match mode { crate::Mode::Dump(root) => { let target = root.join(path.with_extension("out").file_name().unwrap()); - let mut context = snapbox::path::PathFixture::mutable_at(&target)?; + let mut context = snapbox::dir::PathFixture::mutable_at(&target)?; if let Some(cwd) = cwd { context = context.with_template(cwd)?; } Ok(context) } crate::Mode::Fail | crate::Mode::Overwrite => { - let mut context = snapbox::path::PathFixture::mutable_temp()?; + let mut context = snapbox::dir::PathFixture::mutable_temp()?; if let Some(cwd) = cwd { context = context.with_template(cwd)?; } @@ -1040,7 +1040,7 @@ fn fs_context( Err("Sandboxing is disabled".into()) } else { Ok(cwd - .map(snapbox::path::PathFixture::immutable) - .unwrap_or_else(snapbox::path::PathFixture::none)) + .map(snapbox::dir::PathFixture::immutable) + .unwrap_or_else(snapbox::dir::PathFixture::none)) } } diff --git a/crates/trycmd/src/schema.rs b/crates/trycmd/src/schema.rs index eb78705a..1a9512f2 100644 --- a/crates/trycmd/src/schema.rs +++ b/crates/trycmd/src/schema.rs @@ -109,13 +109,13 @@ impl TryCmd { .fs .base .take() - .map(|p| snapbox::path::resolve_dir(p).map_err(|e| e.to_string())) + .map(|p| snapbox::dir::resolve_dir(p).map_err(|e| e.to_string())) .transpose()?; sequence.fs.cwd = sequence .fs .cwd .take() - .map(|p| snapbox::path::resolve_dir(p).map_err(|e| e.to_string())) + .map(|p| snapbox::dir::resolve_dir(p).map_err(|e| e.to_string())) .transpose()?; Ok(sequence)