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