]> git.lizzy.rs Git - rust.git/blob - library/test/src/cli.rs
Auto merge of #103075 - SUPERCILEX:miri-metadata, r=thomcc
[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::options::{ColorConfig, Options, OutputFormat, RunIgnored};
7 use super::time::TestTimeOptions;
8 use std::io::{self, IsTerminal};
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 && io::stdout().is_terminal(),
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         .optflag(
113             "",
114             "report-time",
115             "Show execution time of each test.
116
117             Threshold values for colorized output can be configured via
118             `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` and
119             `RUST_TEST_TIME_DOCTEST` environment variables.
120
121             Expected format of environment variable is `VARIABLE=WARN_TIME,CRITICAL_TIME`.
122             Durations must be specified in milliseconds, e.g. `500,2000` means that the warn time
123             is 0.5 seconds, and the critical time is 2 seconds.
124
125             Not available for --format=terse",
126         )
127         .optflag(
128             "",
129             "ensure-time",
130             "Treat excess of the test execution time limit as error.
131
132             Threshold values for this option can be configured via
133             `RUST_TEST_TIME_UNIT`, `RUST_TEST_TIME_INTEGRATION` and
134             `RUST_TEST_TIME_DOCTEST` environment variables.
135
136             Expected format of environment variable is `VARIABLE=WARN_TIME,CRITICAL_TIME`.
137
138             `CRITICAL_TIME` here means the limit that should not be exceeded by test.
139             ",
140         )
141         .optflag("", "shuffle", "Run tests in random order")
142         .optopt(
143             "",
144             "shuffle-seed",
145             "Run tests in random order; seed the random number generator with SEED",
146             "SEED",
147         );
148     opts
149 }
150
151 fn usage(binary: &str, options: &getopts::Options) {
152     let message = format!("Usage: {binary} [OPTIONS] [FILTERS...]");
153     println!(
154         r#"{usage}
155
156 The FILTER string is tested against the name of all tests, and only those
157 tests whose names contain the filter are run. Multiple filter strings may
158 be passed, which will run all tests matching any of the filters.
159
160 By default, all tests are run in parallel. This can be altered with the
161 --test-threads flag or the RUST_TEST_THREADS environment variable when running
162 tests (set it to 1).
163
164 By default, the tests are run in alphabetical order. Use --shuffle or set
165 RUST_TEST_SHUFFLE to run the tests in random order. Pass the generated
166 "shuffle seed" to --shuffle-seed (or set RUST_TEST_SHUFFLE_SEED) to run the
167 tests in the same order again. Note that --shuffle and --shuffle-seed do not
168 affect whether the tests are run in parallel.
169
170 All tests have their standard output and standard error captured by default.
171 This can be overridden with the --nocapture flag or setting RUST_TEST_NOCAPTURE
172 environment variable to a value other than "0". Logging is not captured by default.
173
174 Test Attributes:
175
176     `#[test]`        - Indicates a function is a test to be run. This function
177                        takes no arguments.
178     `#[bench]`       - Indicates a function is a benchmark to be run. This
179                        function takes one argument (test::Bencher).
180     `#[should_panic]` - This function (also labeled with `#[test]`) will only pass if
181                         the code causes a panic (an assertion failure or panic!)
182                         A message may be provided, which the failure string must
183                         contain: #[should_panic(expected = "foo")].
184     `#[ignore]`       - When applied to a function which is already attributed as a
185                         test, then the test runner will ignore these tests during
186                         normal test runs. Running with --ignored or --include-ignored will run
187                         these tests."#,
188         usage = options.usage(&message)
189     );
190 }
191
192 /// Parses command line arguments into test options.
193 /// Returns `None` if help was requested (since we only show help message and don't run tests),
194 /// returns `Some(Err(..))` if provided arguments are incorrect,
195 /// otherwise creates a `TestOpts` object and returns it.
196 pub fn parse_opts(args: &[String]) -> Option<OptRes> {
197     // Parse matches.
198     let opts = optgroups();
199     let binary = args.get(0).map(|c| &**c).unwrap_or("...");
200     let args = args.get(1..).unwrap_or(args);
201     let matches = match opts.parse(args) {
202         Ok(m) => m,
203         Err(f) => return Some(Err(f.to_string())),
204     };
205
206     // Check if help was requested.
207     if matches.opt_present("h") {
208         // Show help and do nothing more.
209         usage(binary, &opts);
210         return None;
211     }
212
213     // Actually parse the opts.
214     let opts_result = parse_opts_impl(matches);
215
216     Some(opts_result)
217 }
218
219 // Gets the option value and checks if unstable features are enabled.
220 macro_rules! unstable_optflag {
221     ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
222         let opt = $matches.opt_present($option_name);
223         if !$allow_unstable && opt {
224             return Err(format!(
225                 "The \"{}\" flag is only accepted on the nightly compiler with -Z unstable-options",
226                 $option_name
227             ));
228         }
229
230         opt
231     }};
232 }
233
234 // Gets the option value and checks if unstable features are enabled.
235 macro_rules! unstable_optopt {
236     ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
237         let opt = $matches.opt_str($option_name);
238         if !$allow_unstable && opt.is_some() {
239             return Err(format!(
240                 "The \"{}\" option is only accepted on the nightly compiler with -Z unstable-options",
241                 $option_name
242             ));
243         }
244
245         opt
246     }};
247 }
248
249 // Implementation of `parse_opts` that doesn't care about help message
250 // and returns a `Result`.
251 fn parse_opts_impl(matches: getopts::Matches) -> OptRes {
252     let allow_unstable = get_allow_unstable(&matches)?;
253
254     // Unstable flags
255     let force_run_in_process = unstable_optflag!(matches, allow_unstable, "force-run-in-process");
256     let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic");
257     let time_options = get_time_options(&matches, allow_unstable)?;
258     let shuffle = get_shuffle(&matches, allow_unstable)?;
259     let shuffle_seed = get_shuffle_seed(&matches, allow_unstable)?;
260
261     let include_ignored = matches.opt_present("include-ignored");
262     let quiet = matches.opt_present("quiet");
263     let exact = matches.opt_present("exact");
264     let list = matches.opt_present("list");
265     let skip = matches.opt_strs("skip");
266
267     let bench_benchmarks = matches.opt_present("bench");
268     let run_tests = !bench_benchmarks || matches.opt_present("test");
269
270     let logfile = get_log_file(&matches)?;
271     let run_ignored = get_run_ignored(&matches, include_ignored)?;
272     let filters = matches.free.clone();
273     let nocapture = get_nocapture(&matches)?;
274     let test_threads = get_test_threads(&matches)?;
275     let color = get_color_config(&matches)?;
276     let format = get_format(&matches, quiet, allow_unstable)?;
277
278     let options = Options::new().display_output(matches.opt_present("show-output"));
279
280     let test_opts = TestOpts {
281         list,
282         filters,
283         filter_exact: exact,
284         force_run_in_process,
285         exclude_should_panic,
286         run_ignored,
287         run_tests,
288         bench_benchmarks,
289         logfile,
290         nocapture,
291         color,
292         format,
293         shuffle,
294         shuffle_seed,
295         test_threads,
296         skip,
297         time_options,
298         options,
299     };
300
301     Ok(test_opts)
302 }
303
304 // FIXME: Copied from librustc_ast until linkage errors are resolved. Issue #47566
305 fn is_nightly() -> bool {
306     // Whether this is a feature-staged build, i.e., on the beta or stable channel
307     let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some();
308     // Whether we should enable unstable features for bootstrapping
309     let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok();
310
311     bootstrap || !disable_unstable_features
312 }
313
314 // Gets the CLI options associated with `report-time` feature.
315 fn get_time_options(
316     matches: &getopts::Matches,
317     allow_unstable: bool,
318 ) -> OptPartRes<Option<TestTimeOptions>> {
319     let report_time = unstable_optflag!(matches, allow_unstable, "report-time");
320     let ensure_test_time = unstable_optflag!(matches, allow_unstable, "ensure-time");
321
322     // If `ensure-test-time` option is provided, time output is enforced,
323     // so user won't be confused if any of tests will silently fail.
324     let options = if report_time || ensure_test_time {
325         Some(TestTimeOptions::new_from_env(ensure_test_time))
326     } else {
327         None
328     };
329
330     Ok(options)
331 }
332
333 fn get_shuffle(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes<bool> {
334     let mut shuffle = unstable_optflag!(matches, allow_unstable, "shuffle");
335     if !shuffle && allow_unstable {
336         shuffle = match env::var("RUST_TEST_SHUFFLE") {
337             Ok(val) => &val != "0",
338             Err(_) => false,
339         };
340     }
341
342     Ok(shuffle)
343 }
344
345 fn get_shuffle_seed(matches: &getopts::Matches, allow_unstable: bool) -> OptPartRes<Option<u64>> {
346     let mut shuffle_seed = match unstable_optopt!(matches, allow_unstable, "shuffle-seed") {
347         Some(n_str) => match n_str.parse::<u64>() {
348             Ok(n) => Some(n),
349             Err(e) => {
350                 return Err(format!(
351                     "argument for --shuffle-seed must be a number \
352                      (error: {})",
353                     e
354                 ));
355             }
356         },
357         None => None,
358     };
359
360     if shuffle_seed.is_none() && allow_unstable {
361         shuffle_seed = match env::var("RUST_TEST_SHUFFLE_SEED") {
362             Ok(val) => match val.parse::<u64>() {
363                 Ok(n) => Some(n),
364                 Err(_) => panic!("RUST_TEST_SHUFFLE_SEED is `{val}`, should be a number."),
365             },
366             Err(_) => None,
367         };
368     }
369
370     Ok(shuffle_seed)
371 }
372
373 fn get_test_threads(matches: &getopts::Matches) -> OptPartRes<Option<usize>> {
374     let test_threads = match matches.opt_str("test-threads") {
375         Some(n_str) => match n_str.parse::<usize>() {
376             Ok(0) => return Err("argument for --test-threads must not be 0".to_string()),
377             Ok(n) => Some(n),
378             Err(e) => {
379                 return Err(format!(
380                     "argument for --test-threads must be a number > 0 \
381                      (error: {})",
382                     e
383                 ));
384             }
385         },
386         None => None,
387     };
388
389     Ok(test_threads)
390 }
391
392 fn get_format(
393     matches: &getopts::Matches,
394     quiet: bool,
395     allow_unstable: bool,
396 ) -> OptPartRes<OutputFormat> {
397     let format = match matches.opt_str("format").as_deref() {
398         None if quiet => OutputFormat::Terse,
399         Some("pretty") | None => OutputFormat::Pretty,
400         Some("terse") => OutputFormat::Terse,
401         Some("json") => {
402             if !allow_unstable {
403                 return Err("The \"json\" format is only accepted on the nightly compiler".into());
404             }
405             OutputFormat::Json
406         }
407         Some("junit") => {
408             if !allow_unstable {
409                 return Err("The \"junit\" format is only accepted on the nightly compiler".into());
410             }
411             OutputFormat::Junit
412         }
413         Some(v) => {
414             return Err(format!(
415                 "argument for --format must be pretty, terse, json or junit (was \
416                  {})",
417                 v
418             ));
419         }
420     };
421
422     Ok(format)
423 }
424
425 fn get_color_config(matches: &getopts::Matches) -> OptPartRes<ColorConfig> {
426     let color = match matches.opt_str("color").as_deref() {
427         Some("auto") | None => ColorConfig::AutoColor,
428         Some("always") => ColorConfig::AlwaysColor,
429         Some("never") => ColorConfig::NeverColor,
430
431         Some(v) => {
432             return Err(format!(
433                 "argument for --color must be auto, always, or never (was \
434                  {})",
435                 v
436             ));
437         }
438     };
439
440     Ok(color)
441 }
442
443 fn get_nocapture(matches: &getopts::Matches) -> OptPartRes<bool> {
444     let mut nocapture = matches.opt_present("nocapture");
445     if !nocapture {
446         nocapture = match env::var("RUST_TEST_NOCAPTURE") {
447             Ok(val) => &val != "0",
448             Err(_) => false,
449         };
450     }
451
452     Ok(nocapture)
453 }
454
455 fn get_run_ignored(matches: &getopts::Matches, include_ignored: bool) -> OptPartRes<RunIgnored> {
456     let run_ignored = match (include_ignored, matches.opt_present("ignored")) {
457         (true, true) => {
458             return Err("the options --include-ignored and --ignored are mutually exclusive".into());
459         }
460         (true, false) => RunIgnored::Yes,
461         (false, true) => RunIgnored::Only,
462         (false, false) => RunIgnored::No,
463     };
464
465     Ok(run_ignored)
466 }
467
468 fn get_allow_unstable(matches: &getopts::Matches) -> OptPartRes<bool> {
469     let mut allow_unstable = false;
470
471     if let Some(opt) = matches.opt_str("Z") {
472         if !is_nightly() {
473             return Err("the option `Z` is only accepted on the nightly compiler".into());
474         }
475
476         match &*opt {
477             "unstable-options" => {
478                 allow_unstable = true;
479             }
480             _ => {
481                 return Err("Unrecognized option to `Z`".into());
482             }
483         }
484     };
485
486     Ok(allow_unstable)
487 }
488
489 fn get_log_file(matches: &getopts::Matches) -> OptPartRes<Option<PathBuf>> {
490     let logfile = matches.opt_str("logfile").map(|s| PathBuf::from(&s));
491
492     Ok(logfile)
493 }