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