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