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