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