]> git.lizzy.rs Git - rust.git/blob - src/libtest/cli.rs
Split libtest into several smaller modules
[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<Option<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     return 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 // FIXME: Copied from libsyntax until linkage errors are resolved. Issue #47566
190 fn is_nightly() -> bool {
191     // Whether this is a feature-staged build, i.e., on the beta or stable channel
192     let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some();
193     // Whether we should enable unstable features for bootstrapping
194     let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok();
195
196     bootstrap || !disable_unstable_features
197 }
198
199 // Gets the option value and checks if unstable features are enabled.
200 macro_rules! unstable_optflag {
201     ($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
202         let opt = $matches.opt_present($option_name);
203         if !$allow_unstable && opt {
204             return Some(Err(format!(
205                 "The \"{}\" flag is only accepted on the nightly compiler",
206                 $option_name
207             )));
208         }
209
210         opt
211     }};
212 }
213
214 // Gets the CLI options assotiated with `report-time` feature.
215 fn get_time_options(
216     matches: &getopts::Matches,
217     allow_unstable: bool)
218 -> Option<OptPartRes<TestTimeOptions>> {
219     let report_time = unstable_optflag!(matches, allow_unstable, "report-time");
220     let colored_opt_str = matches.opt_str("report-time");
221     let mut report_time_colored = report_time && colored_opt_str == Some("colored".into());
222     let ensure_test_time = unstable_optflag!(matches, allow_unstable, "ensure-time");
223
224     // If `ensure-test-time` option is provided, time output is enforced,
225     // so user won't be confused if any of tests will silently fail.
226     let options = if report_time || ensure_test_time {
227         if ensure_test_time && !report_time {
228             report_time_colored = true;
229         }
230         Some(TestTimeOptions::new_from_env(ensure_test_time, report_time_colored))
231     } else {
232         None
233     };
234
235     Some(Ok(options))
236 }
237
238 // Parses command line arguments into test options
239 pub fn parse_opts(args: &[String]) -> Option<OptRes> {
240     let mut allow_unstable = false;
241     let opts = optgroups();
242     let args = args.get(1..).unwrap_or(args);
243     let matches = match opts.parse(args) {
244         Ok(m) => m,
245         Err(f) => return Some(Err(f.to_string())),
246     };
247
248     if let Some(opt) = matches.opt_str("Z") {
249         if !is_nightly() {
250             return Some(Err(
251                 "the option `Z` is only accepted on the nightly compiler".into(),
252             ));
253         }
254
255         match &*opt {
256             "unstable-options" => {
257                 allow_unstable = true;
258             }
259             _ => {
260                 return Some(Err("Unrecognized option to `Z`".into()));
261             }
262         }
263     };
264
265     if matches.opt_present("h") {
266         usage(&args[0], &opts);
267         return None;
268     }
269
270     let filter = if !matches.free.is_empty() {
271         Some(matches.free[0].clone())
272     } else {
273         None
274     };
275
276     let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic");
277
278     let include_ignored = unstable_optflag!(matches, allow_unstable, "include-ignored");
279
280     let run_ignored = match (include_ignored, matches.opt_present("ignored")) {
281         (true, true) => {
282             return Some(Err(
283                 "the options --include-ignored and --ignored are mutually exclusive".into(),
284             ));
285         }
286         (true, false) => RunIgnored::Yes,
287         (false, true) => RunIgnored::Only,
288         (false, false) => RunIgnored::No,
289     };
290     let quiet = matches.opt_present("quiet");
291     let exact = matches.opt_present("exact");
292     let list = matches.opt_present("list");
293
294     let logfile = matches.opt_str("logfile");
295     let logfile = logfile.map(|s| PathBuf::from(&s));
296
297     let bench_benchmarks = matches.opt_present("bench");
298     let run_tests = !bench_benchmarks || matches.opt_present("test");
299
300     let mut nocapture = matches.opt_present("nocapture");
301     if !nocapture {
302         nocapture = match env::var("RUST_TEST_NOCAPTURE") {
303             Ok(val) => &val != "0",
304             Err(_) => false,
305         };
306     }
307
308     let time_options = match get_time_options(&matches, allow_unstable) {
309         Some(Ok(val)) => val,
310         Some(Err(e)) => return Some(Err(e)),
311         None => panic!("Unexpected output from `get_time_options`"),
312     };
313
314     let test_threads = match matches.opt_str("test-threads") {
315         Some(n_str) => match n_str.parse::<usize>() {
316             Ok(0) => return Some(Err("argument for --test-threads must not be 0".to_string())),
317             Ok(n) => Some(n),
318             Err(e) => {
319                 return Some(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     let color = match matches.opt_str("color").as_ref().map(|s| &**s) {
330         Some("auto") | None => ColorConfig::AutoColor,
331         Some("always") => ColorConfig::AlwaysColor,
332         Some("never") => ColorConfig::NeverColor,
333
334         Some(v) => {
335             return Some(Err(format!(
336                 "argument for --color must be auto, always, or never (was \
337                  {})",
338                 v
339             )));
340         }
341     };
342
343     let format = match matches.opt_str("format").as_ref().map(|s| &**s) {
344         None if quiet => OutputFormat::Terse,
345         Some("pretty") | None => OutputFormat::Pretty,
346         Some("terse") => OutputFormat::Terse,
347         Some("json") => {
348             if !allow_unstable {
349                 return Some(Err(
350                     "The \"json\" format is only accepted on the nightly compiler".into(),
351                 ));
352             }
353             OutputFormat::Json
354         }
355
356         Some(v) => {
357             return Some(Err(format!(
358                 "argument for --format must be pretty, terse, or json (was \
359                  {})",
360                 v
361             )));
362         }
363     };
364
365     let test_opts = TestOpts {
366         list,
367         filter,
368         filter_exact: exact,
369         exclude_should_panic,
370         run_ignored,
371         run_tests,
372         bench_benchmarks,
373         logfile,
374         nocapture,
375         color,
376         format,
377         test_threads,
378         skip: matches.opt_strs("skip"),
379         time_options,
380         options: Options::new().display_output(matches.opt_present("show-output")),
381     };
382
383     Some(Ok(test_opts))
384 }