diff --git a/crates/tryfn/src/lib.rs b/crates/tryfn/src/lib.rs index 0cc04857..b04ba4b6 100644 --- a/crates/tryfn/src/lib.rs +++ b/crates/tryfn/src/lib.rs @@ -21,7 +21,7 @@ //! //! 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"); +//! let expected = tryfn::Data::read_from(&input_path.with_extension("out"), None); //! tryfn::Case { //! name, //! fixture: input_path, @@ -41,25 +41,26 @@ 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 { +pub struct Harness { root: std::path::PathBuf, overrides: Option, setup: S, test: T, config: snapbox::Assert, + test_output: std::marker::PhantomData, + test_error: std::marker::PhantomData, } -impl Harness +impl Harness where + S: Setup + Send + Sync + 'static, + T: Test + Clone + Send + Sync + 'static, 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 /// @@ -67,6 +68,15 @@ where /// are considered /// - `setup`: Given a path, choose the test name and the output location /// - `test`: Given a path, return the actual output value + /// + /// By default filters are applied, including: + /// - `...` is a line-wildcard when on a line by itself + /// - `[..]` is a character-wildcard when inside a line + /// - `[EXE]` matches `.exe` on Windows + /// - `\` to `/` + /// - Newlines + /// + /// To limit this to newline normalization for text, have [`Setup`] call [`Data::raw`][snapbox::Data::raw] on `expected`. pub fn new(input_root: impl Into, setup: S, test: T) -> Self { Self { root: input_root.into(), @@ -74,6 +84,8 @@ where setup, test, config: snapbox::Assert::new().action_env(snapbox::assert::DEFAULT_ACTION_ENV), + test_output: Default::default(), + test_error: Default::default(), } } @@ -89,18 +101,6 @@ where 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 @@ -133,18 +133,23 @@ where let tests: Vec<_> = tests .into_iter() .map(|path| { - let case = (self.setup)(path); + let case = self.setup.setup(path); + assert!( + case.expected.source().map(|s| s.is_path()).unwrap_or(false), + "`Case::expected` must be from a file" + ); 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 = test.run(&case.fixture)?; let actual = actual.to_string(); - let actual = crate::Data::text(actual); - config.try_eq(Some(&case.name), actual, expected.raw())?; + let actual = snapbox::Data::text(actual); + config.try_eq(Some(&case.name), actual, case.expected.clone())?; Ok(()) }) - .with_ignored_flag(shared_config.selected_action() == Action::Ignore) + .with_ignored_flag( + shared_config.selected_action() == snapbox::assert::Action::Ignore, + ) }) .collect(); @@ -153,14 +158,48 @@ where } } -/// A test case enumerated by the [`Harness`] with data from the `setup` function -/// -/// See [`harness`][crate] for more details +/// Function signature for generating a test [`Case`] from a path fixture +pub trait Setup { + fn setup(&self, fixture: std::path::PathBuf) -> Case; +} + +impl Setup for F +where + F: Fn(std::path::PathBuf) -> Case, +{ + fn setup(&self, fixture: std::path::PathBuf) -> Case { + (self)(fixture) + } +} + +/// Function signature for running a test [`Case`] +pub trait Test +where + S: std::fmt::Display, + E: std::fmt::Display, +{ + fn run(&self, fixture: &std::path::Path) -> Result; +} + +impl Test for F +where + F: Fn(&std::path::Path) -> Result, + S: std::fmt::Display, + E: std::fmt::Display, +{ + fn run(&self, fixture: &std::path::Path) -> Result { + (self)(fixture) + } +} + +/// A test case enumerated by the [`Harness`] with data from the [`Setup`] function 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, + /// + /// Generally derived from `fixture` and loaded with [`Data::read_from`] + pub expected: Data, }