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