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