diff --git a/crates/libtest2-harness/src/harness.rs b/crates/libtest2-harness/src/harness.rs index 202cf3d..60e384f 100644 --- a/crates/libtest2-harness/src/harness.rs +++ b/crates/libtest2-harness/src/harness.rs @@ -2,82 +2,153 @@ use libtest_lexarg::OutputFormat; use crate::{cli, notify, Case, RunError, RunMode, TestContext}; -pub struct Harness { - raw: std::io::Result>, - cases: Vec>, +pub trait HarnessState: sealed::_HarnessState_is_Sealed {} + +pub struct Harness { + state: State, } -impl Harness { - pub fn with_env() -> Self { - let raw = std::env::args_os(); - Self::with_args(raw) +pub struct StateInitial { + start: std::time::Instant, +} +impl HarnessState for StateInitial {} +impl sealed::_HarnessState_is_Sealed for StateInitial {} + +impl Harness { + pub fn new() -> Self { + Self { + state: StateInitial { + start: std::time::Instant::now(), + }, + } } - pub fn with_args(args: impl IntoIterator>) -> Self { - let raw = expand_args(args); - Self { raw, cases: vec![] } + pub fn with_env(self) -> std::io::Result> { + let raw = std::env::args_os(); + self.with_args(raw) } - pub fn discover(&mut self, cases: impl IntoIterator) { - for case in cases { - self.cases.push(Box::new(case)); - } + pub fn with_args( + self, + args: impl IntoIterator>, + ) -> std::io::Result> { + let raw = expand_args(args)?; + Ok(Harness { + state: StateArgs { + start: self.state.start, + raw, + }, + }) } +} - pub fn main(self) -> ! { - main(self.raw, self.cases) +impl Default for Harness { + fn default() -> Self { + Self::new() } } -const ERROR_EXIT_CODE: i32 = 101; - -fn main(raw: std::io::Result>, mut cases: Vec>) -> ! { - let start = std::time::Instant::now(); +pub struct StateArgs { + start: std::time::Instant, + raw: Vec, +} +impl HarnessState for StateArgs {} +impl sealed::_HarnessState_is_Sealed for StateArgs {} + +impl Harness { + pub fn parse(&self) -> Result, cli::LexError<'_>> { + let mut parser = cli::Parser::new(&self.state.raw); + let opts = parse(&mut parser)?; + + #[cfg(feature = "color")] + match opts.color { + libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto, + libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always, + libtest_lexarg::ColorConfig::NeverColor => anstream::ColorChoice::Never, + } + .write_global(); - let raw = match raw { - Ok(raw) => raw, - Err(err) => { + let notifier = notifier(&opts).unwrap_or_else(|err| { eprintln!("{err}"); std::process::exit(1) - } - }; - let mut parser = cli::Parser::new(&raw); - let opts = parse(&mut parser).unwrap_or_else(|err| { - eprintln!("{err}"); - std::process::exit(1) - }); + }); + + Ok(Harness { + state: StateParsed { + start: self.state.start, + opts, + notifier, + }, + }) + } +} - #[cfg(feature = "color")] - match opts.color { - libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto, - libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always, - libtest_lexarg::ColorConfig::NeverColor => anstream::ColorChoice::Never, +pub struct StateParsed { + start: std::time::Instant, + opts: libtest_lexarg::TestOpts, + notifier: Box, +} +impl HarnessState for StateParsed {} +impl sealed::_HarnessState_is_Sealed for StateParsed {} + +impl Harness { + pub fn discover( + mut self, + cases: impl IntoIterator, + ) -> std::io::Result> { + let mut cases = cases + .into_iter() + .map(|c| Box::new(c) as Box) + .collect(); + discover( + &self.state.start, + &self.state.opts, + &mut cases, + self.state.notifier.as_mut(), + )?; + Ok(Harness { + state: StateDiscovered { + start: self.state.start, + opts: self.state.opts, + notifier: self.state.notifier, + cases, + }, + }) } - .write_global(); +} - let mut notifier = notifier(&opts).unwrap_or_else(|err| { - eprintln!("{err}"); - std::process::exit(1) - }); - discover(&start, &opts, &mut cases, notifier.as_mut()).unwrap_or_else(|err| { - eprintln!("{err}"); - std::process::exit(1) - }); +pub struct StateDiscovered { + start: std::time::Instant, + opts: libtest_lexarg::TestOpts, + notifier: Box, + cases: Vec>, +} +impl HarnessState for StateDiscovered {} +impl sealed::_HarnessState_is_Sealed for StateDiscovered {} - if !opts.list { - match run(&start, &opts, cases, notifier.as_mut()) { - Ok(true) => {} - Ok(false) => std::process::exit(ERROR_EXIT_CODE), - Err(e) => { - eprintln!("error: io error when listing tests: {e:?}"); - std::process::exit(ERROR_EXIT_CODE) - } +impl Harness { + pub fn run(mut self) -> std::io::Result { + if self.state.opts.list { + Ok(true) + } else { + run( + &self.state.start, + &self.state.opts, + self.state.cases, + self.state.notifier.as_mut(), + ) } } +} - std::process::exit(0) +mod sealed { + #[allow(unnameable_types)] + #[allow(non_camel_case_types)] + pub trait _HarnessState_is_Sealed {} } +pub const ERROR_EXIT_CODE: i32 = 101; + fn parse<'p>(parser: &mut cli::Parser<'p>) -> Result> { let mut test_opts = libtest_lexarg::TestOptsBuilder::new(); diff --git a/crates/libtest2-harness/src/lib.rs b/crates/libtest2-harness/src/lib.rs index f1ebf45..196b2bb 100644 --- a/crates/libtest2-harness/src/lib.rs +++ b/crates/libtest2-harness/src/lib.rs @@ -1,24 +1,4 @@ //! An experimental replacement for the core of libtest -//! -//! # Usage -//! -//! To use this, you most likely want to add a manual `[[test]]` section to -//! `Cargo.toml` and set `harness = false`. For example: -//! -//! ```toml -//! [[test]] -//! name = "mytest" -//! path = "tests/mytest.rs" -//! harness = false -//! ``` -//! -//! And in `tests/mytest.rs` you would call [`Harness::main`] in the `main` function: -//! -//! ```no_run -//! libtest2_harness::Harness::with_env() -//! .main(); -//! ``` -//! #![cfg_attr(docsrs, feature(doc_auto_cfg))] // #![warn(clippy::print_stderr)] diff --git a/crates/libtest2-mimic/src/lib.rs b/crates/libtest2-mimic/src/lib.rs index c3adfad..2ed4fc6 100644 --- a/crates/libtest2-mimic/src/lib.rs +++ b/crates/libtest2-mimic/src/lib.rs @@ -21,36 +21,63 @@ //! #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![warn(clippy::print_stderr)] +//#![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] pub use libtest_json::RunMode; pub struct Harness { - harness: libtest2_harness::Harness, + raw: Vec, + cases: Vec, } impl Harness { pub fn with_args(args: impl IntoIterator>) -> Self { Self { - harness: libtest2_harness::Harness::with_args(args), + raw: args.into_iter().map(|a| a.into()).collect(), + cases: Vec::new(), } } pub fn with_env() -> Self { - Self { - harness: libtest2_harness::Harness::with_env(), - } + let raw = std::env::args_os(); + Self::with_args(raw) } pub fn discover(mut self, cases: impl IntoIterator) -> Self { - self.harness - .discover(cases.into_iter().map(|c| TrialCase { inner: c })); + self.cases.extend(cases); self } pub fn main(self) -> ! { - self.harness.main() + match self.run() { + Ok(true) => std::process::exit(0), + Ok(false) => std::process::exit(libtest2_harness::ERROR_EXIT_CODE), + Err(err) => { + eprintln!("{err}"); + std::process::exit(libtest2_harness::ERROR_EXIT_CODE) + } + } + } + + fn run(self) -> std::io::Result { + let harness = libtest2_harness::Harness::new(); + let harness = match harness.with_args(self.raw) { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + std::process::exit(1); + } + }; + let harness = match harness.parse() { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + std::process::exit(1); + } + }; + let harness = harness.discover(self.cases.into_iter().map(|t| TrialCase { inner: t }))?; + harness.run() } } diff --git a/crates/libtest2/examples/tidy.rs b/crates/libtest2/examples/tidy.rs index 57423df..7c9b588 100644 --- a/crates/libtest2/examples/tidy.rs +++ b/crates/libtest2/examples/tidy.rs @@ -3,10 +3,22 @@ use libtest2::RunResult; use libtest2::Trial; fn main() -> std::io::Result<()> { + let harness = ::libtest2_harness::Harness::new(); + let harness = harness.with_env()?; + let harness = match harness.parse() { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + ::std::process::exit(1); + } + }; let tests = collect_tests()?; - let mut harness = libtest2::Harness::with_env(); - harness.discover(tests); - harness.main() + let harness = harness.discover(tests)?; + if !harness.run()? { + std::process::exit(libtest2_harness::ERROR_EXIT_CODE) + } + + Ok(()) } /// Creates one test for each `.rs` file in the current directory or diff --git a/crates/libtest2/src/lib.rs b/crates/libtest2/src/lib.rs index 8bab2c4..6c32f44 100644 --- a/crates/libtest2/src/lib.rs +++ b/crates/libtest2/src/lib.rs @@ -12,16 +12,23 @@ //! harness = false //! ``` //! -//! And in `tests/mytest.rs` you would call [`Harness::main`] in the `main` function: +//! And in `tests/mytest.rs` you would call [`libtest2_main`], passing it each of your tests: //! //! ```no_run -//! libtest2::Harness::with_env() -//! .main(); +//! # use libtest2::RunError; +//! # use libtest2::RunResult; +//! # use libtest2::TestContext; +//! # use libtest2::libtest2_main; +//! fn check_toph(_context: &TestContext) -> RunResult { +//! Ok(()) +//! } +//! +//! libtest2_main!(check_toph); //! ``` //! #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![warn(clippy::print_stderr)] +//#![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] pub use libtest2_harness::Harness; @@ -29,6 +36,7 @@ pub use libtest2_harness::RunError; pub use libtest2_harness::RunResult; pub use libtest2_harness::TestContext; pub use libtest2_harness::TestKind; +pub use libtest2_harness::ERROR_EXIT_CODE; use libtest2_harness::Case; use libtest2_harness::Source; @@ -75,11 +83,38 @@ impl Case for Trial { macro_rules! libtest2_main { ( $( $test:path ),* $(,)*) => { fn main() { - let mut harness = ::libtest2::Harness::with_env(); - harness.discover([ - $(::libtest2::Trial::test(::std::stringify!($test), $test)),* - ]); - harness.main(); + let harness = $crate::Harness::new(); + let harness = match harness.with_env() { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + ::std::process::exit(1); + } + }; + let harness = match harness.parse() { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + ::std::process::exit(1); + } + }; + let harness = match harness.discover([ + $($crate::Trial::test(::std::stringify!($test), $test)),* + ]) { + Ok(harness) => harness, + Err(err) => { + eprintln!("{err}"); + ::std::process::exit($crate::ERROR_EXIT_CODE) + } + }; + match harness.run() { + Ok(true) => ::std::process::exit(0), + Ok(false) => ::std::process::exit($crate::ERROR_EXIT_CODE), + Err(err) => { + eprintln!("{err}"); + ::std::process::exit($crate::ERROR_EXIT_CODE) + } + } } } }