]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
Remove extra removal from test path
[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         fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
518     }
519 }
520
521 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
522     debug!("making tests from {:?}", config.src_base.display());
523     let inputs = common_inputs_stamp(config);
524     collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
525         .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
526 }
527
528 /// Returns a stamp constructed from input files common to all test cases.
529 fn common_inputs_stamp(config: &Config) -> Stamp {
530     let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
531
532     let mut stamp = Stamp::from_path(&config.rustc_path);
533
534     // Relevant pretty printer files
535     let pretty_printer_files = [
536         "src/etc/rust_types.py",
537         "src/etc/gdb_load_rust_pretty_printers.py",
538         "src/etc/gdb_lookup.py",
539         "src/etc/gdb_providers.py",
540         "src/etc/lldb_batchmode.py",
541         "src/etc/lldb_lookup.py",
542         "src/etc/lldb_providers.py",
543     ];
544     for file in &pretty_printer_files {
545         let path = rust_src_dir.join(file);
546         stamp.add_path(&path);
547     }
548
549     stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
550
551     stamp.add_dir(&config.run_lib_path);
552
553     if let Some(ref rustdoc_path) = config.rustdoc_path {
554         stamp.add_path(&rustdoc_path);
555         stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
556     }
557
558     // Compiletest itself.
559     stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
560
561     stamp
562 }
563
564 fn collect_tests_from_dir(
565     config: &Config,
566     dir: &Path,
567     relative_dir_path: &Path,
568     inputs: &Stamp,
569     tests: &mut Vec<test::TestDescAndFn>,
570 ) -> io::Result<()> {
571     // Ignore directories that contain a file named `compiletest-ignore-dir`.
572     if dir.join("compiletest-ignore-dir").exists() {
573         return Ok(());
574     }
575
576     if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
577         let paths = TestPaths {
578             file: dir.to_path_buf(),
579             relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
580         };
581         tests.extend(make_test(config, &paths, inputs));
582         return Ok(());
583     }
584
585     // If we find a test foo/bar.rs, we have to build the
586     // output directory `$build/foo` so we can write
587     // `$build/foo/bar` into it. We do this *now* in this
588     // sequential loop because otherwise, if we do it in the
589     // tests themselves, they race for the privilege of
590     // creating the directories and sometimes fail randomly.
591     let build_dir = output_relative_path(config, relative_dir_path);
592     fs::create_dir_all(&build_dir).unwrap();
593
594     // Add each `.rs` file as a test, and recurse further on any
595     // subdirectories we find, except for `aux` directories.
596     for file in fs::read_dir(dir)? {
597         let file = file?;
598         let file_path = file.path();
599         let file_name = file.file_name();
600         if is_test(&file_name) {
601             debug!("found test file: {:?}", file_path.display());
602             let paths =
603                 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
604
605             tests.extend(make_test(config, &paths, inputs))
606         } else if file_path.is_dir() {
607             let relative_file_path = relative_dir_path.join(file.file_name());
608             if &file_name != "auxiliary" {
609                 debug!("found directory: {:?}", file_path.display());
610                 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
611             }
612         } else {
613             debug!("found other file/directory: {:?}", file_path.display());
614         }
615     }
616     Ok(())
617 }
618
619 /// Returns true if `file_name` looks like a proper test file name.
620 pub fn is_test(file_name: &OsString) -> bool {
621     let file_name = file_name.to_str().unwrap();
622
623     if !file_name.ends_with(".rs") {
624         return false;
625     }
626
627     // `.`, `#`, and `~` are common temp-file prefixes.
628     let invalid_prefixes = &[".", "#", "~"];
629     !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
630 }
631
632 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
633     let test_path = if config.mode == Mode::RunMake {
634         // Parse directives in the Makefile
635         testpaths.file.join("Makefile")
636     } else {
637         PathBuf::from(&testpaths.file)
638     };
639     let early_props = EarlyProps::from_file(config, &test_path);
640
641     // Incremental tests are special, they inherently cannot be run in parallel.
642     // `runtest::run` will be responsible for iterating over revisions.
643     let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
644         vec![None]
645     } else {
646         early_props.revisions.iter().map(Some).collect()
647     };
648     revisions
649         .into_iter()
650         .map(|revision| {
651             let src_file =
652                 std::fs::File::open(&test_path).expect("open test file to parse ignores");
653             let cfg = revision.map(|v| &**v);
654             let test_name = crate::make_test_name(config, testpaths, revision);
655             let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
656             // Ignore tests that already run and are up to date with respect to inputs.
657             if !config.force_rerun {
658                 desc.ignore |= is_up_to_date(
659                     config,
660                     testpaths,
661                     &early_props,
662                     revision.map(|s| s.as_str()),
663                     inputs,
664                 );
665             }
666             test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
667         })
668         .collect()
669 }
670
671 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
672     output_base_dir(config, testpaths, revision).join("stamp")
673 }
674
675 fn files_related_to_test(
676     config: &Config,
677     testpaths: &TestPaths,
678     props: &EarlyProps,
679     revision: Option<&str>,
680 ) -> Vec<PathBuf> {
681     let mut related = vec![];
682
683     if testpaths.file.is_dir() {
684         // run-make tests use their individual directory
685         for entry in WalkDir::new(&testpaths.file) {
686             let path = entry.unwrap().into_path();
687             if path.is_file() {
688                 related.push(path);
689             }
690         }
691     } else {
692         related.push(testpaths.file.clone());
693     }
694
695     for aux in &props.aux {
696         let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
697         related.push(path);
698     }
699
700     // UI test files.
701     for extension in UI_EXTENSIONS {
702         let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
703         related.push(path);
704     }
705
706     related
707 }
708
709 fn is_up_to_date(
710     config: &Config,
711     testpaths: &TestPaths,
712     props: &EarlyProps,
713     revision: Option<&str>,
714     inputs: &Stamp,
715 ) -> bool {
716     let stamp_name = stamp(config, testpaths, revision);
717     // Check hash.
718     let contents = match fs::read_to_string(&stamp_name) {
719         Ok(f) => f,
720         Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
721         Err(_) => return false,
722     };
723     let expected_hash = runtest::compute_stamp_hash(config);
724     if contents != expected_hash {
725         return false;
726     }
727
728     // Check timestamps.
729     let mut inputs = inputs.clone();
730     for path in files_related_to_test(config, testpaths, props, revision) {
731         inputs.add_path(&path);
732     }
733
734     inputs < Stamp::from_path(&stamp_name)
735 }
736
737 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
738 struct Stamp {
739     time: SystemTime,
740 }
741
742 impl Stamp {
743     fn from_path(path: &Path) -> Self {
744         let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
745         stamp.add_path(path);
746         stamp
747     }
748
749     fn add_path(&mut self, path: &Path) {
750         let modified = fs::metadata(path)
751             .and_then(|metadata| metadata.modified())
752             .unwrap_or(SystemTime::UNIX_EPOCH);
753         self.time = self.time.max(modified);
754     }
755
756     fn add_dir(&mut self, path: &Path) {
757         for entry in WalkDir::new(path) {
758             let entry = entry.unwrap();
759             if entry.file_type().is_file() {
760                 let modified = entry
761                     .metadata()
762                     .ok()
763                     .and_then(|metadata| metadata.modified().ok())
764                     .unwrap_or(SystemTime::UNIX_EPOCH);
765                 self.time = self.time.max(modified);
766             }
767         }
768     }
769 }
770
771 fn make_test_name(
772     config: &Config,
773     testpaths: &TestPaths,
774     revision: Option<&String>,
775 ) -> test::TestName {
776     // Print the name of the file, relative to the repository root.
777     // `src_base` looks like `/path/to/rust/tests/ui`
778     let root_directory = config.src_base.parent().unwrap().parent().unwrap();
779     let path = testpaths.file.strip_prefix(root_directory).unwrap();
780     let debugger = match config.debugger {
781         Some(d) => format!("-{}", d),
782         None => String::new(),
783     };
784     let mode_suffix = match config.compare_mode {
785         Some(ref mode) => format!(" ({})", mode.to_str()),
786         None => String::new(),
787     };
788
789     test::DynTestName(format!(
790         "[{}{}{}] {}{}",
791         config.mode,
792         debugger,
793         mode_suffix,
794         path.display(),
795         revision.map_or("".to_string(), |rev| format!("#{}", rev))
796     ))
797 }
798
799 fn make_test_closure(
800     config: &Config,
801     testpaths: &TestPaths,
802     revision: Option<&String>,
803 ) -> test::TestFn {
804     let config = config.clone();
805     let testpaths = testpaths.clone();
806     let revision = revision.cloned();
807     test::DynTestFn(Box::new(move || {
808         runtest::run(config, &testpaths, revision.as_deref());
809         Ok(())
810     }))
811 }
812
813 /// Returns `true` if the given target is an Android target for the
814 /// purposes of GDB testing.
815 fn is_android_gdb_target(target: &str) -> bool {
816     matches!(
817         &target[..],
818         "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
819     )
820 }
821
822 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
823 fn is_pc_windows_msvc_target(target: &str) -> bool {
824     target.ends_with("-pc-windows-msvc")
825 }
826
827 fn find_cdb(target: &str) -> Option<OsString> {
828     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
829         return None;
830     }
831
832     let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
833     let cdb_arch = if cfg!(target_arch = "x86") {
834         "x86"
835     } else if cfg!(target_arch = "x86_64") {
836         "x64"
837     } else if cfg!(target_arch = "aarch64") {
838         "arm64"
839     } else if cfg!(target_arch = "arm") {
840         "arm"
841     } else {
842         return None; // No compatible CDB.exe in the Windows 10 SDK
843     };
844
845     let mut path = PathBuf::new();
846     path.push(pf86);
847     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
848     path.push(cdb_arch);
849     path.push(r"cdb.exe");
850
851     if !path.exists() {
852         return None;
853     }
854
855     Some(path.into_os_string())
856 }
857
858 /// Returns Path to CDB
859 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
860     let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
861
862     let mut version = None;
863     if let Some(cdb) = cdb.as_ref() {
864         if let Ok(output) = Command::new(cdb).arg("/version").output() {
865             if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
866                 version = extract_cdb_version(&first_line);
867             }
868         }
869     }
870
871     (cdb, version)
872 }
873
874 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
875     // Example full_version_line: "cdb version 10.0.18362.1"
876     let version = full_version_line.rsplit(' ').next()?;
877     let mut components = version.split('.');
878     let major: u16 = components.next().unwrap().parse().unwrap();
879     let minor: u16 = components.next().unwrap().parse().unwrap();
880     let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
881     let build: u16 = components.next().unwrap_or("0").parse().unwrap();
882     Some([major, minor, patch, build])
883 }
884
885 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
886 fn analyze_gdb(
887     gdb: Option<String>,
888     target: &str,
889     android_cross_path: &PathBuf,
890 ) -> (Option<String>, Option<u32>, bool) {
891     #[cfg(not(windows))]
892     const GDB_FALLBACK: &str = "gdb";
893     #[cfg(windows)]
894     const GDB_FALLBACK: &str = "gdb.exe";
895
896     const MIN_GDB_WITH_RUST: u32 = 7011010;
897
898     let fallback_gdb = || {
899         if is_android_gdb_target(target) {
900             let mut gdb_path = match android_cross_path.to_str() {
901                 Some(x) => x.to_owned(),
902                 None => panic!("cannot find android cross path"),
903             };
904             gdb_path.push_str("/bin/gdb");
905             gdb_path
906         } else {
907             GDB_FALLBACK.to_owned()
908         }
909     };
910
911     let gdb = match gdb {
912         None => fallback_gdb(),
913         Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
914         Some(ref s) => s.to_owned(),
915     };
916
917     let mut version_line = None;
918     if let Ok(output) = Command::new(&gdb).arg("--version").output() {
919         if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
920             version_line = Some(first_line.to_string());
921         }
922     }
923
924     let version = match version_line {
925         Some(line) => extract_gdb_version(&line),
926         None => return (None, None, false),
927     };
928
929     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
930
931     (Some(gdb), version, gdb_native_rust)
932 }
933
934 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
935     let full_version_line = full_version_line.trim();
936
937     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
938     // of the ? sections being optional
939
940     // We will parse up to 3 digits for each component, ignoring the date
941
942     // We skip text in parentheses.  This avoids accidentally parsing
943     // the openSUSE version, which looks like:
944     //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
945     // This particular form is documented in the GNU coding standards:
946     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
947
948     let unbracketed_part = full_version_line.split('[').next().unwrap();
949     let mut splits = unbracketed_part.trim_end().rsplit(' ');
950     let version_string = splits.next().unwrap();
951
952     let mut splits = version_string.split('.');
953     let major = splits.next().unwrap();
954     let minor = splits.next().unwrap();
955     let patch = splits.next();
956
957     let major: u32 = major.parse().unwrap();
958     let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
959         None => {
960             let minor = minor.parse().unwrap();
961             let patch: u32 = match patch {
962                 Some(patch) => match patch.find(not_a_digit) {
963                     None => patch.parse().unwrap(),
964                     Some(idx) if idx > 3 => 0,
965                     Some(idx) => patch[..idx].parse().unwrap(),
966                 },
967                 None => 0,
968             };
969             (minor, patch)
970         }
971         // There is no patch version after minor-date (e.g. "4-2012").
972         Some(idx) => {
973             let minor = minor[..idx].parse().unwrap();
974             (minor, 0)
975         }
976     };
977
978     Some(((major * 1000) + minor) * 1000 + patch)
979 }
980
981 /// Returns (LLDB version, LLDB is rust-enabled)
982 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
983     // Extract the major LLDB version from the given version string.
984     // LLDB version strings are different for Apple and non-Apple platforms.
985     // The Apple variant looks like this:
986     //
987     // LLDB-179.5 (older versions)
988     // lldb-300.2.51 (new versions)
989     //
990     // We are only interested in the major version number, so this function
991     // will return `Some(179)` and `Some(300)` respectively.
992     //
993     // Upstream versions look like:
994     // lldb version 6.0.1
995     //
996     // There doesn't seem to be a way to correlate the Apple version
997     // with the upstream version, and since the tests were originally
998     // written against Apple versions, we make a fake Apple version by
999     // multiplying the first number by 100.  This is a hack, but
1000     // normally fine because the only non-Apple version we test is
1001     // rust-enabled.
1002
1003     let full_version_line = full_version_line.trim();
1004
1005     if let Some(apple_ver) =
1006         full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1007     {
1008         if let Some(idx) = apple_ver.find(not_a_digit) {
1009             let version: u32 = apple_ver[..idx].parse().unwrap();
1010             return Some((version, full_version_line.contains("rust-enabled")));
1011         }
1012     } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1013         if let Some(idx) = lldb_ver.find(not_a_digit) {
1014             let version: u32 = lldb_ver[..idx].parse().ok()?;
1015             return Some((version * 100, full_version_line.contains("rust-enabled")));
1016         }
1017     }
1018     None
1019 }
1020
1021 fn not_a_digit(c: char) -> bool {
1022     !c.is_digit(10)
1023 }