]> git.proxmox.com Git - rustc.git/blob - src/tools/compiletest/src/main.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / src / tools / compiletest / src / main.rs
1 #![crate_name = "compiletest"]
2 // The `test` crate is the only unstable feature
3 // allowed here, just to share similar code.
4 #![feature(test)]
5
6 extern crate test;
7
8 use crate::common::{
9 expected_output_path, output_base_dir, output_relative_path, PanicStrategy, UI_EXTENSIONS,
10 };
11 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
12 use crate::util::logv;
13 use getopts::Options;
14 use std::env;
15 use std::ffi::OsString;
16 use std::fs;
17 use std::io::{self, ErrorKind};
18 use std::path::{Path, PathBuf};
19 use std::process::{Command, Stdio};
20 use std::time::SystemTime;
21 use test::ColorConfig;
22 use tracing::*;
23 use walkdir::WalkDir;
24
25 use self::header::{make_test_description, EarlyProps};
26
27 #[cfg(test)]
28 mod tests;
29
30 pub mod common;
31 pub mod errors;
32 pub mod header;
33 mod json;
34 mod raise_fd_limit;
35 mod read2;
36 pub mod runtest;
37 pub mod util;
38
39 fn main() {
40 tracing_subscriber::fmt::init();
41
42 let config = parse_config(env::args().collect());
43
44 if config.valgrind_path.is_none() && config.force_valgrind {
45 panic!("Can't find Valgrind to run Valgrind tests");
46 }
47
48 if !config.has_tidy && config.mode == Mode::Rustdoc {
49 eprintln!("warning: `tidy` is not installed; diffs will not be generated");
50 }
51
52 log_config(&config);
53 run_tests(config);
54 }
55
56 pub fn parse_config(args: Vec<String>) -> Config {
57 let mut opts = Options::new();
58 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
59 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
60 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
61 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
62 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
63 .reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
64 .reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
65 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
66 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
67 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
68 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
69 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
70 .reqopt("", "src-base", "directory to scan for test files", "PATH")
71 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
72 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
73 .reqopt(
74 "",
75 "mode",
76 "which sort of compile tests to run",
77 "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
78 | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
79 )
80 .reqopt(
81 "",
82 "suite",
83 "which suite of compile tests to run. used for nicer error reporting.",
84 "SUITE",
85 )
86 .optopt(
87 "",
88 "pass",
89 "force {check,build,run}-pass tests to this mode.",
90 "check | build | run",
91 )
92 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
93 .optflag("", "ignored", "run tests marked as ignored")
94 .optflag("", "exact", "filters match exactly")
95 .optopt(
96 "",
97 "runtool",
98 "supervisor program to run tests under \
99 (eg. emulator, valgrind)",
100 "PROGRAM",
101 )
102 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
103 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
104 .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
105 .optflag("", "verbose", "run tests verbosely, showing all output")
106 .optflag(
107 "",
108 "bless",
109 "overwrite stderr/stdout files instead of complaining about a mismatch",
110 )
111 .optflag("", "quiet", "print one character per test instead of one line")
112 .optopt("", "color", "coloring: auto, always, never", "WHEN")
113 .optopt("", "logfile", "file to log test execution to", "FILE")
114 .optopt("", "target", "the target to build for", "TARGET")
115 .optopt("", "host", "the host to build for", "HOST")
116 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
117 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
118 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
119 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
120 .optflag("", "system-llvm", "is LLVM the system LLVM")
121 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
122 .optopt("", "adb-path", "path to the android debugger", "PATH")
123 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
124 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
125 .reqopt("", "cc", "path to a C compiler", "PATH")
126 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
127 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
128 .optopt("", "ar", "path to an archiver", "PATH")
129 .optopt("", "linker", "path to a linker", "PATH")
130 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
131 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
132 .optopt("", "nodejs", "the name of nodejs", "PATH")
133 .optopt("", "npm", "the name of npm", "PATH")
134 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
135 .optopt(
136 "",
137 "compare-mode",
138 "mode describing what file the actual ui output will be compared to",
139 "COMPARE MODE",
140 )
141 .optflag(
142 "",
143 "rustfix-coverage",
144 "enable this to generate a Rustfix coverage file, which is saved in \
145 `./<build_base>/rustfix_missing_coverage.txt`",
146 )
147 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
148 .optflag("h", "help", "show this message")
149 .reqopt("", "channel", "current Rust channel", "CHANNEL");
150
151 let (argv0, args_) = args.split_first().unwrap();
152 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
153 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
154 println!("{}", opts.usage(&message));
155 println!();
156 panic!()
157 }
158
159 let matches = &match opts.parse(args_) {
160 Ok(m) => m,
161 Err(f) => panic!("{:?}", f),
162 };
163
164 if matches.opt_present("h") || matches.opt_present("help") {
165 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
166 println!("{}", opts.usage(&message));
167 println!();
168 panic!()
169 }
170
171 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
172 match m.opt_str(nm) {
173 Some(s) => PathBuf::from(&s),
174 None => panic!("no option (=path) found for {}", nm),
175 }
176 }
177
178 fn make_absolute(path: PathBuf) -> PathBuf {
179 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
180 }
181
182 let target = opt_str2(matches.opt_str("target"));
183 let android_cross_path = opt_path(matches, "android-cross-path");
184 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
185 let (gdb, gdb_version, gdb_native_rust) =
186 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
187 let (lldb_version, lldb_native_rust) = matches
188 .opt_str("lldb-version")
189 .as_deref()
190 .and_then(extract_lldb_version)
191 .map(|(v, b)| (Some(v), b))
192 .unwrap_or((None, false));
193 let color = match matches.opt_str("color").as_deref() {
194 Some("auto") | None => ColorConfig::AutoColor,
195 Some("always") => ColorConfig::AlwaysColor,
196 Some("never") => ColorConfig::NeverColor,
197 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
198 };
199 let llvm_version =
200 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
201
202 let src_base = opt_path(matches, "src-base");
203 let run_ignored = matches.opt_present("ignored");
204 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
205 let has_tidy = if mode == Mode::Rustdoc {
206 Command::new("tidy")
207 .arg("--version")
208 .stdout(Stdio::null())
209 .status()
210 .map_or(false, |status| status.success())
211 } else {
212 // Avoid spawning an external command when we know tidy won't be used.
213 false
214 };
215 Config {
216 bless: matches.opt_present("bless"),
217 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
218 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
219 rustc_path: opt_path(matches, "rustc-path"),
220 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
221 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
222 lldb_python: matches.opt_str("lldb-python").unwrap(),
223 docck_python: matches.opt_str("docck-python").unwrap(),
224 jsondocck_path: matches.opt_str("jsondocck-path"),
225 valgrind_path: matches.opt_str("valgrind-path"),
226 force_valgrind: matches.opt_present("force-valgrind"),
227 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
228 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
229 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
230 src_base,
231 build_base: opt_path(matches, "build-base"),
232 stage_id: matches.opt_str("stage-id").unwrap(),
233 mode,
234 suite: matches.opt_str("suite").unwrap(),
235 debugger: None,
236 run_ignored,
237 filters: matches.free.clone(),
238 filter_exact: matches.opt_present("exact"),
239 force_pass_mode: matches.opt_str("pass").map(|mode| {
240 mode.parse::<PassMode>()
241 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
242 }),
243 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
244 "auto" => None,
245 "always" => Some(true),
246 "never" => Some(false),
247 _ => panic!("unknown `--run` option `{}` given", mode),
248 }),
249 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
250 runtool: matches.opt_str("runtool"),
251 host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
252 target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
253 target_panic: match matches.opt_str("target-panic").as_deref() {
254 Some("unwind") | None => PanicStrategy::Unwind,
255 Some("abort") => PanicStrategy::Abort,
256 _ => panic!("unknown `--target-panic` option `{}` given", mode),
257 },
258 target,
259 host: opt_str2(matches.opt_str("host")),
260 cdb,
261 cdb_version,
262 gdb,
263 gdb_version,
264 gdb_native_rust,
265 lldb_version,
266 lldb_native_rust,
267 llvm_version,
268 system_llvm: matches.opt_present("system-llvm"),
269 android_cross_path,
270 adb_path: opt_str2(matches.opt_str("adb-path")),
271 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
272 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
273 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
274 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
275 lldb_python_dir: matches.opt_str("lldb-python-dir"),
276 verbose: matches.opt_present("verbose"),
277 quiet: matches.opt_present("quiet"),
278 color,
279 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
280 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
281 rustfix_coverage: matches.opt_present("rustfix-coverage"),
282 has_tidy,
283 channel: matches.opt_str("channel").unwrap(),
284
285 cc: matches.opt_str("cc").unwrap(),
286 cxx: matches.opt_str("cxx").unwrap(),
287 cflags: matches.opt_str("cflags").unwrap(),
288 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
289 linker: matches.opt_str("linker"),
290 llvm_components: matches.opt_str("llvm-components").unwrap(),
291 nodejs: matches.opt_str("nodejs"),
292 npm: matches.opt_str("npm"),
293
294 force_rerun: matches.opt_present("force-rerun"),
295 }
296 }
297
298 pub fn log_config(config: &Config) {
299 let c = config;
300 logv(c, "configuration:".to_string());
301 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
302 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
303 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
304 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
305 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
306 logv(c, format!("src_base: {:?}", config.src_base.display()));
307 logv(c, format!("build_base: {:?}", config.build_base.display()));
308 logv(c, format!("stage_id: {}", config.stage_id));
309 logv(c, format!("mode: {}", config.mode));
310 logv(c, format!("run_ignored: {}", config.run_ignored));
311 logv(c, format!("filters: {:?}", config.filters));
312 logv(c, format!("filter_exact: {}", config.filter_exact));
313 logv(
314 c,
315 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
316 );
317 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
318 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
319 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
320 logv(c, format!("target: {}", config.target));
321 logv(c, format!("host: {}", config.host));
322 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
323 logv(c, format!("adb_path: {:?}", config.adb_path));
324 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
325 logv(c, format!("adb_device_status: {}", config.adb_device_status));
326 logv(c, format!("ar: {}", config.ar));
327 logv(c, format!("linker: {:?}", config.linker));
328 logv(c, format!("verbose: {}", config.verbose));
329 logv(c, format!("quiet: {}", config.quiet));
330 logv(c, "\n".to_string());
331 }
332
333 pub fn opt_str(maybestr: &Option<String>) -> &str {
334 match *maybestr {
335 None => "(none)",
336 Some(ref s) => s,
337 }
338 }
339
340 pub fn opt_str2(maybestr: Option<String>) -> String {
341 match maybestr {
342 None => "(none)".to_owned(),
343 Some(s) => s,
344 }
345 }
346
347 pub fn run_tests(config: Config) {
348 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
349 if let Mode::CodegenUnits = config.mode {
350 let _ = fs::remove_dir_all("tmp/partitioning-tests");
351 }
352
353 // If we want to collect rustfix coverage information,
354 // we first make sure that the coverage file does not exist.
355 // It will be created later on.
356 if config.rustfix_coverage {
357 let mut coverage_file_path = config.build_base.clone();
358 coverage_file_path.push("rustfix_missing_coverage.txt");
359 if coverage_file_path.exists() {
360 if let Err(e) = fs::remove_file(&coverage_file_path) {
361 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
362 }
363 }
364 }
365
366 // sadly osx needs some file descriptor limits raised for running tests in
367 // parallel (especially when we have lots and lots of child processes).
368 // For context, see #8904
369 unsafe {
370 raise_fd_limit::raise_fd_limit();
371 }
372 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
373 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
374 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
375
376 // Let tests know which target they're running as
377 env::set_var("TARGET", &config.target);
378
379 let opts = test_opts(&config);
380
381 let mut configs = Vec::new();
382 if let Mode::DebugInfo = config.mode {
383 // Debugging emscripten code doesn't make sense today
384 if !config.target.contains("emscripten") {
385 configs.extend(configure_cdb(&config));
386 configs.extend(configure_gdb(&config));
387 configs.extend(configure_lldb(&config));
388 }
389 } else {
390 configs.push(config.clone());
391 };
392
393 let mut tests = Vec::new();
394 for c in &configs {
395 make_tests(c, &mut tests);
396 }
397
398 let res = test::run_tests_console(&opts, tests);
399 match res {
400 Ok(true) => {}
401 Ok(false) => {
402 // We want to report that the tests failed, but we also want to give
403 // some indication of just what tests we were running. Especially on
404 // CI, where there can be cross-compiled tests for a lot of
405 // architectures, without this critical information it can be quite
406 // easy to miss which tests failed, and as such fail to reproduce
407 // the failure locally.
408
409 eprintln!(
410 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
411 config.suite,
412 config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(),
413 config.mode,
414 config.host,
415 config.target
416 );
417
418 std::process::exit(1);
419 }
420 Err(e) => {
421 // We don't know if tests passed or not, but if there was an error
422 // during testing we don't want to just succeed (we may not have
423 // tested something), so fail.
424 //
425 // This should realistically "never" happen, so don't try to make
426 // this a pretty error message.
427 panic!("I/O failure during tests: {:?}", e);
428 }
429 }
430 }
431
432 fn configure_cdb(config: &Config) -> Option<Config> {
433 config.cdb.as_ref()?;
434
435 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
436 }
437
438 fn configure_gdb(config: &Config) -> Option<Config> {
439 config.gdb_version?;
440
441 if util::matches_env(&config.target, "msvc") {
442 return None;
443 }
444
445 if config.remote_test_client.is_some() && !config.target.contains("android") {
446 println!(
447 "WARNING: debuginfo tests are not available when \
448 testing with remote"
449 );
450 return None;
451 }
452
453 if config.target.contains("android") {
454 println!(
455 "{} debug-info test uses tcp 5039 port.\
456 please reserve it",
457 config.target
458 );
459
460 // android debug-info test uses remote debugger so, we test 1 thread
461 // at once as they're all sharing the same TCP port to communicate
462 // over.
463 //
464 // we should figure out how to lift this restriction! (run them all
465 // on different ports allocated dynamically).
466 env::set_var("RUST_TEST_THREADS", "1");
467 }
468
469 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
470 }
471
472 fn configure_lldb(config: &Config) -> Option<Config> {
473 config.lldb_python_dir.as_ref()?;
474
475 if let Some(350) = config.lldb_version {
476 println!(
477 "WARNING: The used version of LLDB (350) has a \
478 known issue that breaks debuginfo tests. See \
479 issue #32520 for more information. Skipping all \
480 LLDB-based tests!",
481 );
482 return None;
483 }
484
485 // Some older versions of LLDB seem to have problems with multiple
486 // instances running in parallel, so only run one test thread at a
487 // time.
488 env::set_var("RUST_TEST_THREADS", "1");
489
490 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
491 }
492
493 pub fn test_opts(config: &Config) -> test::TestOpts {
494 test::TestOpts {
495 exclude_should_panic: false,
496 filters: config.filters.clone(),
497 filter_exact: config.filter_exact,
498 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
499 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
500 logfile: config.logfile.clone(),
501 run_tests: true,
502 bench_benchmarks: true,
503 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
504 Ok(val) => &val != "0",
505 Err(_) => false,
506 },
507 color: config.color,
508 test_threads: None,
509 skip: vec![],
510 list: false,
511 options: test::Options::new(),
512 time_options: None,
513 force_run_in_process: false,
514 }
515 }
516
517 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
518 debug!("making tests from {:?}", config.src_base.display());
519 let inputs = common_inputs_stamp(config);
520 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
521 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
522 }
523
524 /// Returns a stamp constructed from input files common to all test cases.
525 fn common_inputs_stamp(config: &Config) -> Stamp {
526 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
527
528 let mut stamp = Stamp::from_path(&config.rustc_path);
529
530 // Relevant pretty printer files
531 let pretty_printer_files = [
532 "src/etc/rust_types.py",
533 "src/etc/gdb_load_rust_pretty_printers.py",
534 "src/etc/gdb_lookup.py",
535 "src/etc/gdb_providers.py",
536 "src/etc/lldb_batchmode.py",
537 "src/etc/lldb_lookup.py",
538 "src/etc/lldb_providers.py",
539 ];
540 for file in &pretty_printer_files {
541 let path = rust_src_dir.join(file);
542 stamp.add_path(&path);
543 }
544
545 stamp.add_dir(&config.run_lib_path);
546
547 if let Some(ref rustdoc_path) = config.rustdoc_path {
548 stamp.add_path(&rustdoc_path);
549 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
550 }
551
552 // Compiletest itself.
553 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
554
555 stamp
556 }
557
558 fn collect_tests_from_dir(
559 config: &Config,
560 dir: &Path,
561 relative_dir_path: &Path,
562 inputs: &Stamp,
563 tests: &mut Vec<test::TestDescAndFn>,
564 ) -> io::Result<()> {
565 // Ignore directories that contain a file named `compiletest-ignore-dir`.
566 if dir.join("compiletest-ignore-dir").exists() {
567 return Ok(());
568 }
569
570 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
571 let paths = TestPaths {
572 file: dir.to_path_buf(),
573 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
574 };
575 tests.extend(make_test(config, &paths, inputs));
576 return Ok(());
577 }
578
579 // If we find a test foo/bar.rs, we have to build the
580 // output directory `$build/foo` so we can write
581 // `$build/foo/bar` into it. We do this *now* in this
582 // sequential loop because otherwise, if we do it in the
583 // tests themselves, they race for the privilege of
584 // creating the directories and sometimes fail randomly.
585 let build_dir = output_relative_path(config, relative_dir_path);
586 fs::create_dir_all(&build_dir).unwrap();
587
588 // Add each `.rs` file as a test, and recurse further on any
589 // subdirectories we find, except for `aux` directories.
590 for file in fs::read_dir(dir)? {
591 let file = file?;
592 let file_path = file.path();
593 let file_name = file.file_name();
594 if is_test(&file_name) {
595 debug!("found test file: {:?}", file_path.display());
596 let paths =
597 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
598 tests.extend(make_test(config, &paths, inputs))
599 } else if file_path.is_dir() {
600 let relative_file_path = relative_dir_path.join(file.file_name());
601 if &file_name != "auxiliary" {
602 debug!("found directory: {:?}", file_path.display());
603 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
604 }
605 } else {
606 debug!("found other file/directory: {:?}", file_path.display());
607 }
608 }
609 Ok(())
610 }
611
612 /// Returns true if `file_name` looks like a proper test file name.
613 pub fn is_test(file_name: &OsString) -> bool {
614 let file_name = file_name.to_str().unwrap();
615
616 if !file_name.ends_with(".rs") {
617 return false;
618 }
619
620 // `.`, `#`, and `~` are common temp-file prefixes.
621 let invalid_prefixes = &[".", "#", "~"];
622 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
623 }
624
625 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
626 let test_path = if config.mode == Mode::RunMake {
627 // Parse directives in the Makefile
628 testpaths.file.join("Makefile")
629 } else {
630 PathBuf::from(&testpaths.file)
631 };
632 let early_props = EarlyProps::from_file(config, &test_path);
633
634 // Incremental tests are special, they inherently cannot be run in parallel.
635 // `runtest::run` will be responsible for iterating over revisions.
636 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
637 vec![None]
638 } else {
639 early_props.revisions.iter().map(Some).collect()
640 };
641 revisions
642 .into_iter()
643 .map(|revision| {
644 let src_file =
645 std::fs::File::open(&test_path).expect("open test file to parse ignores");
646 let cfg = revision.map(|v| &**v);
647 let test_name = crate::make_test_name(config, testpaths, revision);
648 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
649 // Ignore tests that already run and are up to date with respect to inputs.
650 if !config.force_rerun {
651 desc.ignore |= is_up_to_date(
652 config,
653 testpaths,
654 &early_props,
655 revision.map(|s| s.as_str()),
656 inputs,
657 );
658 }
659 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
660 })
661 .collect()
662 }
663
664 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
665 output_base_dir(config, testpaths, revision).join("stamp")
666 }
667
668 fn is_up_to_date(
669 config: &Config,
670 testpaths: &TestPaths,
671 props: &EarlyProps,
672 revision: Option<&str>,
673 inputs: &Stamp,
674 ) -> bool {
675 let stamp_name = stamp(config, testpaths, revision);
676 // Check hash.
677 let contents = match fs::read_to_string(&stamp_name) {
678 Ok(f) => f,
679 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
680 Err(_) => return false,
681 };
682 let expected_hash = runtest::compute_stamp_hash(config);
683 if contents != expected_hash {
684 return false;
685 }
686
687 // Check timestamps.
688 let mut inputs = inputs.clone();
689 // Use `add_dir` to account for run-make tests, which use their individual directory
690 inputs.add_dir(&testpaths.file);
691
692 for aux in &props.aux {
693 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
694 inputs.add_path(&path);
695 }
696
697 // UI test files.
698 for extension in UI_EXTENSIONS {
699 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
700 inputs.add_path(path);
701 }
702
703 inputs < Stamp::from_path(&stamp_name)
704 }
705
706 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
707 struct Stamp {
708 time: SystemTime,
709 }
710
711 impl Stamp {
712 fn from_path(path: &Path) -> Self {
713 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
714 stamp.add_path(path);
715 stamp
716 }
717
718 fn add_path(&mut self, path: &Path) {
719 let modified = fs::metadata(path)
720 .and_then(|metadata| metadata.modified())
721 .unwrap_or(SystemTime::UNIX_EPOCH);
722 self.time = self.time.max(modified);
723 }
724
725 fn add_dir(&mut self, path: &Path) {
726 for entry in WalkDir::new(path) {
727 let entry = entry.unwrap();
728 if entry.file_type().is_file() {
729 let modified = entry
730 .metadata()
731 .ok()
732 .and_then(|metadata| metadata.modified().ok())
733 .unwrap_or(SystemTime::UNIX_EPOCH);
734 self.time = self.time.max(modified);
735 }
736 }
737 }
738 }
739
740 fn make_test_name(
741 config: &Config,
742 testpaths: &TestPaths,
743 revision: Option<&String>,
744 ) -> test::TestName {
745 // Convert a complete path to something like
746 //
747 // ui/foo/bar/baz.rs
748 let path = PathBuf::from(config.src_base.file_name().unwrap())
749 .join(&testpaths.relative_dir)
750 .join(&testpaths.file.file_name().unwrap());
751 let debugger = match config.debugger {
752 Some(d) => format!("-{}", d),
753 None => String::new(),
754 };
755 let mode_suffix = match config.compare_mode {
756 Some(ref mode) => format!(" ({})", mode.to_str()),
757 None => String::new(),
758 };
759
760 test::DynTestName(format!(
761 "[{}{}{}] {}{}",
762 config.mode,
763 debugger,
764 mode_suffix,
765 path.display(),
766 revision.map_or("".to_string(), |rev| format!("#{}", rev))
767 ))
768 }
769
770 fn make_test_closure(
771 config: &Config,
772 testpaths: &TestPaths,
773 revision: Option<&String>,
774 ) -> test::TestFn {
775 let config = config.clone();
776 let testpaths = testpaths.clone();
777 let revision = revision.cloned();
778 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
779 }
780
781 /// Returns `true` if the given target is an Android target for the
782 /// purposes of GDB testing.
783 fn is_android_gdb_target(target: &str) -> bool {
784 matches!(
785 &target[..],
786 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
787 )
788 }
789
790 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
791 fn is_pc_windows_msvc_target(target: &str) -> bool {
792 target.ends_with("-pc-windows-msvc")
793 }
794
795 fn find_cdb(target: &str) -> Option<OsString> {
796 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
797 return None;
798 }
799
800 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
801 let cdb_arch = if cfg!(target_arch = "x86") {
802 "x86"
803 } else if cfg!(target_arch = "x86_64") {
804 "x64"
805 } else if cfg!(target_arch = "aarch64") {
806 "arm64"
807 } else if cfg!(target_arch = "arm") {
808 "arm"
809 } else {
810 return None; // No compatible CDB.exe in the Windows 10 SDK
811 };
812
813 let mut path = PathBuf::new();
814 path.push(pf86);
815 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
816 path.push(cdb_arch);
817 path.push(r"cdb.exe");
818
819 if !path.exists() {
820 return None;
821 }
822
823 Some(path.into_os_string())
824 }
825
826 /// Returns Path to CDB
827 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
828 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
829
830 let mut version = None;
831 if let Some(cdb) = cdb.as_ref() {
832 if let Ok(output) = Command::new(cdb).arg("/version").output() {
833 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
834 version = extract_cdb_version(&first_line);
835 }
836 }
837 }
838
839 (cdb, version)
840 }
841
842 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
843 // Example full_version_line: "cdb version 10.0.18362.1"
844 let version = full_version_line.rsplit(' ').next()?;
845 let mut components = version.split('.');
846 let major: u16 = components.next().unwrap().parse().unwrap();
847 let minor: u16 = components.next().unwrap().parse().unwrap();
848 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
849 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
850 Some([major, minor, patch, build])
851 }
852
853 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
854 fn analyze_gdb(
855 gdb: Option<String>,
856 target: &str,
857 android_cross_path: &PathBuf,
858 ) -> (Option<String>, Option<u32>, bool) {
859 #[cfg(not(windows))]
860 const GDB_FALLBACK: &str = "gdb";
861 #[cfg(windows)]
862 const GDB_FALLBACK: &str = "gdb.exe";
863
864 const MIN_GDB_WITH_RUST: u32 = 7011010;
865
866 let fallback_gdb = || {
867 if is_android_gdb_target(target) {
868 let mut gdb_path = match android_cross_path.to_str() {
869 Some(x) => x.to_owned(),
870 None => panic!("cannot find android cross path"),
871 };
872 gdb_path.push_str("/bin/gdb");
873 gdb_path
874 } else {
875 GDB_FALLBACK.to_owned()
876 }
877 };
878
879 let gdb = match gdb {
880 None => fallback_gdb(),
881 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
882 Some(ref s) => s.to_owned(),
883 };
884
885 let mut version_line = None;
886 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
887 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
888 version_line = Some(first_line.to_string());
889 }
890 }
891
892 let version = match version_line {
893 Some(line) => extract_gdb_version(&line),
894 None => return (None, None, false),
895 };
896
897 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
898
899 (Some(gdb), version, gdb_native_rust)
900 }
901
902 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
903 let full_version_line = full_version_line.trim();
904
905 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
906 // of the ? sections being optional
907
908 // We will parse up to 3 digits for each component, ignoring the date
909
910 // We skip text in parentheses. This avoids accidentally parsing
911 // the openSUSE version, which looks like:
912 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
913 // This particular form is documented in the GNU coding standards:
914 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
915
916 let unbracketed_part = full_version_line.split('[').next().unwrap();
917 let mut splits = unbracketed_part.trim_end().rsplit(' ');
918 let version_string = splits.next().unwrap();
919
920 let mut splits = version_string.split('.');
921 let major = splits.next().unwrap();
922 let minor = splits.next().unwrap();
923 let patch = splits.next();
924
925 let major: u32 = major.parse().unwrap();
926 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
927 None => {
928 let minor = minor.parse().unwrap();
929 let patch: u32 = match patch {
930 Some(patch) => match patch.find(not_a_digit) {
931 None => patch.parse().unwrap(),
932 Some(idx) if idx > 3 => 0,
933 Some(idx) => patch[..idx].parse().unwrap(),
934 },
935 None => 0,
936 };
937 (minor, patch)
938 }
939 // There is no patch version after minor-date (e.g. "4-2012").
940 Some(idx) => {
941 let minor = minor[..idx].parse().unwrap();
942 (minor, 0)
943 }
944 };
945
946 Some(((major * 1000) + minor) * 1000 + patch)
947 }
948
949 /// Returns (LLDB version, LLDB is rust-enabled)
950 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
951 // Extract the major LLDB version from the given version string.
952 // LLDB version strings are different for Apple and non-Apple platforms.
953 // The Apple variant looks like this:
954 //
955 // LLDB-179.5 (older versions)
956 // lldb-300.2.51 (new versions)
957 //
958 // We are only interested in the major version number, so this function
959 // will return `Some(179)` and `Some(300)` respectively.
960 //
961 // Upstream versions look like:
962 // lldb version 6.0.1
963 //
964 // There doesn't seem to be a way to correlate the Apple version
965 // with the upstream version, and since the tests were originally
966 // written against Apple versions, we make a fake Apple version by
967 // multiplying the first number by 100. This is a hack, but
968 // normally fine because the only non-Apple version we test is
969 // rust-enabled.
970
971 let full_version_line = full_version_line.trim();
972
973 if let Some(apple_ver) =
974 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
975 {
976 if let Some(idx) = apple_ver.find(not_a_digit) {
977 let version: u32 = apple_ver[..idx].parse().unwrap();
978 return Some((version, full_version_line.contains("rust-enabled")));
979 }
980 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
981 if let Some(idx) = lldb_ver.find(not_a_digit) {
982 let version: u32 = lldb_ver[..idx].parse().ok()?;
983 return Some((version * 100, full_version_line.contains("rust-enabled")));
984 }
985 }
986 None
987 }
988
989 fn not_a_digit(c: char) -> bool {
990 !c.is_digit(10)
991 }