1use std::env;
4use std::io::{self, IsTerminal, Write};
5use std::path::PathBuf;
6
7use super::options::{ColorConfig, Options, OutputFormat, RunIgnored};
8use super::time::TestTimeOptions;
9
10#[derive(Debug)]
11pub struct TestOpts {
12 pub list: bool,
13 pub filters: Vec<String>,
14 pub filter_exact: bool,
15 pub force_run_in_process: bool,
16 pub exclude_should_panic: bool,
17 pub run_ignored: RunIgnored,
18 pub run_tests: bool,
19 pub bench_benchmarks: bool,
20 pub logfile: Option<PathBuf>,
21 pub nocapture: bool,
22 pub color: ColorConfig,
23 pub format: OutputFormat,
24 pub shuffle: bool,
25 pub shuffle_seed: Option<u64>,
26 pub test_threads: Option<usize>,
27 pub skip: Vec<String>,
28 pub time_options: Option<TestTimeOptions>,
29 pub fail_fast: bool,
33 pub options: Options,
34}
35
36impl TestOpts {
37 pub fn use_color(&self) -> bool {
38 match self.color {
39 ColorConfig::AutoColor => !self.nocapture && io::stdout().is_terminal(),
40 ColorConfig::AlwaysColor => true,
41 ColorConfig::NeverColor => false,
42 }
43 }
44}
45
46pub(crate) type OptRes = Result<TestOpts, String>;
48type OptPartRes<T> = Result<T, String>;
50
51fn optgroups() -> getopts::Options {
52 let mut opts = getopts::Options::new();
53 opts.optflag("", "include-ignored", "Run ignored and not ignored tests")
54 .optflag("", "ignored", "Run only ignored tests")
55 .optflag("", "force-run-in-process", "Forces tests to run in-process when panic=abort")
56 .optflag("", "exclude-should-panic", "Excludes tests marked as should_panic")
57 .optflag("", "test", "Run tests and not benchmarks")
58 .optflag("", "bench", "Run benchmarks instead of tests")
59 .optflag("", "list", "List all tests and benchmarks")
60 .optflag("", "fail-fast", "Don't start new tests after the first failure")
61 .optflag("h", "help", "Display this message")
62 .optopt("", "logfile", "Write logs to the specified file (deprecated)", "PATH")
63 .optflag(
64 "",
65 "no-capture",
66 "don't capture stdout/stderr of each \
67 task, allow printing directly",
68 )
69 .optopt(
70 "",
71 "test-threads",
72 "Number of threads used for running tests \
73 in parallel",
74 "n_threads",
75 )
76 .optmulti(
77 "",
78 "skip",
79 "Skip tests whose names contain FILTER (this flag can \
80 be used multiple times)",
81 "FILTER",
82 )
83 .optflag(
84 "q",
85 "quiet",
86 "Display one character per test instead of one line. \
87 Alias to --format=terse",
88 )
89 .optflag("", "exact", "Exactly match filters rather than by substring")
90 .optopt(
91 "",
92 "color",
93 "Configure coloring of output:
94 auto = colorize if stdout is a tty and tests are run on serially (default);
95 always = always colorize output;
96 never = never colorize output;",
97 "auto|always|never",
98 )
99 .optopt(
100 "",
101 "format",
102 "Configure formatting of output:
103 pretty = Print verbose output;
104 terse = Display one character per test;
105 json = Output a json document;
106 junit = Output a JUnit document",
107 "pretty|terse|json|junit",
108 )
109 .optflag("", "show-output", "Show captured stdout of successful tests")
110 .optopt(
111 "Z",
112 "",
113 "Enable nightly-only flags:
114 unstable-options = Allow use of experimental features",
115 "unstable-options",
116 )
117 .optflag(
118 "",
119 "report-time",
120 "Show execution time of each test.
121
122 Threshold values for colorized output can be configured via
123 `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` and
124 `RUST_TEST_TIME_DOCTEST` environment variables.
125
126 Expected format of environment variable is `VARIABLE=WARN_TIME,CRITICAL_TIME`.
127 Durations must be specified in milliseconds, e.g. `500,2000` means that the warn time
128 is 0.5 seconds, and the critical time is 2 seconds.
129
130 Not available for --format=terse",
131 )
132 .optflag(
133 "",
134 "ensure-time",
135 "Treat excess of the test execution time limit as error.
136
137 Threshold values for this option can be configured via
138 `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` and
139 `RUST_TEST_TIME_DOCTEST` environment variables.
140
141 Expected format of environment variable is `VARIABLE=WARN_TIME,CRITICAL_TIME`.
142
143 `CRITICAL_TIME` here means the limit that should not be exceeded by test.
144 ",
145 )
146 .optflag("", "shuffle", "Run tests in random order")
147 .optopt(
148 "",
149 "shuffle-seed",
150 "Run tests in random order; seed the random number generator with SEED",
151 "SEED",
152 );
153 opts
154}
155
156fn usage(binary: &str, options: &getopts::Options) {
157 let message = format!("Usage: {binary} [OPTIONS] [FILTERS...]");
158 println!(
159 r#"{usage}
160
161The FILTER string is tested against the name of all tests, and only those
162tests whose names contain the filter are run. Multiple filter strings may
163be passed, which will run all tests matching any of the filters.
164
165By default, all tests are run in parallel. This can be altered with the
166--test-threads flag when running tests (set it to 1).
167
168By default, the tests are run in alphabetical order. Use --shuffle to run
169the tests in random order. Pass the generated "shuffle seed" to
170--shuffle-seed to run the tests in the same order again. Note that
171--shuffle and --shuffle-seed do not affect whether the tests are run in
172parallel.
173
174All tests have their standard output and standard error captured by default.
175This can be overridden with the --no-capture flag to a value other than "0".
176Logging is not captured by default.
177
178Test Attributes:
179
180 `#[test]` - Indicates a function is a test to be run. This function
181 takes no arguments.
182 `#[bench]` - Indicates a function is a benchmark to be run. This
183 function takes one argument (test::Bencher).
184 `#[should_panic]` - This function (also labeled with `#[test]`) will only pass if
185 the code causes a panic (an assertion failure or panic!)
186 A message may be provided, which the failure string must
187 contain: #[should_panic(expected = "foo")].
188 `#[ignore]` - When applied to a function which is already attributed as a
189 test, then the test runner will ignore these tests during
190 normal test runs. Running with --ignored or --include-ignored will run
191 these tests."#,
192 usage = options.usage(&message)
193 );
194}
195
196pub fn parse_opts(args: &[String]) -> Option<OptRes> {
201 let mut opts = optgroups();
203 opts.optflag("", "nocapture", "Deprecated, use `--no-capture`");
205
206 let binary = args.first().map(|c| &**c).unwrap_or("...");
207 let args = args.get(1..).unwrap_or(args);
208 let matches = match opts.parse(args) {
209 Ok(m) => m,
210 Err(f) => return Some(Err(f.to_string())),
211 };
212
213 if matches.opt_present("h") {
215 usage(binary, &optgroups());
217 return None;
218 }
219
220 let opts_result = parse_opts_impl(matches);
222
223 Some(opts_result)
224}
225
226macro_rules! unstable_optflag {
228 ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
229 let opt = $matches.opt_present($option_name);
230 if !$allow_unstable && opt {
231 return Err(format!(
232 "The \"{}\" flag is only accepted on the nightly compiler with -Z unstable-options",
233 $option_name
234 ));
235 }
236
237 opt
238 }};
239}
240
241macro_rules! unstable_optopt {
243 ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
244 let opt = $matches.opt_str($option_name);
245 if !$allow_unstable && opt.is_some() {
246 return Err(format!(
247 "The \"{}\" option is only accepted on the nightly compiler with -Z unstable-options",
248 $option_name
249 ));
250 }
251
252 opt
253 }};
254}
255
256fn parse_opts_impl(matches: getopts::Matches) -> OptRes {
259 let allow_unstable = get_allow_unstable(&matches)?;
260
261 let force_run_in_process = unstable_optflag!(matches, allow_unstable, "force-run-in-process");
263 let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic");
264 let fail_fast = unstable_optflag!(matches, allow_unstable, "fail-fast");
265 let time_options = get_time_options(&matches, allow_unstable)?;
266 let shuffle = get_shuffle(&matches, allow_unstable)?;
267 let shuffle_seed = get_shuffle_seed(&matches, allow_unstable)?;
268
269 let include_ignored = matches.opt_present("include-ignored");
270 let quiet = matches.opt_present("quiet");
271 let exact = matches.opt_present("exact");
272 let list = matches.opt_present("list");
273 let skip = matches.opt_strs("skip");
274
275 let bench_benchmarks = matches.opt_present("bench");
276 let run_tests = !bench_benchmarks || matches.opt_present("test");
277
278 let logfile = get_log_file(&matches)?;
279 let run_ignored = get_run_ignored(&matches, include_ignored)?;
280 let filters = matches.free.clone();
281 let nocapture = get_nocapture(&matches)?;
282 let test_threads = get_test_threads(&matches)?;
283 let color = get_color_config(&matches)?;
284 let format = get_format(&matches, quiet, allow_unstable)?;
285
286 let options = Options::new().display_output(matches.opt_present("show-output"));
287
288 if logfile.is_some() {
289 let _ = write!(io::stderr(), "warning: `--logfile` is deprecated");
290 }
291
292 let test_opts = TestOpts {
293 list,
294 filters,
295 filter_exact: exact,
296 force_run_in_process,
297 exclude_should_panic,
298 run_ignored,
299 run_tests,
300 bench_benchmarks,
301 logfile,
302 nocapture,
303 color,
304 format,
305 shuffle,
306 shuffle_seed,
307 test_threads,
308 skip,
309 time_options,
310 options,
311 fail_fast,
312 };
313
314 Ok(test_opts)
315}
316
317fn is_nightly() -> bool {
318 let enable_unstable_features = cfg!(enable_unstable_features);
320
321 let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok();
323
324 bootstrap || enable_unstable_features
325}
326
327fn get_time_options(
329 matches: &getopts::Matches,
330 allow_unstable: bool,
331) -> OptPartRes<Option<TestTimeOptions>> {
332 let report_time = unstable_optflag!(matches, allow_unstable, "report-time");
333 let ensure_test_time = unstable_optflag!(matches, allow_unstable, "ensure-time");
334
335 let options = if report_time || ensure_test_time {
338 Some(TestTimeOptions::new_from_env(ensure_test_time))
339 } else {
340 None
341 };
342
343 Ok(options)
344}
345
346fn get_shuffle(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes<bool> {
347 let mut shuffle = unstable_optflag!(matches, allow_unstable, "shuffle");
348 if !shuffle && allow_unstable {
349 shuffle = match env::var("RUST_TEST_SHUFFLE") {
350 Ok(val) => &val != "0",
351 Err(_) => false,
352 };
353 }
354
355 Ok(shuffle)
356}
357
358fn get_shuffle_seed(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes<Option<u64>> {
359 let mut shuffle_seed = match unstable_optopt!(matches, allow_unstable, "shuffle-seed") {
360 Some(n_str) => match n_str.parse::<u64>() {
361 Ok(n) => Some(n),
362 Err(e) => {
363 return Err(format!(
364 "argument for --shuffle-seed must be a number \
365 (error: {e})"
366 ));
367 }
368 },
369 None => None,
370 };
371
372 if shuffle_seed.is_none() && allow_unstable {
373 shuffle_seed = match env::var("RUST_TEST_SHUFFLE_SEED") {
374 Ok(val) => match val.parse::<u64>() {
375 Ok(n) => Some(n),
376 Err(_) => panic!("RUST_TEST_SHUFFLE_SEED is `{val}`, should be a number."),
377 },
378 Err(_) => None,
379 };
380 }
381
382 Ok(shuffle_seed)
383}
384
385fn get_test_threads(matches: &getopts::Matches) -> OptPartRes<Option<usize>> {
386 let test_threads = match matches.opt_str("test-threads") {
387 Some(n_str) => match n_str.parse::<usize>() {
388 Ok(0) => return Err("argument for --test-threads must not be 0".to_string()),
389 Ok(n) => Some(n),
390 Err(e) => {
391 return Err(format!(
392 "argument for --test-threads must be a number > 0 \
393 (error: {e})"
394 ));
395 }
396 },
397 None => None,
398 };
399
400 Ok(test_threads)
401}
402
403fn get_format(
404 matches: &getopts::Matches,
405 quiet: bool,
406 allow_unstable: bool,
407) -> OptPartRes<OutputFormat> {
408 let format = match matches.opt_str("format").as_deref() {
409 None if quiet => OutputFormat::Terse,
410 Some("pretty") | None => OutputFormat::Pretty,
411 Some("terse") => OutputFormat::Terse,
412 Some("json") => {
413 if !allow_unstable {
414 return Err("The \"json\" format is only accepted on the nightly compiler with -Z unstable-options".into());
415 }
416 OutputFormat::Json
417 }
418 Some("junit") => {
419 if !allow_unstable {
420 return Err("The \"junit\" format is only accepted on the nightly compiler with -Z unstable-options".into());
421 }
422 OutputFormat::Junit
423 }
424 Some(v) => {
425 return Err(format!(
426 "argument for --format must be pretty, terse, json or junit (was \
427 {v})"
428 ));
429 }
430 };
431
432 Ok(format)
433}
434
435fn get_color_config(matches: &getopts::Matches) -> OptPartRes<ColorConfig> {
436 let color = match matches.opt_str("color").as_deref() {
437 Some("auto") | None => ColorConfig::AutoColor,
438 Some("always") => ColorConfig::AlwaysColor,
439 Some("never") => ColorConfig::NeverColor,
440
441 Some(v) => {
442 return Err(format!(
443 "argument for --color must be auto, always, or never (was \
444 {v})"
445 ));
446 }
447 };
448
449 Ok(color)
450}
451
452fn get_nocapture(matches: &getopts::Matches) -> OptPartRes<bool> {
453 let mut nocapture = matches.opt_present("nocapture") || matches.opt_present("no-capture");
454 if !nocapture {
455 nocapture = match env::var("RUST_TEST_NOCAPTURE") {
456 Ok(val) => &val != "0",
457 Err(_) => false,
458 };
459 }
460
461 Ok(nocapture)
462}
463
464fn get_run_ignored(matches: &getopts::Matches, include_ignored: bool) -> OptPartRes<RunIgnored> {
465 let run_ignored = match (include_ignored, matches.opt_present("ignored")) {
466 (true, true) => {
467 return Err("the options --include-ignored and --ignored are mutually exclusive".into());
468 }
469 (true, false) => RunIgnored::Yes,
470 (false, true) => RunIgnored::Only,
471 (false, false) => RunIgnored::No,
472 };
473
474 Ok(run_ignored)
475}
476
477fn get_allow_unstable(matches: &getopts::Matches) -> OptPartRes<bool> {
478 let mut allow_unstable = false;
479
480 if let Some(opt) = matches.opt_str("Z") {
481 if !is_nightly() {
482 return Err("the option `Z` is only accepted on the nightly compiler".into());
483 }
484
485 match &*opt {
486 "unstable-options" => {
487 allow_unstable = true;
488 }
489 _ => {
490 return Err("Unrecognized option to `Z`".into());
491 }
492 }
493 };
494
495 Ok(allow_unstable)
496}
497
498fn get_log_file(matches: &getopts::Matches) -> OptPartRes<Option<PathBuf>> {
499 let logfile = matches.opt_str("logfile").map(|s| PathBuf::from(&s));
500
501 Ok(logfile)
502}