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