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