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