Skip to content

Commit 1ef5e92

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

5 files changed

Lines changed: 219 additions & 94 deletions

File tree

crates/libtest2-harness/src/harness.rs

Lines changed: 124 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,153 @@ 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>>,
5+
pub trait HarnessState: sealed::_HarnessState_is_Sealed {}
6+
7+
pub struct Harness<State: HarnessState> {
8+
state: State,
89
}
910

10-
impl Harness {
11-
pub fn with_env() -> Self {
12-
let raw = std::env::args_os();
13-
Self::with_args(raw)
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+
}
1424
}
1525

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![] }
26+
pub fn with_env(self) -> std::io::Result<Harness<StateArgs>> {
27+
let raw = std::env::args_os();
28+
self.with_args(raw)
1929
}
2030

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-
}
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+
})
2542
}
43+
}
2644

27-
pub fn main(self) -> ! {
28-
main(self.raw, self.cases)
45+
impl Default for Harness<StateInitial> {
46+
fn default() -> Self {
47+
Self::new()
2948
}
3049
}
3150

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();
51+
pub struct StateArgs {
52+
start: std::time::Instant,
53+
raw: Vec<std::ffi::OsString>,
54+
}
55+
impl HarnessState for StateArgs {}
56+
impl sealed::_HarnessState_is_Sealed for StateArgs {}
57+
58+
impl Harness<StateArgs> {
59+
pub fn parse(&self) -> Result<Harness<StateParsed>, cli::LexError<'_>> {
60+
let mut parser = cli::Parser::new(&self.state.raw);
61+
let opts = parse(&mut parser)?;
62+
63+
#[cfg(feature = "color")]
64+
match opts.color {
65+
libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto,
66+
libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always,
67+
libtest_lexarg::ColorConfig::NeverColor => anstream::ColorChoice::Never,
68+
}
69+
.write_global();
3670

37-
let raw = match raw {
38-
Ok(raw) => raw,
39-
Err(err) => {
71+
let notifier = notifier(&opts).unwrap_or_else(|err| {
4072
eprintln!("{err}");
4173
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-
});
74+
});
75+
76+
Ok(Harness {
77+
state: StateParsed {
78+
start: self.state.start,
79+
opts,
80+
notifier,
81+
},
82+
})
83+
}
84+
}
4985

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,
86+
pub struct StateParsed {
87+
start: std::time::Instant,
88+
opts: libtest_lexarg::TestOpts,
89+
notifier: Box<dyn notify::Notifier>,
90+
}
91+
impl HarnessState for StateParsed {}
92+
impl sealed::_HarnessState_is_Sealed for StateParsed {}
93+
94+
impl Harness<StateParsed> {
95+
pub fn discover(
96+
mut self,
97+
cases: impl IntoIterator<Item = impl Case + 'static>,
98+
) -> std::io::Result<Harness<StateDiscovered>> {
99+
let mut cases = cases
100+
.into_iter()
101+
.map(|c| Box::new(c) as Box<dyn Case>)
102+
.collect();
103+
discover(
104+
&self.state.start,
105+
&self.state.opts,
106+
&mut cases,
107+
self.state.notifier.as_mut(),
108+
)?;
109+
Ok(Harness {
110+
state: StateDiscovered {
111+
start: self.state.start,
112+
opts: self.state.opts,
113+
notifier: self.state.notifier,
114+
cases,
115+
},
116+
})
55117
}
56-
.write_global();
118+
}
57119

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-
});
120+
pub struct StateDiscovered {
121+
start: std::time::Instant,
122+
opts: libtest_lexarg::TestOpts,
123+
notifier: Box<dyn notify::Notifier>,
124+
cases: Vec<Box<dyn Case>>,
125+
}
126+
impl HarnessState for StateDiscovered {}
127+
impl sealed::_HarnessState_is_Sealed for StateDiscovered {}
66128

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-
}
129+
impl Harness<StateDiscovered> {
130+
pub fn run(mut self) -> std::io::Result<bool> {
131+
if self.state.opts.list {
132+
Ok(true)
133+
} else {
134+
run(
135+
&self.state.start,
136+
&self.state.opts,
137+
self.state.cases,
138+
self.state.notifier.as_mut(),
139+
)
75140
}
76141
}
142+
}
77143

78-
std::process::exit(0)
144+
mod sealed {
145+
#[allow(unnameable_types)]
146+
#[allow(non_camel_case_types)]
147+
pub trait _HarnessState_is_Sealed {}
79148
}
80149

150+
pub const ERROR_EXIT_CODE: i32 = 101;
151+
81152
fn parse<'p>(parser: &mut cli::Parser<'p>) -> Result<libtest_lexarg::TestOpts, cli::LexError<'p>> {
82153
let mut test_opts = libtest_lexarg::TestOptsBuilder::new();
83154

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: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,63 @@
2121
//!
2222
2323
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
24-
#![warn(clippy::print_stderr)]
24+
//#![warn(clippy::print_stderr)]
2525
#![warn(clippy::print_stdout)]
2626

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

0 commit comments

Comments
 (0)