1#![doc = include_str!("../examples/libtest-cli.rs")]
10#![cfg_attr(docsrs, feature(doc_auto_cfg))]
13#![forbid(unsafe_code)]
14#![warn(missing_debug_implementations, elided_lifetimes_in_paths)]
15
16use lexarg::Arg;
17use lexarg_error::ErrorContext;
18
19#[derive(Debug, Default)]
23pub struct TestOpts {
24 pub list: bool,
25 pub filters: Vec<String>,
26 pub filter_exact: bool,
27 pub run_ignored: RunIgnored,
28 pub run_tests: bool,
29 pub bench_benchmarks: bool,
30 pub nocapture: bool,
31 pub color: ColorConfig,
32 pub format: OutputFormat,
33 pub test_threads: Option<std::num::NonZeroUsize>,
34 pub skip: Vec<String>,
35 pub fail_fast: bool,
39 pub options: Options,
40 pub allowed_unstable: Vec<String>,
41}
42
43#[derive(Copy, Clone, Debug, PartialEq, Eq)]
45pub enum RunIgnored {
46 Yes,
47 No,
48 Only,
50}
51
52impl Default for RunIgnored {
53 fn default() -> Self {
54 Self::No
55 }
56}
57
58#[derive(Copy, Clone, Debug)]
60pub enum ColorConfig {
61 AutoColor,
62 AlwaysColor,
63 NeverColor,
64}
65
66impl Default for ColorConfig {
67 fn default() -> Self {
68 Self::AutoColor
69 }
70}
71
72#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74pub enum OutputFormat {
75 Pretty,
77 Terse,
79 Json,
81}
82
83impl Default for OutputFormat {
84 fn default() -> Self {
85 Self::Pretty
86 }
87}
88
89#[derive(Copy, Clone, Debug, Default)]
94pub struct Options {
95 pub display_output: bool,
96 pub panic_abort: bool,
97}
98
99pub const UNSTABLE_OPTIONS: &str = "unstable-options";
100
101pub const OPTIONS_HELP: &str = r#"
102Options:
103 --include-ignored
104 Run ignored and not ignored tests
105 --ignored Run only ignored tests
106 --test Run tests and not benchmarks
107 --bench Run benchmarks instead of tests
108 --list List all tests and benchmarks
109 --nocapture don't capture stdout/stderr of each task, allow
110 printing directly
111 --test-threads n_threads
112 Number of threads used for running tests in parallel
113 --skip FILTER Skip tests whose names contain FILTER (this flag can
114 be used multiple times)
115 -q, --quiet Display one character per test instead of one line.
116 Alias to --format=terse
117 --exact Exactly match filters rather than by substring
118 --color auto|always|never
119 Configure coloring of output:
120 auto = colorize if stdout is a tty and tests are run
121 on serially (default);
122 always = always colorize output;
123 never = never colorize output;
124 --format pretty|terse|json
125 Configure formatting of output:
126 pretty = Print verbose output;
127 terse = Display one character per test;
128 json = Output a json document;
129 --show-output Show captured stdout of successful tests
130 -Z unstable-options Enable nightly-only flags:
131 unstable-options = Allow use of experimental features
132 --report-time Show execution time of each test.
133 Threshold values for colorized output can be
134 configured via
135 `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION`
136 and
137 `RUST_TEST_TIME_DOCTEST` environment variables.
138 Expected format of environment variable is
139 `VARIABLE=WARN_TIME,CRITICAL_TIME`.
140 Durations must be specified in milliseconds, e.g.
141 `500,2000` means that the warn time
142 is 0.5 seconds, and the critical time is 2 seconds.
143 Not available for --format=terse
144 --ensure-time Treat excess of the test execution time limit as
145 error.
146 Threshold values for this option can be configured via
147 `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION`
148 and
149 `RUST_TEST_TIME_DOCTEST` environment variables.
150 Expected format of environment variable is
151 `VARIABLE=WARN_TIME,CRITICAL_TIME`.
152 `CRITICAL_TIME` here means the limit that should not
153 be exceeded by test.
154"#;
155
156pub const AFTER_HELP: &str = r#"
157The FILTER string is tested against the name of all tests, and only those
158tests whose names contain the filter are run. Multiple filter strings may
159be passed, which will run all tests matching any of the filters.
160
161By default, all tests are run in parallel. This can be altered with the
162--test-threads flag or the RUST_TEST_THREADS environment variable when running
163tests (set it to 1).
164
165All tests have their standard output and standard error captured by default.
166This can be overridden with the --nocapture flag or setting RUST_TEST_NOCAPTURE
167environment variable to a value other than "0". Logging is not captured by default.
168
169Test Attributes:
170
171 `#[test]` - Indicates a function is a test to be run. This function
172 takes no arguments.
173 `#[bench]` - Indicates a function is a benchmark to be run. This
174 function takes one argument (test::Bencher).
175 `#[should_panic]` - This function (also labeled with `#[test]`) will only pass if
176 the code causes a panic (an assertion failure or panic!)
177 A message may be provided, which the failure string must
178 contain: #[should_panic(expected = "foo")].
179 `#[ignore]` - When applied to a function which is already attributed as a
180 test, then the test runner will ignore these tests during
181 normal test runs. Running with --ignored or --include-ignored will run
182 these tests.
183"#;
184
185#[derive(Debug, Default)]
189pub struct TestOptsBuilder {
190 opts: TestOpts,
191 quiet: bool,
192 format: Option<OutputFormat>,
193 include_ignored: bool,
194 ignored: bool,
195}
196
197impl TestOptsBuilder {
198 pub fn new() -> Self {
199 Default::default()
200 }
201
202 pub fn parse_next<'a>(
204 &mut self,
205 parser: &mut lexarg::Parser<'a>,
206 arg: Arg<'a>,
207 ) -> Result<Option<Arg<'a>>, ErrorContext<'a>> {
208 use lexarg::prelude::*;
209
210 match arg {
211 Long("include-ignored") => {
212 self.include_ignored = true;
213 }
214 Long("ignored") => self.ignored = true,
215 Long("test") => {
216 self.opts.run_tests = true;
217 }
218 Long("bench") => {
219 self.opts.bench_benchmarks = true;
220 }
221 Long("list") => {
222 self.opts.list = true;
223 }
224 Long("nocapture") => {
225 self.opts.nocapture = true;
226 }
227 Long("test-threads") => {
228 let test_threads = parser
229 .next_flag_value()
230 .ok_or_missing(Value(std::ffi::OsStr::new("NUM")))
231 .parse()
232 .within(arg)?;
233 self.opts.test_threads = Some(test_threads);
234 }
235 Long("skip") => {
236 let filter = parser
237 .next_flag_value()
238 .ok_or_missing(Value(std::ffi::OsStr::new("NAME")))
239 .string("NAME")
240 .within(arg)?;
241 self.opts.skip.push(filter.to_owned());
242 }
243 Long("exact") => {
244 self.opts.filter_exact = true;
245 }
246 Long("color") => {
247 let color = parser
248 .next_flag_value()
249 .ok_or_missing(Value(std::ffi::OsStr::new("WHEN")))
250 .one_of(&["auto", "always", "never"])
251 .within(arg)?;
252 self.opts.color = match color {
253 "auto" => ColorConfig::AutoColor,
254 "always" => ColorConfig::AlwaysColor,
255 "never" => ColorConfig::NeverColor,
256 _ => unreachable!("`one_of` should prevent this"),
257 };
258 }
259 Short("q") | Long("quiet") => {
260 self.format = None;
261 self.quiet = true;
262 }
263 Long("format") => {
264 self.quiet = false;
265 let format = parser
266 .next_flag_value()
267 .ok_or_missing(Value(std::ffi::OsStr::new("FORMAT")))
268 .one_of(&["pretty", "terse", "json"])
269 .within(arg)?;
270 self.format = Some(match format {
271 "pretty" => OutputFormat::Pretty,
272 "terse" => OutputFormat::Terse,
273 "json" => OutputFormat::Json,
274 _ => unreachable!("`one_of` should prevent this"),
275 });
276 }
277 Long("show-output") => {
278 self.opts.options.display_output = true;
279 }
280 Short("Z") => {
281 let feature = parser
282 .next_flag_value()
283 .ok_or_missing(Value(std::ffi::OsStr::new("FEATURE")))
284 .string("FEATURE")
285 .within(arg)?;
286 if !is_nightly() {
287 return Err(ErrorContext::msg("expected nightly compiler").unexpected(arg));
288 }
289 self.opts.allowed_unstable.push(feature.to_owned());
291 }
292 Value(filter) => {
293 let filter = filter.string("FILTER")?;
294 self.opts.filters.push(filter.to_owned());
295 }
296 _ => {
297 return Ok(Some(arg));
298 }
299 }
300 Ok(None)
301 }
302
303 pub fn finish(mut self) -> Result<TestOpts, ErrorContext<'static>> {
305 let allow_unstable_options = self
306 .opts
307 .allowed_unstable
308 .iter()
309 .any(|f| f == UNSTABLE_OPTIONS);
310
311 if !self.opts.nocapture {
312 self.opts.nocapture = match std::env::var("RUST_TEST_NOCAPTURE") {
313 Ok(val) => &val != "0",
314 Err(_) => false,
315 };
316 }
317
318 if self.format.is_some() && !allow_unstable_options {
319 return Err(ErrorContext::msg(
320 "`--format` requires `-Zunstable-options`",
321 ));
322 }
323 if let Some(format) = self.format {
324 self.opts.format = format;
325 } else if self.quiet {
326 self.opts.format = OutputFormat::Terse;
327 }
328
329 self.opts.run_tests |= !self.opts.bench_benchmarks;
330
331 self.opts.run_ignored = match (self.include_ignored, self.ignored) {
332 (true, true) => {
333 return Err(ErrorContext::msg(
334 "`--include-ignored` and `--ignored` are mutually exclusive",
335 ))
336 }
337 (true, false) => RunIgnored::Yes,
338 (false, true) => RunIgnored::Only,
339 (false, false) => RunIgnored::No,
340 };
341
342 if self.opts.test_threads.is_none() {
343 if let Ok(value) = std::env::var("RUST_TEST_THREADS") {
344 self.opts.test_threads =
345 Some(value.parse::<std::num::NonZeroUsize>().map_err(|_e| {
346 ErrorContext::msg(format!(
347 "RUST_TEST_THREADS is `{value}`, should be a positive integer."
348 ))
349 })?);
350 }
351 }
352
353 let opts = self.opts;
354 Ok(opts)
355 }
356}
357
358fn is_nightly() -> bool {
360 let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES")
362 .map(|s| s != "0")
363 .unwrap_or(false);
364 let bootstrap = std::env::var("RUSTC_BOOTSTRAP").is_ok();
366
367 bootstrap || !disable_unstable_features
368}
369
370#[doc = include_str!("../README.md")]
371#[cfg(doctest)]
372pub struct ReadmeDoctests;