Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 124 additions & 53 deletions crates/libtest2-harness/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,153 @@ use libtest_lexarg::OutputFormat;

use crate::{cli, notify, Case, RunError, RunMode, TestContext};

pub struct Harness {
raw: std::io::Result<Vec<std::ffi::OsString>>,
cases: Vec<Box<dyn Case>>,
pub trait HarnessState: sealed::_HarnessState_is_Sealed {}

pub struct Harness<State: HarnessState> {
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<StateInitial> {
pub fn new() -> Self {
Self {
state: StateInitial {
start: std::time::Instant::now(),
},
}
}

pub fn with_args(args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
let raw = expand_args(args);
Self { raw, cases: vec![] }
pub fn with_env(self) -> std::io::Result<Harness<StateArgs>> {
let raw = std::env::args_os();
self.with_args(raw)
}

pub fn discover(&mut self, cases: impl IntoIterator<Item = impl Case + 'static>) {
for case in cases {
self.cases.push(Box::new(case));
}
pub fn with_args(
self,
args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>,
) -> std::io::Result<Harness<StateArgs>> {
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<StateInitial> {
fn default() -> Self {
Self::new()
}
}

const ERROR_EXIT_CODE: i32 = 101;

fn main(raw: std::io::Result<Vec<std::ffi::OsString>>, mut cases: Vec<Box<dyn Case>>) -> ! {
let start = std::time::Instant::now();
pub struct StateArgs {
start: std::time::Instant,
raw: Vec<std::ffi::OsString>,
}
impl HarnessState for StateArgs {}
impl sealed::_HarnessState_is_Sealed for StateArgs {}

impl Harness<StateArgs> {
pub fn parse(&self) -> Result<Harness<StateParsed>, 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<dyn notify::Notifier>,
}
impl HarnessState for StateParsed {}
impl sealed::_HarnessState_is_Sealed for StateParsed {}

impl Harness<StateParsed> {
pub fn discover(
mut self,
cases: impl IntoIterator<Item = impl Case + 'static>,
) -> std::io::Result<Harness<StateDiscovered>> {
let mut cases = cases
.into_iter()
.map(|c| Box::new(c) as Box<dyn Case>)
.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<dyn notify::Notifier>,
cases: Vec<Box<dyn Case>>,
}
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<StateDiscovered> {
pub fn run(mut self) -> std::io::Result<bool> {
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<libtest_lexarg::TestOpts, cli::LexError<'p>> {
let mut test_opts = libtest_lexarg::TestOptsBuilder::new();

Expand Down
20 changes: 0 additions & 20 deletions crates/libtest2-harness/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
45 changes: 36 additions & 9 deletions crates/libtest2-mimic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::ffi::OsString>,
cases: Vec<Trial>,
}

impl Harness {
pub fn with_args(args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> 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<Item = Trial>) -> 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<bool> {
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()
}
}

Expand Down
18 changes: 15 additions & 3 deletions crates/libtest2/examples/tidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading