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