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