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