use libtest_mimic::Trial;
pub use snapbox::data::DataFormat;
pub use snapbox::Data;
pub struct Harness<S, T, I, E> {
root: std::path::PathBuf,
overrides: Option<ignore::overrides::Override>,
setup: S,
test: T,
config: snapbox::Assert,
test_output: std::marker::PhantomData<I>,
test_error: std::marker::PhantomData<E>,
}
impl<S, T, I, E> Harness<S, T, I, E>
where
S: Setup + Send + Sync + 'static,
T: Test<I, E> + Clone + Send + Sync + 'static,
I: std::fmt::Display,
E: std::fmt::Display,
{
pub fn new(input_root: impl Into<std::path::PathBuf>, 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),
test_output: Default::default(),
test_error: Default::default(),
}
}
pub fn select<'p>(mut self, patterns: impl IntoIterator<Item = &'p str>) -> 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
}
pub fn with_assert(mut self, config: snapbox::Assert) -> Self {
self.config = config;
self
}
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.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 actual = test.run(&case.fixture)?;
let actual = actual.to_string();
let actual = snapbox::Data::text(actual);
config.try_eq(Some(&case.name), actual, case.expected.clone())?;
Ok(())
})
.with_ignored_flag(
shared_config.selected_action() == snapbox::assert::Action::Ignore,
)
})
.collect();
let args = libtest_mimic::Arguments::from_args();
libtest_mimic::run(&args, tests).exit()
}
}
pub trait Setup {
fn setup(&self, fixture: std::path::PathBuf) -> Case;
}
impl<F> Setup for F
where
F: Fn(std::path::PathBuf) -> Case,
{
fn setup(&self, fixture: std::path::PathBuf) -> Case {
(self)(fixture)
}
}
pub trait Test<S, E>
where
S: std::fmt::Display,
E: std::fmt::Display,
{
fn run(&self, fixture: &std::path::Path) -> Result<S, E>;
}
impl<F, S, E> Test<S, E> for F
where
F: Fn(&std::path::Path) -> Result<S, E>,
S: std::fmt::Display,
E: std::fmt::Display,
{
fn run(&self, fixture: &std::path::Path) -> Result<S, E> {
(self)(fixture)
}
}
pub struct Case {
pub name: String,
pub fixture: std::path::PathBuf,
pub expected: Data,
}