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