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