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