1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
24#![warn(clippy::print_stdout)]
26
27pub struct Harness {
28 raw: Vec<std::ffi::OsString>,
29 cases: Vec<Trial>,
30}
31
32impl Harness {
33 pub fn with_args(args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self {
34 Self {
35 raw: args.into_iter().map(|a| a.into()).collect(),
36 cases: Vec::new(),
37 }
38 }
39
40 pub fn with_env() -> Self {
41 let raw = std::env::args_os();
42 Self::with_args(raw)
43 }
44
45 pub fn discover(mut self, cases: impl IntoIterator<Item = Trial>) -> Self {
46 self.cases.extend(cases);
47 self
48 }
49
50 pub fn main(self) -> ! {
51 match self.run() {
52 Ok(true) => std::process::exit(0),
53 Ok(false) => std::process::exit(libtest2_harness::ERROR_EXIT_CODE),
54 Err(err) => {
55 eprintln!("{err}");
56 std::process::exit(libtest2_harness::ERROR_EXIT_CODE)
57 }
58 }
59 }
60
61 fn run(self) -> std::io::Result<bool> {
62 let harness = libtest2_harness::Harness::new();
63 let harness = match harness.with_args(self.raw) {
64 Ok(harness) => harness,
65 Err(err) => {
66 eprintln!("{err}");
67 std::process::exit(1);
68 }
69 };
70 let harness = match harness.parse() {
71 Ok(harness) => harness,
72 Err(err) => {
73 eprintln!("{err}");
74 std::process::exit(1);
75 }
76 };
77 let harness = harness.discover(self.cases.into_iter().map(|t| TrialCase { inner: t }))?;
78 harness.run()
79 }
80}
81
82pub struct Trial {
83 name: String,
84 #[allow(clippy::type_complexity)]
85 runner: Box<dyn Fn(RunContext<'_>) -> Result<(), RunError> + Send + Sync>,
86}
87
88impl Trial {
89 pub fn test(
90 name: impl Into<String>,
91 runner: impl Fn(RunContext<'_>) -> Result<(), RunError> + Send + Sync + 'static,
92 ) -> Self {
93 Self {
94 name: name.into(),
95 runner: Box::new(runner),
96 }
97 }
98}
99
100struct TrialCase {
101 inner: Trial,
102}
103
104impl libtest2_harness::Case for TrialCase {
105 fn name(&self) -> &str {
106 &self.inner.name
107 }
108 fn kind(&self) -> libtest2_harness::TestKind {
109 Default::default()
110 }
111 fn source(&self) -> Option<&libtest2_harness::Source> {
112 None
113 }
114 fn exclusive(&self, _: &libtest2_harness::TestContext) -> bool {
115 false
116 }
117
118 fn run(
119 &self,
120 context: &libtest2_harness::TestContext,
121 ) -> Result<(), libtest2_harness::RunError> {
122 (self.inner.runner)(RunContext { inner: context }).map_err(|e| e.inner)
123 }
124}
125
126pub type RunResult = Result<(), RunError>;
127
128#[derive(Debug)]
129pub struct RunError {
130 inner: libtest2_harness::RunError,
131}
132
133impl RunError {
134 pub fn with_cause(cause: impl std::error::Error + Send + Sync + 'static) -> Self {
135 Self {
136 inner: libtest2_harness::RunError::with_cause(cause),
137 }
138 }
139
140 pub fn fail(cause: impl std::fmt::Display) -> Self {
141 Self {
142 inner: libtest2_harness::RunError::fail(cause),
143 }
144 }
145}
146
147pub struct RunContext<'t> {
148 inner: &'t libtest2_harness::TestContext,
149}
150
151impl<'t> RunContext<'t> {
152 pub fn ignore(&self) -> Result<(), RunError> {
153 self.inner.ignore().map_err(|e| RunError { inner: e })
154 }
155
156 pub fn ignore_for(&self, reason: impl std::fmt::Display) -> Result<(), RunError> {
157 self.inner
158 .ignore_for(reason)
159 .map_err(|e| RunError { inner: e })
160 }
161}
162
163#[doc = include_str!("../README.md")]
164#[cfg(doctest)]
165pub struct ReadmeDoctests;