diff --git a/crates/tryfn/src/lib.rs b/crates/tryfn/src/lib.rs index b2cb7927..14a66ff5 100644 --- a/crates/tryfn/src/lib.rs +++ b/crates/tryfn/src/lib.rs @@ -45,20 +45,22 @@ 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 /// @@ -66,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`][snapbox::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(), @@ -73,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(), } } @@ -120,7 +133,7 @@ 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" @@ -128,10 +141,10 @@ where let test = self.test.clone(); let config = shared_config.clone(); Trial::test(case.name.clone(), move || { - 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, case.expected.clone())?; + let actual = snapbox::Data::text(actual); + config.try_eq(case.expected.clone(), actual, Some(&case.name))?; Ok(()) }) .with_ignored_flag( @@ -145,9 +158,41 @@ 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,