Skip to content

Commit 0da0b57

Browse files
committed
fix(harness)!: Give callers more control over the process
1 parent ac8481f commit 0da0b57

5 files changed

Lines changed: 213 additions & 94 deletions

File tree

crates/libtest2-harness/src/harness.rs

Lines changed: 120 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,147 @@ use libtest_lexarg::OutputFormat;
22

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

5-
pub struct Harness {
6-
raw: std::io::Result<Vec<std::ffi::OsString>>,
7-
cases: Vec<Box<dyn Case>>,
8-
}
5+
pub trait HarnessState: sealed::_HarnessState_is_Sealed {}
96

10-
impl Harness {
11-
pub fn with_env() -> Self {
12-
let raw = std::env::args_os();
13-
Self::with_args(raw)
14-
}
7+
pub struct Harness<State: HarnessState> {
8+
state: State,
9+
}
1510

16-
pub fn with_args(args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
17-
let raw = expand_args(args);
18-
Self { raw, cases: vec![] }
11+
pub struct StateInitial {
12+
start: std::time::Instant,
13+
}
14+
impl HarnessState for StateInitial {}
15+
impl sealed::_HarnessState_is_Sealed for StateInitial {}
16+
17+
impl Harness<StateInitial> {
18+
pub fn new() -> Self {
19+
Self {
20+
state: StateInitial {
21+
start: std::time::Instant::now(),
22+
},
23+
}
1924
}
2025

21-
pub fn discover(&mut self, cases: impl IntoIterator<Item = impl Case + 'static>) {
22-
for case in cases {
23-
self.cases.push(Box::new(case));
24-
}
26+
pub fn with_env(self) -> std::io::Result<Harness<StateArgs>> {
27+
let raw = std::env::args_os();
28+
self.with_args(raw)
2529
}
2630

27-
pub fn main(self) -> ! {
28-
main(self.raw, self.cases)
31+
pub fn with_args(
32+
self,
33+
args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>,
34+
) -> std::io::Result<Harness<StateArgs>> {
35+
let raw = expand_args(args)?;
36+
Ok(Harness {
37+
state: StateArgs {
38+
start: self.state.start,
39+
raw,
40+
},
41+
})
2942
}
3043
}
3144

32-
const ERROR_EXIT_CODE: i32 = 101;
33-
34-
fn main(raw: std::io::Result<Vec<std::ffi::OsString>>, mut cases: Vec<Box<dyn Case>>) -> ! {
35-
let start = std::time::Instant::now();
45+
pub struct StateArgs {
46+
start: std::time::Instant,
47+
raw: Vec<std::ffi::OsString>,
48+
}
49+
impl HarnessState for StateArgs {}
50+
impl sealed::_HarnessState_is_Sealed for StateArgs {}
51+
52+
impl Harness<StateArgs> {
53+
pub fn parse(&self) -> Result<Harness<StateParsed>, cli::LexError<'_>> {
54+
let mut parser = cli::Parser::new(&self.state.raw);
55+
let opts = parse(&mut parser)?;
56+
57+
#[cfg(feature = "color")]
58+
match opts.color {
59+
libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto,
60+
libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always,
61+
libtest_lexarg::ColorConfig::NeverColor => anstream::ColorChoice::Never,
62+
}
63+
.write_global();
3664

37-
let raw = match raw {
38-
Ok(raw) => raw,
39-
Err(err) => {
65+
let notifier = notifier(&opts).unwrap_or_else(|err| {
4066
eprintln!("{err}");
4167
std::process::exit(1)
42-
}
43-
};
44-
let mut parser = cli::Parser::new(&raw);
45-
let opts = parse(&mut parser).unwrap_or_else(|err| {
46-
eprintln!("{err}");
47-
std::process::exit(1)
48-
});
68+
});
69+
70+
Ok(Harness {
71+
state: StateParsed {
72+
start: self.state.start,
73+
opts,
74+
notifier,
75+
},
76+
})
77+
}
78+
}
4979

50-
#[cfg(feature = "color")]
51-
match opts.color {
52-
libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto,
53-
libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always,
54-
libtest_lexarg::ColorConfig::NeverColor => anstream::ColorChoice::Never,
80+
pub struct StateParsed {
81+
start: std::time::Instant,
82+
opts: libtest_lexarg::TestOpts,
83+
notifier: Box<dyn notify::Notifier>,
84+
}
85+
impl HarnessState for StateParsed {}
86+
impl sealed::_HarnessState_is_Sealed for StateParsed {}
87+
88+
impl Harness<StateParsed> {
89+
pub fn discover(
90+
mut self,
91+
cases: impl IntoIterator<Item = impl Case + 'static>,
92+
) -> std::io::Result<Harness<StateDiscovered>> {
93+
let mut cases = cases
94+
.into_iter()
95+
.map(|c| Box::new(c) as Box<dyn Case>)
96+
.collect();
97+
discover(
98+
&self.state.start,
99+
&self.state.opts,
100+
&mut cases,
101+
self.state.notifier.as_mut(),
102+
)?;
103+
Ok(Harness {
104+
state: StateDiscovered {
105+
start: self.state.start,
106+
opts: self.state.opts,
107+
notifier: self.state.notifier,
108+
cases,
109+
},
110+
})
55111
}
56-
.write_global();
112+
}
57113

58-
let mut notifier = notifier(&opts).unwrap_or_else(|err| {
59-
eprintln!("{err}");
60-
std::process::exit(1)
61-
});
62-
discover(&start, &opts, &mut cases, notifier.as_mut()).unwrap_or_else(|err| {
63-
eprintln!("{err}");
64-
std::process::exit(1)
65-
});
114+
pub struct StateDiscovered {
115+
start: std::time::Instant,
116+
opts: libtest_lexarg::TestOpts,
117+
notifier: Box<dyn notify::Notifier>,
118+
cases: Vec<Box<dyn Case>>,
119+
}
120+
impl HarnessState for StateDiscovered {}
121+
impl sealed::_HarnessState_is_Sealed for StateDiscovered {}
66122

67-
if !opts.list {
68-
match run(&start, &opts, cases, notifier.as_mut()) {
69-
Ok(true) => {}
70-
Ok(false) => std::process::exit(ERROR_EXIT_CODE),
71-
Err(e) => {
72-
eprintln!("error: io error when listing tests: {e:?}");
73-
std::process::exit(ERROR_EXIT_CODE)
74-
}
123+
impl Harness<StateDiscovered> {
124+
pub fn run(mut self) -> std::io::Result<bool> {
125+
if self.state.opts.list {
126+
Ok(true)
127+
} else {
128+
run(
129+
&self.state.start,
130+
&self.state.opts,
131+
self.state.cases,
132+
self.state.notifier.as_mut(),
133+
)
75134
}
76135
}
136+
}
77137

78-
std::process::exit(0)
138+
mod sealed {
139+
#[allow(unnameable_types)]
140+
#[allow(non_camel_case_types)]
141+
pub trait _HarnessState_is_Sealed {}
79142
}
80143

144+
pub const ERROR_EXIT_CODE: i32 = 101;
145+
81146
fn parse<'p>(parser: &mut cli::Parser<'p>) -> Result<libtest_lexarg::TestOpts, cli::LexError<'p>> {
82147
let mut test_opts = libtest_lexarg::TestOptsBuilder::new();
83148

crates/libtest2-harness/src/lib.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,4 @@
11
//! An experimental replacement for the core of libtest
2-
//!
3-
//! # Usage
4-
//!
5-
//! To use this, you most likely want to add a manual `[[test]]` section to
6-
//! `Cargo.toml` and set `harness = false`. For example:
7-
//!
8-
//! ```toml
9-
//! [[test]]
10-
//! name = "mytest"
11-
//! path = "tests/mytest.rs"
12-
//! harness = false
13-
//! ```
14-
//!
15-
//! And in `tests/mytest.rs` you would call [`Harness::main`] in the `main` function:
16-
//!
17-
//! ```no_run
18-
//! libtest2_harness::Harness::with_env()
19-
//! .main();
20-
//! ```
21-
//!
222
233
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
244
// #![warn(clippy::print_stderr)]

crates/libtest2-mimic/src/lib.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,57 @@
2727
pub use libtest_json::RunMode;
2828

2929
pub struct Harness {
30-
harness: libtest2_harness::Harness,
30+
raw: Vec<std::ffi::OsString>,
31+
cases: Vec<Trial>,
3132
}
3233

3334
impl Harness {
3435
pub fn with_args(args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
3536
Self {
36-
harness: libtest2_harness::Harness::with_args(args),
37+
raw: args.into_iter().map(|a| a.into()).collect(),
38+
cases: Vec::new(),
3739
}
3840
}
3941

4042
pub fn with_env() -> Self {
41-
Self {
42-
harness: libtest2_harness::Harness::with_env(),
43-
}
43+
let raw = std::env::args_os();
44+
Self::with_args(raw)
4445
}
4546

4647
pub fn discover(mut self, cases: impl IntoIterator<Item = Trial>) -> Self {
47-
self.harness
48-
.discover(cases.into_iter().map(|c| TrialCase { inner: c }));
48+
self.cases.extend(cases);
4949
self
5050
}
5151

5252
pub fn main(self) -> ! {
53-
self.harness.main()
53+
match self.run() {
54+
Ok(true) => std::process::exit(0),
55+
Ok(false) => std::process::exit(libtest2_harness::ERROR_EXIT_CODE),
56+
Err(err) => {
57+
eprintln!("{err}");
58+
std::process::exit(libtest2_harness::ERROR_EXIT_CODE)
59+
}
60+
}
61+
}
62+
63+
fn run(self) -> std::io::Result<bool> {
64+
let harness = libtest2_harness::Harness::new();
65+
let harness = match harness.with_args(self.raw) {
66+
Ok(harness) => harness,
67+
Err(err) => {
68+
eprintln!("{err}");
69+
std::process::exit(1);
70+
}
71+
};
72+
let harness = match harness.parse() {
73+
Ok(harness) => harness,
74+
Err(err) => {
75+
eprintln!("{err}");
76+
std::process::exit(1);
77+
}
78+
};
79+
let harness = harness.discover(self.cases.into_iter().map(|t| TrialCase { inner: t }))?;
80+
harness.run()
5481
}
5582
}
5683

crates/libtest2/examples/tidy.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@ use libtest2::RunResult;
33
use libtest2::Trial;
44

55
fn main() -> std::io::Result<()> {
6+
let harness = ::libtest2_harness::Harness::new();
7+
let harness = harness.with_env()?;
8+
let harness = match harness.parse() {
9+
Ok(harness) => harness,
10+
Err(err) => {
11+
eprintln!("{err}");
12+
::std::process::exit(1);
13+
}
14+
};
615
let tests = collect_tests()?;
7-
let mut harness = libtest2::Harness::with_env();
8-
harness.discover(tests);
9-
harness.main()
16+
let harness = harness.discover(tests)?;
17+
if !harness.run()? {
18+
std::process::exit(libtest2_harness::ERROR_EXIT_CODE)
19+
}
20+
21+
Ok(())
1022
}
1123

1224
/// Creates one test for each `.rs` file in the current directory or

crates/libtest2/src/lib.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212
//! harness = false
1313
//! ```
1414
//!
15-
//! And in `tests/mytest.rs` you would call [`Harness::main`] in the `main` function:
15+
//! And in `tests/mytest.rs` you would call [`libtest2_main`], passing it each of your tests:
1616
//!
1717
//! ```no_run
18-
//! libtest2::Harness::with_env()
19-
//! .main();
18+
//! # use libtest2::RunError;
19+
//! # use libtest2::RunResult;
20+
//! # use libtest2::TestContext;
21+
//! # use libtest2::libtest2_main;
22+
//! fn check_toph(_context: &TestContext) -> RunResult {
23+
//! Ok(())
24+
//! }
25+
//!
26+
//! libtest2_main!(check_toph);
2027
//! ```
2128
//!
2229
@@ -29,6 +36,7 @@ pub use libtest2_harness::RunError;
2936
pub use libtest2_harness::RunResult;
3037
pub use libtest2_harness::TestContext;
3138
pub use libtest2_harness::TestKind;
39+
pub use libtest2_harness::ERROR_EXIT_CODE;
3240

3341
use libtest2_harness::Case;
3442
use libtest2_harness::Source;
@@ -75,11 +83,38 @@ impl Case for Trial {
7583
macro_rules! libtest2_main {
7684
( $( $test:path ),* $(,)*) => {
7785
fn main() {
78-
let mut harness = ::libtest2::Harness::with_env();
79-
harness.discover([
80-
$(::libtest2::Trial::test(::std::stringify!($test), $test)),*
81-
]);
82-
harness.main();
86+
let harness = $crate::Harness::new();
87+
let harness = match harness.with_env() {
88+
Ok(harness) => harness,
89+
Err(err) => {
90+
eprintln!("{err}");
91+
::std::process::exit(1);
92+
}
93+
};
94+
let harness = match harness.parse() {
95+
Ok(harness) => harness,
96+
Err(err) => {
97+
eprintln!("{err}");
98+
::std::process::exit(1);
99+
}
100+
};
101+
let harness = match harness.discover([
102+
$($crate::Trial::test(::std::stringify!($test), $test)),*
103+
]) {
104+
Ok(harness) => harness,
105+
Err(err) => {
106+
eprintln!("{err}");
107+
::std::process::exit($crate::ERROR_EXIT_CODE)
108+
}
109+
};
110+
match harness.run() {
111+
Ok(true) => ::std::process::exit(0),
112+
Ok(false) => ::std::process::exit($crate::ERROR_EXIT_CODE),
113+
Err(err) => {
114+
eprintln!("{err}");
115+
::std::process::exit($crate::ERROR_EXIT_CODE)
116+
}
117+
}
83118
}
84119
}
85120
}

0 commit comments

Comments
 (0)