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