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