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