]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
Fix font color for help button in ayu and dark themes
[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("ar".into()),
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     if config.cdb.is_none() {
365         return None;
366     }
367
368     Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
369 }
370
371 fn configure_gdb(config: &Config) -> Option<Config> {
372     if config.gdb_version.is_none() {
373         return None;
374     }
375
376     if util::matches_env(&config.target, "msvc") {
377         return None;
378     }
379
380     if config.remote_test_client.is_some() && !config.target.contains("android") {
381         println!(
382             "WARNING: debuginfo tests are not available when \
383              testing with remote"
384         );
385         return None;
386     }
387
388     if config.target.contains("android") {
389         println!(
390             "{} debug-info test uses tcp 5039 port.\
391              please reserve it",
392             config.target
393         );
394
395         // android debug-info test uses remote debugger so, we test 1 thread
396         // at once as they're all sharing the same TCP port to communicate
397         // over.
398         //
399         // we should figure out how to lift this restriction! (run them all
400         // on different ports allocated dynamically).
401         env::set_var("RUST_TEST_THREADS", "1");
402     }
403
404     Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
405 }
406
407 fn configure_lldb(config: &Config) -> Option<Config> {
408     if config.lldb_python_dir.is_none() {
409         return None;
410     }
411
412     if let Some(350) = config.lldb_version {
413         println!(
414             "WARNING: The used version of LLDB (350) has a \
415              known issue that breaks debuginfo tests. See \
416              issue #32520 for more information. Skipping all \
417              LLDB-based tests!",
418         );
419         return None;
420     }
421
422     // Some older versions of LLDB seem to have problems with multiple
423     // instances running in parallel, so only run one test thread at a
424     // time.
425     env::set_var("RUST_TEST_THREADS", "1");
426
427     Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
428 }
429
430 pub fn test_opts(config: &Config) -> test::TestOpts {
431     test::TestOpts {
432         exclude_should_panic: false,
433         filter: config.filter.clone(),
434         filter_exact: config.filter_exact,
435         run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
436         format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
437         logfile: config.logfile.clone(),
438         run_tests: true,
439         bench_benchmarks: true,
440         nocapture: match env::var("RUST_TEST_NOCAPTURE") {
441             Ok(val) => &val != "0",
442             Err(_) => false,
443         },
444         color: config.color,
445         test_threads: None,
446         skip: vec![],
447         list: false,
448         options: test::Options::new(),
449         time_options: None,
450         force_run_in_process: false,
451     }
452 }
453
454 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
455     debug!("making tests from {:?}", config.src_base.display());
456     let inputs = common_inputs_stamp(config);
457     collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
458         .expect(&format!("Could not read tests from {}", config.src_base.display()));
459 }
460
461 /// Returns a stamp constructed from input files common to all test cases.
462 fn common_inputs_stamp(config: &Config) -> Stamp {
463     let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
464
465     let mut stamp = Stamp::from_path(&config.rustc_path);
466
467     // Relevant pretty printer files
468     let pretty_printer_files = [
469         "src/etc/rust_types.py",
470         "src/etc/gdb_load_rust_pretty_printers.py",
471         "src/etc/gdb_lookup.py",
472         "src/etc/gdb_providers.py",
473         "src/etc/lldb_batchmode.py",
474         "src/etc/lldb_lookup.py",
475         "src/etc/lldb_providers.py",
476     ];
477     for file in &pretty_printer_files {
478         let path = rust_src_dir.join(file);
479         stamp.add_path(&path);
480     }
481
482     stamp.add_dir(&config.run_lib_path);
483
484     if let Some(ref rustdoc_path) = config.rustdoc_path {
485         stamp.add_path(&rustdoc_path);
486         stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
487     }
488     // FIXME(richkadel): Do I need to add an `if let Some(rust_demangler_path) contribution to the
489     // stamp here as well?
490
491     // Compiletest itself.
492     stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
493
494     stamp
495 }
496
497 fn collect_tests_from_dir(
498     config: &Config,
499     dir: &Path,
500     relative_dir_path: &Path,
501     inputs: &Stamp,
502     tests: &mut Vec<test::TestDescAndFn>,
503 ) -> io::Result<()> {
504     // Ignore directories that contain a file named `compiletest-ignore-dir`.
505     if dir.join("compiletest-ignore-dir").exists() {
506         return Ok(());
507     }
508
509     if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
510         let paths = TestPaths {
511             file: dir.to_path_buf(),
512             relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
513         };
514         tests.extend(make_test(config, &paths, inputs));
515         return Ok(());
516     }
517
518     // If we find a test foo/bar.rs, we have to build the
519     // output directory `$build/foo` so we can write
520     // `$build/foo/bar` into it. We do this *now* in this
521     // sequential loop because otherwise, if we do it in the
522     // tests themselves, they race for the privilege of
523     // creating the directories and sometimes fail randomly.
524     let build_dir = output_relative_path(config, relative_dir_path);
525     fs::create_dir_all(&build_dir).unwrap();
526
527     // Add each `.rs` file as a test, and recurse further on any
528     // subdirectories we find, except for `aux` directories.
529     for file in fs::read_dir(dir)? {
530         let file = file?;
531         let file_path = file.path();
532         let file_name = file.file_name();
533         if is_test(&file_name) {
534             debug!("found test file: {:?}", file_path.display());
535             let paths =
536                 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
537             tests.extend(make_test(config, &paths, inputs))
538         } else if file_path.is_dir() {
539             let relative_file_path = relative_dir_path.join(file.file_name());
540             if &file_name != "auxiliary" {
541                 debug!("found directory: {:?}", file_path.display());
542                 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
543             }
544         } else {
545             debug!("found other file/directory: {:?}", file_path.display());
546         }
547     }
548     Ok(())
549 }
550
551 /// Returns true if `file_name` looks like a proper test file name.
552 pub fn is_test(file_name: &OsString) -> bool {
553     let file_name = file_name.to_str().unwrap();
554
555     if !file_name.ends_with(".rs") {
556         return false;
557     }
558
559     // `.`, `#`, and `~` are common temp-file prefixes.
560     let invalid_prefixes = &[".", "#", "~"];
561     !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
562 }
563
564 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
565     let early_props = if config.mode == Mode::RunMake {
566         // Allow `ignore` directives to be in the Makefile.
567         EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
568     } else {
569         EarlyProps::from_file(config, &testpaths.file)
570     };
571
572     // The `should-fail` annotation doesn't apply to pretty tests,
573     // since we run the pretty printer across all tests by default.
574     // If desired, we could add a `should-fail-pretty` annotation.
575     let should_panic = match config.mode {
576         Pretty => test::ShouldPanic::No,
577         _ => {
578             if early_props.should_fail {
579                 test::ShouldPanic::Yes
580             } else {
581                 test::ShouldPanic::No
582             }
583         }
584     };
585
586     // Incremental tests are special, they inherently cannot be run in parallel.
587     // `runtest::run` will be responsible for iterating over revisions.
588     let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
589         vec![None]
590     } else {
591         early_props.revisions.iter().map(|r| Some(r)).collect()
592     };
593     revisions
594         .into_iter()
595         .map(|revision| {
596             let ignore = early_props.ignore
597                 // Ignore tests that already run and are up to date with respect to inputs.
598                 || is_up_to_date(
599                     config,
600                     testpaths,
601                     &early_props,
602                     revision.map(|s| s.as_str()),
603                     inputs,
604                 );
605             test::TestDescAndFn {
606                 desc: test::TestDesc {
607                     name: make_test_name(config, testpaths, revision),
608                     ignore,
609                     should_panic,
610                     allow_fail: false,
611                     test_type: test::TestType::Unknown,
612                 },
613                 testfn: make_test_closure(config, testpaths, revision),
614             }
615         })
616         .collect()
617 }
618
619 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
620     output_base_dir(config, testpaths, revision).join("stamp")
621 }
622
623 fn is_up_to_date(
624     config: &Config,
625     testpaths: &TestPaths,
626     props: &EarlyProps,
627     revision: Option<&str>,
628     inputs: &Stamp,
629 ) -> bool {
630     let stamp_name = stamp(config, testpaths, revision);
631     // Check hash.
632     let contents = match fs::read_to_string(&stamp_name) {
633         Ok(f) => f,
634         Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
635         Err(_) => return false,
636     };
637     let expected_hash = runtest::compute_stamp_hash(config);
638     if contents != expected_hash {
639         return false;
640     }
641
642     // Check timestamps.
643     let mut inputs = inputs.clone();
644     // Use `add_dir` to account for run-make tests, which use their individual directory
645     inputs.add_dir(&testpaths.file);
646
647     for aux in &props.aux {
648         let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
649         inputs.add_path(&path);
650     }
651
652     // UI test files.
653     for extension in UI_EXTENSIONS {
654         let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
655         inputs.add_path(path);
656     }
657
658     inputs < Stamp::from_path(&stamp_name)
659 }
660
661 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
662 struct Stamp {
663     time: SystemTime,
664 }
665
666 impl Stamp {
667     fn from_path(path: &Path) -> Self {
668         let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
669         stamp.add_path(path);
670         stamp
671     }
672
673     fn add_path(&mut self, path: &Path) {
674         let modified = fs::metadata(path)
675             .and_then(|metadata| metadata.modified())
676             .unwrap_or(SystemTime::UNIX_EPOCH);
677         self.time = self.time.max(modified);
678     }
679
680     fn add_dir(&mut self, path: &Path) {
681         for entry in WalkDir::new(path) {
682             let entry = entry.unwrap();
683             if entry.file_type().is_file() {
684                 let modified = entry
685                     .metadata()
686                     .ok()
687                     .and_then(|metadata| metadata.modified().ok())
688                     .unwrap_or(SystemTime::UNIX_EPOCH);
689                 self.time = self.time.max(modified);
690             }
691         }
692     }
693 }
694
695 fn make_test_name(
696     config: &Config,
697     testpaths: &TestPaths,
698     revision: Option<&String>,
699 ) -> test::TestName {
700     // Convert a complete path to something like
701     //
702     //    ui/foo/bar/baz.rs
703     let path = PathBuf::from(config.src_base.file_name().unwrap())
704         .join(&testpaths.relative_dir)
705         .join(&testpaths.file.file_name().unwrap());
706     let debugger = match config.debugger {
707         Some(d) => format!("-{}", d),
708         None => String::new(),
709     };
710     let mode_suffix = match config.compare_mode {
711         Some(ref mode) => format!(" ({})", mode.to_str()),
712         None => String::new(),
713     };
714
715     test::DynTestName(format!(
716         "[{}{}{}] {}{}",
717         config.mode,
718         debugger,
719         mode_suffix,
720         path.display(),
721         revision.map_or("".to_string(), |rev| format!("#{}", rev))
722     ))
723 }
724
725 fn make_test_closure(
726     config: &Config,
727     testpaths: &TestPaths,
728     revision: Option<&String>,
729 ) -> test::TestFn {
730     let config = config.clone();
731     let testpaths = testpaths.clone();
732     let revision = revision.cloned();
733     test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
734 }
735
736 /// Returns `true` if the given target is an Android target for the
737 /// purposes of GDB testing.
738 fn is_android_gdb_target(target: &String) -> bool {
739     match &target[..] {
740         "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
741         _ => false,
742     }
743 }
744
745 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
746 fn is_pc_windows_msvc_target(target: &String) -> bool {
747     target.ends_with("-pc-windows-msvc")
748 }
749
750 fn find_cdb(target: &String) -> Option<OsString> {
751     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
752         return None;
753     }
754
755     let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
756     let cdb_arch = if cfg!(target_arch = "x86") {
757         "x86"
758     } else if cfg!(target_arch = "x86_64") {
759         "x64"
760     } else if cfg!(target_arch = "aarch64") {
761         "arm64"
762     } else if cfg!(target_arch = "arm") {
763         "arm"
764     } else {
765         return None; // No compatible CDB.exe in the Windows 10 SDK
766     };
767
768     let mut path = PathBuf::new();
769     path.push(pf86);
770     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
771     path.push(cdb_arch);
772     path.push(r"cdb.exe");
773
774     if !path.exists() {
775         return None;
776     }
777
778     Some(path.into_os_string())
779 }
780
781 /// Returns Path to CDB
782 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
783     cdb.map(|s| OsString::from(s)).or(find_cdb(target))
784 }
785
786 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
787 fn analyze_gdb(
788     gdb: Option<String>,
789     target: &String,
790     android_cross_path: &PathBuf,
791 ) -> (Option<String>, Option<u32>, bool) {
792     #[cfg(not(windows))]
793     const GDB_FALLBACK: &str = "gdb";
794     #[cfg(windows)]
795     const GDB_FALLBACK: &str = "gdb.exe";
796
797     const MIN_GDB_WITH_RUST: u32 = 7011010;
798
799     let fallback_gdb = || {
800         if is_android_gdb_target(target) {
801             let mut gdb_path = match android_cross_path.to_str() {
802                 Some(x) => x.to_owned(),
803                 None => panic!("cannot find android cross path"),
804             };
805             gdb_path.push_str("/bin/gdb");
806             gdb_path
807         } else {
808             GDB_FALLBACK.to_owned()
809         }
810     };
811
812     let gdb = match gdb {
813         None => fallback_gdb(),
814         Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
815         Some(ref s) => s.to_owned(),
816     };
817
818     let mut version_line = None;
819     if let Ok(output) = Command::new(&gdb).arg("--version").output() {
820         if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
821             version_line = Some(first_line.to_string());
822         }
823     }
824
825     let version = match version_line {
826         Some(line) => extract_gdb_version(&line),
827         None => return (None, None, false),
828     };
829
830     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
831
832     (Some(gdb), version, gdb_native_rust)
833 }
834
835 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
836     let full_version_line = full_version_line.trim();
837
838     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
839     // of the ? sections being optional
840
841     // We will parse up to 3 digits for each component, ignoring the date
842
843     // We skip text in parentheses.  This avoids accidentally parsing
844     // the openSUSE version, which looks like:
845     //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
846     // This particular form is documented in the GNU coding standards:
847     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
848
849     let mut splits = full_version_line.rsplit(' ');
850     let version_string = splits.next().unwrap();
851
852     let mut splits = version_string.split('.');
853     let major = splits.next().unwrap();
854     let minor = splits.next().unwrap();
855     let patch = splits.next();
856
857     let major: u32 = major.parse().unwrap();
858     let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
859         None => {
860             let minor = minor.parse().unwrap();
861             let patch: u32 = match patch {
862                 Some(patch) => match patch.find(not_a_digit) {
863                     None => patch.parse().unwrap(),
864                     Some(idx) if idx > 3 => 0,
865                     Some(idx) => patch[..idx].parse().unwrap(),
866                 },
867                 None => 0,
868             };
869             (minor, patch)
870         }
871         // There is no patch version after minor-date (e.g. "4-2012").
872         Some(idx) => {
873             let minor = minor[..idx].parse().unwrap();
874             (minor, 0)
875         }
876     };
877
878     Some(((major * 1000) + minor) * 1000 + patch)
879 }
880
881 /// Returns (LLDB version, LLDB is rust-enabled)
882 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
883     // Extract the major LLDB version from the given version string.
884     // LLDB version strings are different for Apple and non-Apple platforms.
885     // The Apple variant looks like this:
886     //
887     // LLDB-179.5 (older versions)
888     // lldb-300.2.51 (new versions)
889     //
890     // We are only interested in the major version number, so this function
891     // will return `Some(179)` and `Some(300)` respectively.
892     //
893     // Upstream versions look like:
894     // lldb version 6.0.1
895     //
896     // There doesn't seem to be a way to correlate the Apple version
897     // with the upstream version, and since the tests were originally
898     // written against Apple versions, we make a fake Apple version by
899     // multiplying the first number by 100.  This is a hack, but
900     // normally fine because the only non-Apple version we test is
901     // rust-enabled.
902
903     let full_version_line = full_version_line.trim();
904
905     if let Some(apple_ver) =
906         full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
907     {
908         if let Some(idx) = apple_ver.find(not_a_digit) {
909             let version: u32 = apple_ver[..idx].parse().unwrap();
910             return Some((version, full_version_line.contains("rust-enabled")));
911         }
912     } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
913         if let Some(idx) = lldb_ver.find(not_a_digit) {
914             let version: u32 = lldb_ver[..idx].parse().unwrap();
915             return Some((version * 100, full_version_line.contains("rust-enabled")));
916         }
917     }
918     None
919 }
920
921 fn not_a_digit(c: char) -> bool {
922     !c.is_digit(10)
923 }