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