]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
Auto merge of #104507 - WaffleLapkin:asderefsyou, r=wesleywiser
[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::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
9 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
10 use crate::util::logv;
11 use getopts::Options;
12 use lazycell::LazyCell;
13 use std::env;
14 use std::ffi::OsString;
15 use std::fs;
16 use std::io::{self, ErrorKind};
17 use std::path::{Path, PathBuf};
18 use std::process::{Command, Stdio};
19 use std::time::SystemTime;
20 use test::ColorConfig;
21 use tracing::*;
22 use walkdir::WalkDir;
23
24 use self::header::{make_test_description, EarlyProps};
25
26 #[cfg(test)]
27 mod tests;
28
29 pub mod common;
30 pub mod compute_diff;
31 pub mod errors;
32 pub mod header;
33 mod json;
34 mod raise_fd_limit;
35 mod read2;
36 pub mod runtest;
37 pub mod util;
38
39 fn main() {
40     tracing_subscriber::fmt::init();
41
42     let config = parse_config(env::args().collect());
43
44     if config.valgrind_path.is_none() && config.force_valgrind {
45         panic!("Can't find Valgrind to run Valgrind tests");
46     }
47
48     if !config.has_tidy && config.mode == Mode::Rustdoc {
49         eprintln!("warning: `tidy` is not installed; diffs will not be generated");
50     }
51
52     log_config(&config);
53     run_tests(config);
54 }
55
56 pub fn parse_config(args: Vec<String>) -> Config {
57     let mut opts = Options::new();
58     opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
59         .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
60         .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
61         .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
62         .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
63         .reqopt("", "python", "path to python to use for doc tests", "PATH")
64         .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
65         .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
66         .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
67         .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
68         .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
69         .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
70         .reqopt("", "src-base", "directory to scan for test files", "PATH")
71         .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
72         .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "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         .optflag("", "verbose", "run tests verbosely, showing all output")
108         .optflag(
109             "",
110             "bless",
111             "overwrite stderr/stdout files instead of complaining about a mismatch",
112         )
113         .optflag("", "quiet", "print one character per test instead of one line")
114         .optopt("", "color", "coloring: auto, always, never", "WHEN")
115         .optopt("", "logfile", "file to log test execution to", "FILE")
116         .optopt("", "target", "the target to build for", "TARGET")
117         .optopt("", "host", "the host to build for", "HOST")
118         .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
119         .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
120         .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
121         .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
122         .optflag("", "system-llvm", "is LLVM the system LLVM")
123         .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
124         .optopt("", "adb-path", "path to the android debugger", "PATH")
125         .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
126         .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
127         .reqopt("", "cc", "path to a C compiler", "PATH")
128         .reqopt("", "cxx", "path to a C++ compiler", "PATH")
129         .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
130         .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
131         .optopt("", "ar", "path to an archiver", "PATH")
132         .optopt("", "linker", "path to a linker", "PATH")
133         .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
134         .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
135         .optopt("", "nodejs", "the name of nodejs", "PATH")
136         .optopt("", "npm", "the name of npm", "PATH")
137         .optopt("", "remote-test-client", "path to the remote test client", "PATH")
138         .optopt(
139             "",
140             "compare-mode",
141             "mode describing what file the actual ui output will be compared to",
142             "COMPARE MODE",
143         )
144         .optflag(
145             "",
146             "rustfix-coverage",
147             "enable this to generate a Rustfix coverage file, which is saved in \
148                 `./<build_base>/rustfix_missing_coverage.txt`",
149         )
150         .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
151         .optflag("h", "help", "show this message")
152         .reqopt("", "channel", "current Rust channel", "CHANNEL")
153         .optopt("", "edition", "default Rust edition", "EDITION");
154
155     let (argv0, args_) = args.split_first().unwrap();
156     if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
157         let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
158         println!("{}", opts.usage(&message));
159         println!();
160         panic!()
161     }
162
163     let matches = &match opts.parse(args_) {
164         Ok(m) => m,
165         Err(f) => panic!("{:?}", f),
166     };
167
168     if matches.opt_present("h") || matches.opt_present("help") {
169         let message = format!("Usage: {} [OPTIONS]  [TESTNAME...]", argv0);
170         println!("{}", opts.usage(&message));
171         println!();
172         panic!()
173     }
174
175     fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
176         match m.opt_str(nm) {
177             Some(s) => PathBuf::from(&s),
178             None => panic!("no option (=path) found for {}", nm),
179         }
180     }
181
182     fn make_absolute(path: PathBuf) -> PathBuf {
183         if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
184     }
185
186     let target = opt_str2(matches.opt_str("target"));
187     let android_cross_path = opt_path(matches, "android-cross-path");
188     let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
189     let (gdb, gdb_version, gdb_native_rust) =
190         analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
191     let (lldb_version, lldb_native_rust) = matches
192         .opt_str("lldb-version")
193         .as_deref()
194         .and_then(extract_lldb_version)
195         .map(|(v, b)| (Some(v), b))
196         .unwrap_or((None, false));
197     let color = match matches.opt_str("color").as_deref() {
198         Some("auto") | None => ColorConfig::AutoColor,
199         Some("always") => ColorConfig::AlwaysColor,
200         Some("never") => ColorConfig::NeverColor,
201         Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
202     };
203     let llvm_version =
204         matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version).or_else(
205             || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
206         );
207
208     let src_base = opt_path(matches, "src-base");
209     let run_ignored = matches.opt_present("ignored");
210     let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
211     let has_tidy = if mode == Mode::Rustdoc {
212         Command::new("tidy")
213             .arg("--version")
214             .stdout(Stdio::null())
215             .status()
216             .map_or(false, |status| status.success())
217     } else {
218         // Avoid spawning an external command when we know tidy won't be used.
219         false
220     };
221     Config {
222         bless: matches.opt_present("bless"),
223         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
224         run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
225         rustc_path: opt_path(matches, "rustc-path"),
226         rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
227         rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
228         python: matches.opt_str("python").unwrap(),
229         jsondocck_path: matches.opt_str("jsondocck-path"),
230         jsondoclint_path: matches.opt_str("jsondoclint-path"),
231         valgrind_path: matches.opt_str("valgrind-path"),
232         force_valgrind: matches.opt_present("force-valgrind"),
233         run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
234         llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
235         llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
236         src_base,
237         build_base: opt_path(matches, "build-base"),
238         sysroot_base: opt_path(matches, "sysroot-base"),
239         stage_id: matches.opt_str("stage-id").unwrap(),
240         mode,
241         suite: matches.opt_str("suite").unwrap(),
242         debugger: None,
243         run_ignored,
244         filters: matches.free.clone(),
245         skip: matches.opt_strs("skip"),
246         filter_exact: matches.opt_present("exact"),
247         force_pass_mode: matches.opt_str("pass").map(|mode| {
248             mode.parse::<PassMode>()
249                 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
250         }),
251         run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
252             "auto" => None,
253             "always" => Some(true),
254             "never" => Some(false),
255             _ => panic!("unknown `--run` option `{}` given", mode),
256         }),
257         logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
258         runtool: matches.opt_str("runtool"),
259         host_rustcflags: matches.opt_strs("host-rustcflags"),
260         target_rustcflags: matches.opt_strs("target-rustcflags"),
261         optimize_tests: matches.opt_present("optimize-tests"),
262         target,
263         host: opt_str2(matches.opt_str("host")),
264         cdb,
265         cdb_version,
266         gdb,
267         gdb_version,
268         gdb_native_rust,
269         lldb_version,
270         lldb_native_rust,
271         llvm_version,
272         system_llvm: matches.opt_present("system-llvm"),
273         android_cross_path,
274         adb_path: opt_str2(matches.opt_str("adb-path")),
275         adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
276         adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
277             && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
278             && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
279         lldb_python_dir: matches.opt_str("lldb-python-dir"),
280         verbose: matches.opt_present("verbose"),
281         quiet: matches.opt_present("quiet"),
282         color,
283         remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
284         compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
285         rustfix_coverage: matches.opt_present("rustfix-coverage"),
286         has_tidy,
287         channel: matches.opt_str("channel").unwrap(),
288         edition: matches.opt_str("edition"),
289
290         cc: matches.opt_str("cc").unwrap(),
291         cxx: matches.opt_str("cxx").unwrap(),
292         cflags: matches.opt_str("cflags").unwrap(),
293         cxxflags: matches.opt_str("cxxflags").unwrap(),
294         ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
295         linker: matches.opt_str("linker"),
296         llvm_components: matches.opt_str("llvm-components").unwrap(),
297         nodejs: matches.opt_str("nodejs"),
298         npm: matches.opt_str("npm"),
299
300         force_rerun: matches.opt_present("force-rerun"),
301
302         target_cfg: LazyCell::new(),
303     }
304 }
305
306 pub fn log_config(config: &Config) {
307     let c = config;
308     logv(c, "configuration:".to_string());
309     logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
310     logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
311     logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
312     logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
313     logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
314     logv(c, format!("src_base: {:?}", config.src_base.display()));
315     logv(c, format!("build_base: {:?}", config.build_base.display()));
316     logv(c, format!("stage_id: {}", config.stage_id));
317     logv(c, format!("mode: {}", config.mode));
318     logv(c, format!("run_ignored: {}", config.run_ignored));
319     logv(c, format!("filters: {:?}", config.filters));
320     logv(c, format!("skip: {:?}", config.skip));
321     logv(c, format!("filter_exact: {}", config.filter_exact));
322     logv(
323         c,
324         format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
325     );
326     logv(c, format!("runtool: {}", opt_str(&config.runtool)));
327     logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
328     logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
329     logv(c, format!("target: {}", config.target));
330     logv(c, format!("host: {}", config.host));
331     logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
332     logv(c, format!("adb_path: {:?}", config.adb_path));
333     logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
334     logv(c, format!("adb_device_status: {}", config.adb_device_status));
335     logv(c, format!("ar: {}", config.ar));
336     logv(c, format!("linker: {:?}", config.linker));
337     logv(c, format!("verbose: {}", config.verbose));
338     logv(c, format!("quiet: {}", config.quiet));
339     logv(c, "\n".to_string());
340 }
341
342 pub fn opt_str(maybestr: &Option<String>) -> &str {
343     match *maybestr {
344         None => "(none)",
345         Some(ref s) => s,
346     }
347 }
348
349 pub fn opt_str2(maybestr: Option<String>) -> String {
350     match maybestr {
351         None => "(none)".to_owned(),
352         Some(s) => s,
353     }
354 }
355
356 pub fn run_tests(config: Config) {
357     // If we want to collect rustfix coverage information,
358     // we first make sure that the coverage file does not exist.
359     // It will be created later on.
360     if config.rustfix_coverage {
361         let mut coverage_file_path = config.build_base.clone();
362         coverage_file_path.push("rustfix_missing_coverage.txt");
363         if coverage_file_path.exists() {
364             if let Err(e) = fs::remove_file(&coverage_file_path) {
365                 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
366             }
367         }
368     }
369
370     // sadly osx needs some file descriptor limits raised for running tests in
371     // parallel (especially when we have lots and lots of child processes).
372     // For context, see #8904
373     unsafe {
374         raise_fd_limit::raise_fd_limit();
375     }
376     // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
377     // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
378     env::set_var("__COMPAT_LAYER", "RunAsInvoker");
379
380     // Let tests know which target they're running as
381     env::set_var("TARGET", &config.target);
382
383     let opts = test_opts(&config);
384
385     let mut configs = Vec::new();
386     if let Mode::DebugInfo = config.mode {
387         // Debugging emscripten code doesn't make sense today
388         if !config.target.contains("emscripten") {
389             configs.extend(configure_cdb(&config));
390             configs.extend(configure_gdb(&config));
391             configs.extend(configure_lldb(&config));
392         }
393     } else {
394         configs.push(config.clone());
395     };
396
397     let mut tests = Vec::new();
398     for c in &configs {
399         make_tests(c, &mut tests);
400     }
401
402     tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
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 || {
807         runtest::run(config, &testpaths, revision.as_deref());
808         Ok(())
809     }))
810 }
811
812 /// Returns `true` if the given target is an Android target for the
813 /// purposes of GDB testing.
814 fn is_android_gdb_target(target: &str) -> bool {
815     matches!(
816         &target[..],
817         "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
818     )
819 }
820
821 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
822 fn is_pc_windows_msvc_target(target: &str) -> bool {
823     target.ends_with("-pc-windows-msvc")
824 }
825
826 fn find_cdb(target: &str) -> Option<OsString> {
827     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
828         return None;
829     }
830
831     let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
832     let cdb_arch = if cfg!(target_arch = "x86") {
833         "x86"
834     } else if cfg!(target_arch = "x86_64") {
835         "x64"
836     } else if cfg!(target_arch = "aarch64") {
837         "arm64"
838     } else if cfg!(target_arch = "arm") {
839         "arm"
840     } else {
841         return None; // No compatible CDB.exe in the Windows 10 SDK
842     };
843
844     let mut path = PathBuf::new();
845     path.push(pf86);
846     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
847     path.push(cdb_arch);
848     path.push(r"cdb.exe");
849
850     if !path.exists() {
851         return None;
852     }
853
854     Some(path.into_os_string())
855 }
856
857 /// Returns Path to CDB
858 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
859     let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
860
861     let mut version = None;
862     if let Some(cdb) = cdb.as_ref() {
863         if let Ok(output) = Command::new(cdb).arg("/version").output() {
864             if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
865                 version = extract_cdb_version(&first_line);
866             }
867         }
868     }
869
870     (cdb, version)
871 }
872
873 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
874     // Example full_version_line: "cdb version 10.0.18362.1"
875     let version = full_version_line.rsplit(' ').next()?;
876     let mut components = version.split('.');
877     let major: u16 = components.next().unwrap().parse().unwrap();
878     let minor: u16 = components.next().unwrap().parse().unwrap();
879     let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
880     let build: u16 = components.next().unwrap_or("0").parse().unwrap();
881     Some([major, minor, patch, build])
882 }
883
884 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
885 fn analyze_gdb(
886     gdb: Option<String>,
887     target: &str,
888     android_cross_path: &PathBuf,
889 ) -> (Option<String>, Option<u32>, bool) {
890     #[cfg(not(windows))]
891     const GDB_FALLBACK: &str = "gdb";
892     #[cfg(windows)]
893     const GDB_FALLBACK: &str = "gdb.exe";
894
895     const MIN_GDB_WITH_RUST: u32 = 7011010;
896
897     let fallback_gdb = || {
898         if is_android_gdb_target(target) {
899             let mut gdb_path = match android_cross_path.to_str() {
900                 Some(x) => x.to_owned(),
901                 None => panic!("cannot find android cross path"),
902             };
903             gdb_path.push_str("/bin/gdb");
904             gdb_path
905         } else {
906             GDB_FALLBACK.to_owned()
907         }
908     };
909
910     let gdb = match gdb {
911         None => fallback_gdb(),
912         Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
913         Some(ref s) => s.to_owned(),
914     };
915
916     let mut version_line = None;
917     if let Ok(output) = Command::new(&gdb).arg("--version").output() {
918         if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
919             version_line = Some(first_line.to_string());
920         }
921     }
922
923     let version = match version_line {
924         Some(line) => extract_gdb_version(&line),
925         None => return (None, None, false),
926     };
927
928     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
929
930     (Some(gdb), version, gdb_native_rust)
931 }
932
933 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
934     let full_version_line = full_version_line.trim();
935
936     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
937     // of the ? sections being optional
938
939     // We will parse up to 3 digits for each component, ignoring the date
940
941     // We skip text in parentheses.  This avoids accidentally parsing
942     // the openSUSE version, which looks like:
943     //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
944     // This particular form is documented in the GNU coding standards:
945     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
946
947     let unbracketed_part = full_version_line.split('[').next().unwrap();
948     let mut splits = unbracketed_part.trim_end().rsplit(' ');
949     let version_string = splits.next().unwrap();
950
951     let mut splits = version_string.split('.');
952     let major = splits.next().unwrap();
953     let minor = splits.next().unwrap();
954     let patch = splits.next();
955
956     let major: u32 = major.parse().unwrap();
957     let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
958         None => {
959             let minor = minor.parse().unwrap();
960             let patch: u32 = match patch {
961                 Some(patch) => match patch.find(not_a_digit) {
962                     None => patch.parse().unwrap(),
963                     Some(idx) if idx > 3 => 0,
964                     Some(idx) => patch[..idx].parse().unwrap(),
965                 },
966                 None => 0,
967             };
968             (minor, patch)
969         }
970         // There is no patch version after minor-date (e.g. "4-2012").
971         Some(idx) => {
972             let minor = minor[..idx].parse().unwrap();
973             (minor, 0)
974         }
975     };
976
977     Some(((major * 1000) + minor) * 1000 + patch)
978 }
979
980 /// Returns (LLDB version, LLDB is rust-enabled)
981 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
982     // Extract the major LLDB version from the given version string.
983     // LLDB version strings are different for Apple and non-Apple platforms.
984     // The Apple variant looks like this:
985     //
986     // LLDB-179.5 (older versions)
987     // lldb-300.2.51 (new versions)
988     //
989     // We are only interested in the major version number, so this function
990     // will return `Some(179)` and `Some(300)` respectively.
991     //
992     // Upstream versions look like:
993     // lldb version 6.0.1
994     //
995     // There doesn't seem to be a way to correlate the Apple version
996     // with the upstream version, and since the tests were originally
997     // written against Apple versions, we make a fake Apple version by
998     // multiplying the first number by 100.  This is a hack, but
999     // normally fine because the only non-Apple version we test is
1000     // rust-enabled.
1001
1002     let full_version_line = full_version_line.trim();
1003
1004     if let Some(apple_ver) =
1005         full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1006     {
1007         if let Some(idx) = apple_ver.find(not_a_digit) {
1008             let version: u32 = apple_ver[..idx].parse().unwrap();
1009             return Some((version, full_version_line.contains("rust-enabled")));
1010         }
1011     } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1012         if let Some(idx) = lldb_ver.find(not_a_digit) {
1013             let version: u32 = lldb_ver[..idx].parse().ok()?;
1014             return Some((version * 100, full_version_line.contains("rust-enabled")));
1015         }
1016     }
1017     None
1018 }
1019
1020 fn not_a_digit(c: char) -> bool {
1021     !c.is_digit(10)
1022 }