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