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