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