From 7aea08db6e5a11a1dd7f45d34eae0ddfd4951585 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 17 May 2024 15:04:02 -0500 Subject: [PATCH 1/2] fix(tryfn): Split out harness Cherry pick 8e26ddb9cd402d8b0786cf3934fbd9cf6718df16 (#295) Cherry pick c736e0317518cb078d96b4a82b3b72293c077566 (#295) --- Cargo.lock | 9 ++ crates/snapbox/src/assert/mod.rs | 8 +- crates/snapbox/src/harness.rs | 5 +- crates/tryfn/CHANGELOG.md | 11 ++ crates/tryfn/Cargo.toml | 40 ++++++++ crates/tryfn/LICENSE-APACHE | 1 + crates/tryfn/LICENSE-MIT | 1 + crates/tryfn/README.md | 26 +++++ crates/tryfn/src/lib.rs | 166 +++++++++++++++++++++++++++++++ 9 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 crates/tryfn/CHANGELOG.md create mode 100644 crates/tryfn/Cargo.toml create mode 120000 crates/tryfn/LICENSE-APACHE create mode 120000 crates/tryfn/LICENSE-MIT create mode 100644 crates/tryfn/README.md create mode 100644 crates/tryfn/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d29eff3c..5c17020b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -925,6 +925,15 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "tryfn" +version = "0.1.0" +dependencies = [ + "ignore", + "libtest-mimic", + "snapbox", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/crates/snapbox/src/assert/mod.rs b/crates/snapbox/src/assert/mod.rs index abfafcd3..467ba554 100644 --- a/crates/snapbox/src/assert/mod.rs +++ b/crates/snapbox/src/assert/mod.rs @@ -148,7 +148,7 @@ impl Assert { } } - pub(crate) fn try_eq( + pub fn try_eq( &self, actual_name: Option<&dyn std::fmt::Display>, actual: crate::Data, @@ -468,6 +468,12 @@ impl Assert { } } +impl Assert { + pub fn selected_action(&self) -> Action { + self.action + } +} + impl Default for Assert { fn default() -> Self { Self { diff --git a/crates/snapbox/src/harness.rs b/crates/snapbox/src/harness.rs index 38b12a15..237397c0 100644 --- a/crates/snapbox/src/harness.rs +++ b/crates/snapbox/src/harness.rs @@ -40,7 +40,10 @@ use crate::Action; use libtest_mimic::Trial; -/// [`Harness`] for discovering test inputs and asserting against snapshot files +/// [`Fallback dependenciesforfallback-dependenciess +/// [`Build script directivesck-build-script-directivess +/// [`When to use packages or workspaces?ck-when-to-use-packages-or-workspacess +/// [`Cargo and rustupes?cargo-and-rustups /// /// See [`harness`][crate::harness] for more details pub struct Harness { diff --git a/crates/tryfn/CHANGELOG.md b/crates/tryfn/CHANGELOG.md new file mode 100644 index 00000000..1c71609d --- /dev/null +++ b/crates/tryfn/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [Unreleased] - ReleaseDate + + +[Unreleased]: https://github.com/assert-rs/trycmd/compare/3e293f6f6167270d85f57a7b59fd94590af6fa97...HEAD diff --git a/crates/tryfn/Cargo.toml b/crates/tryfn/Cargo.toml new file mode 100644 index 00000000..ada42991 --- /dev/null +++ b/crates/tryfn/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tryfn" +version = "0.1.0" +description = "File-driven snapshot testing for a function" +authors = ["Ed Page "] +repository = "https://github.com/assert-rs/trycmd.git" +homepage = "https://github.com/assert-rs/trycmd" +documentation = "http://docs.rs/tryfn/" +readme = "README.md" +categories = ["development-tools::testing"] +keywords = ["test", "assert", "snapsjot"] +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/trycmd/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = ["color-auto", "diff"] +diff = ["snapbox/diff"] +color = ["snapbox/color"] +color-auto = ["snapbox/color-auto"] + +[dependencies] +snapbox = { path = "../snapbox", version = "0.5.9", default-features = false } +libtest-mimic = "0.7.0" +ignore = "0.4" diff --git a/crates/tryfn/LICENSE-APACHE b/crates/tryfn/LICENSE-APACHE new file mode 120000 index 00000000..1cd601d0 --- /dev/null +++ b/crates/tryfn/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/tryfn/LICENSE-MIT b/crates/tryfn/LICENSE-MIT new file mode 120000 index 00000000..b2cfbdc7 --- /dev/null +++ b/crates/tryfn/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/tryfn/README.md b/crates/tryfn/README.md new file mode 100644 index 00000000..9a6c44a9 --- /dev/null +++ b/crates/tryfn/README.md @@ -0,0 +1,26 @@ +# tryfn + +> File-driven snapshot testing for a function + +[![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] +![License](https://img.shields.io/crates/l/tryfn.svg) +[![Crates Status](https://img.shields.io/crates/v/tryfn.svg)][Crates.io] + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + +[Crates.io]: https://crates.io/crates/tryfn +[Documentation]: https://docs.rs/tryfn diff --git a/crates/tryfn/src/lib.rs b/crates/tryfn/src/lib.rs new file mode 100644 index 00000000..0cc04857 --- /dev/null +++ b/crates/tryfn/src/lib.rs @@ -0,0 +1,166 @@ +//! [`Harness`] for discovering test inputs and asserting against snapshot files +//! +//! This is a custom test harness and should be put in its own test binary with +//! [`test.harness = false`](https://doc.rust-lang.org/stable/cargo/reference/cargo-targets.html#the-harness-field). +//! +//! # Examples +//! +//! ```rust,no_run +//! fn some_func(num: usize) -> usize { +//! // ... +//! # 10 +//! } +//! +//! tryfn::Harness::new( +//! "tests/fixtures/invalid", +//! setup, +//! test, +//! ) +//! .select(["tests/cases/*.in"]) +//! .test(); +//! +//! fn setup(input_path: std::path::PathBuf) -> tryfn::Case { +//! let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); +//! let expected = input_path.with_extension("out"); +//! tryfn::Case { +//! name, +//! fixture: input_path, +//! expected, +//! } +//! } +//! +//! fn test(input_path: &std::path::Path) -> Result> { +//! let raw = std::fs::read_to_string(input_path)?; +//! let num = raw.parse::()?; +//! +//! let actual = some_func(num); +//! +//! Ok(actual) +//! } +//! ``` + +use libtest_mimic::Trial; + +pub use snapbox::assert::Action; +pub use snapbox::data::DataFormat; +pub use snapbox::Data; + +/// [`Harness`] for discovering test inputs and asserting against snapshot files +pub struct Harness { + root: std::path::PathBuf, + overrides: Option, + setup: S, + test: T, + config: snapbox::Assert, +} + +impl Harness +where + I: std::fmt::Display, + E: std::fmt::Display, + S: Fn(std::path::PathBuf) -> Case + Send + Sync + 'static, + T: Fn(&std::path::Path) -> Result + Send + Sync + 'static + Clone, +{ + /// Specify where the test scenarios + /// + /// - `input_root`: where to find the files. See [`Self::select`] for restricting what files + /// are considered + /// - `setup`: Given a path, choose the test name and the output location + /// - `test`: Given a path, return the actual output value + pub fn new(input_root: impl Into, setup: S, test: T) -> Self { + Self { + root: input_root.into(), + overrides: None, + setup, + test, + config: snapbox::Assert::new().action_env(snapbox::assert::DEFAULT_ACTION_ENV), + } + } + + /// Path patterns for selecting input files + /// + /// This uses gitignore syntax + pub fn select<'p>(mut self, patterns: impl IntoIterator) -> Self { + let mut overrides = ignore::overrides::OverrideBuilder::new(&self.root); + for line in patterns { + overrides.add(line).unwrap(); + } + self.overrides = Some(overrides.build().unwrap()); + self + } + + /// Read the failure action from an environment variable + pub fn action_env(mut self, var_name: &str) -> Self { + self.config = self.config.action_env(var_name); + self + } + + /// Override the failure action + pub fn action(mut self, action: Action) -> Self { + self.config = self.config.action(action); + self + } + + /// Customize the assertion behavior + /// + /// Includes + /// - Configuring redactions + /// - Override updating environment vaeiable + pub fn with_assert(mut self, config: snapbox::Assert) -> Self { + self.config = config; + self + } + + /// Run tests + pub fn test(self) -> ! { + let mut walk = ignore::WalkBuilder::new(&self.root); + walk.standard_filters(false); + let tests = walk.build().filter_map(|entry| { + let entry = entry.unwrap(); + let is_dir = entry.file_type().map(|f| f.is_dir()).unwrap_or(false); + let path = entry.into_path(); + if let Some(overrides) = &self.overrides { + overrides + .matched(&path, is_dir) + .is_whitelist() + .then_some(path) + } else { + Some(path) + } + }); + + let shared_config = std::sync::Arc::new(self.config); + let tests: Vec<_> = tests + .into_iter() + .map(|path| { + let case = (self.setup)(path); + let test = self.test.clone(); + let config = shared_config.clone(); + Trial::test(case.name.clone(), move || { + let expected = crate::Data::read_from(&case.expected, Some(DataFormat::Text)); + let actual = (test)(&case.fixture)?; + let actual = actual.to_string(); + let actual = crate::Data::text(actual); + config.try_eq(Some(&case.name), actual, expected.raw())?; + Ok(()) + }) + .with_ignored_flag(shared_config.selected_action() == Action::Ignore) + }) + .collect(); + + let args = libtest_mimic::Arguments::from_args(); + libtest_mimic::run(&args, tests).exit() + } +} + +/// A test case enumerated by the [`Harness`] with data from the `setup` function +/// +/// See [`harness`][crate] for more details +pub struct Case { + /// Display name + pub name: String, + /// Input for the test + pub fixture: std::path::PathBuf, + /// What the actual output should be compared against or updated + pub expected: std::path::PathBuf, +} From 667e940948a9a8ed3908ecf04fe39ad949bd1304 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 17 May 2024 15:16:43 -0500 Subject: [PATCH 2/2] fix(tryfn): Deprecate Action overriding for Assert Cherry pick a30d3bf4d72aef572a1794d3eb002fd518a56b2b (#295) --- crates/snapbox/src/harness.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/snapbox/src/harness.rs b/crates/snapbox/src/harness.rs index 237397c0..0565d1e4 100644 --- a/crates/snapbox/src/harness.rs +++ b/crates/snapbox/src/harness.rs @@ -12,7 +12,6 @@ //! test, //! ) //! .select(["tests/cases/*.in"]) -//! .action_env("SNAPSHOTS") //! .test(); //! //! fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { @@ -90,12 +89,14 @@ where } /// Read the failure action from an environment variable + #[deprecated(since = "0.1.0", note = "Replaced with `Harness::with_assert`")] pub fn action_env(mut self, var_name: &str) -> Self { self.config = self.config.action_env(var_name); self } /// Override the failure action + #[deprecated(since = "0.1.0", note = "Replaced with `Harness::with_assert`")] pub fn action(mut self, action: Action) -> Self { self.config = self.config.action(action); self