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