]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
Rollup merge of #97837 - sunfishcode:sunfishcode/proc-self-mem, r=m-ou-se
[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         .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
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);
204
205     let src_base = opt_path(matches, "src-base");
206     let run_ignored = matches.opt_present("ignored");
207     let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
208     let has_tidy = if mode == Mode::Rustdoc {
209         Command::new("tidy")
210             .arg("--version")
211             .stdout(Stdio::null())
212             .status()
213             .map_or(false, |status| status.success())
214     } else {
215         // Avoid spawning an external command when we know tidy won't be used.
216         false
217     };
218     Config {
219         bless: matches.opt_present("bless"),
220         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
221         run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
222         rustc_path: opt_path(matches, "rustc-path"),
223         rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
224         rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
225         python: matches.opt_str("python").unwrap(),
226         jsondocck_path: matches.opt_str("jsondocck-path"),
227         valgrind_path: matches.opt_str("valgrind-path"),
228         force_valgrind: matches.opt_present("force-valgrind"),
229         run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
230         llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
231         llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
232         src_base,
233         build_base: opt_path(matches, "build-base"),
234         stage_id: matches.opt_str("stage-id").unwrap(),
235         mode,
236         suite: matches.opt_str("suite").unwrap(),
237         debugger: None,
238         run_ignored,
239         filters: matches.free.clone(),
240         skip: matches.opt_strs("skip"),
241         filter_exact: matches.opt_present("exact"),
242         force_pass_mode: matches.opt_str("pass").map(|mode| {
243             mode.parse::<PassMode>()
244                 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
245         }),
246         run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
247             "auto" => None,
248             "always" => Some(true),
249             "never" => Some(false),
250             _ => panic!("unknown `--run` option `{}` given", mode),
251         }),
252         logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
253         runtool: matches.opt_str("runtool"),
254         host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
255         target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
256         target_panic: match matches.opt_str("target-panic").as_deref() {
257             Some("unwind") | None => PanicStrategy::Unwind,
258             Some("abort") => PanicStrategy::Abort,
259             _ => panic!("unknown `--target-panic` option `{}` given", mode),
260         },
261         target,
262         host: opt_str2(matches.opt_str("host")),
263         cdb,
264         cdb_version,
265         gdb,
266         gdb_version,
267         gdb_native_rust,
268         lldb_version,
269         lldb_native_rust,
270         llvm_version,
271         system_llvm: matches.opt_present("system-llvm"),
272         android_cross_path,
273         adb_path: opt_str2(matches.opt_str("adb-path")),
274         adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
275         adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
276             && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
277             && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
278         lldb_python_dir: matches.opt_str("lldb-python-dir"),
279         verbose: matches.opt_present("verbose"),
280         quiet: matches.opt_present("quiet"),
281         color,
282         remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
283         compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
284         rustfix_coverage: matches.opt_present("rustfix-coverage"),
285         has_tidy,
286         channel: matches.opt_str("channel").unwrap(),
287         edition: matches.opt_str("edition"),
288
289         cc: matches.opt_str("cc").unwrap(),
290         cxx: matches.opt_str("cxx").unwrap(),
291         cflags: matches.opt_str("cflags").unwrap(),
292         cxxflags: matches.opt_str("cxxflags").unwrap(),
293         ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
294         linker: matches.opt_str("linker"),
295         llvm_components: matches.opt_str("llvm-components").unwrap(),
296         nodejs: matches.opt_str("nodejs"),
297         npm: matches.opt_str("npm"),
298
299         force_rerun: matches.opt_present("force-rerun"),
300     }
301 }
302
303 pub fn log_config(config: &Config) {
304     let c = config;
305     logv(c, "configuration:".to_string());
306     logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
307     logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
308     logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
309     logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
310     logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
311     logv(c, format!("src_base: {:?}", config.src_base.display()));
312     logv(c, format!("build_base: {:?}", config.build_base.display()));
313     logv(c, format!("stage_id: {}", config.stage_id));
314     logv(c, format!("mode: {}", config.mode));
315     logv(c, format!("run_ignored: {}", config.run_ignored));
316     logv(c, format!("filters: {:?}", config.filters));
317     logv(c, format!("skip: {:?}", config.skip));
318     logv(c, format!("filter_exact: {}", config.filter_exact));
319     logv(
320         c,
321         format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
322     );
323     logv(c, format!("runtool: {}", opt_str(&config.runtool)));
324     logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
325     logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
326     logv(c, format!("target: {}", config.target));
327     logv(c, format!("host: {}", config.host));
328     logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
329     logv(c, format!("adb_path: {:?}", config.adb_path));
330     logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
331     logv(c, format!("adb_device_status: {}", config.adb_device_status));
332     logv(c, format!("ar: {}", config.ar));
333     logv(c, format!("linker: {:?}", config.linker));
334     logv(c, format!("verbose: {}", config.verbose));
335     logv(c, format!("quiet: {}", config.quiet));
336     logv(c, "\n".to_string());
337 }
338
339 pub fn opt_str(maybestr: &Option<String>) -> &str {
340     match *maybestr {
341         None => "(none)",
342         Some(ref s) => s,
343     }
344 }
345
346 pub fn opt_str2(maybestr: Option<String>) -> String {
347     match maybestr {
348         None => "(none)".to_owned(),
349         Some(s) => s,
350     }
351 }
352
353 pub fn run_tests(config: Config) {
354     // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
355     if let Mode::CodegenUnits = config.mode {
356         let _ = fs::remove_dir_all("tmp/partitioning-tests");
357     }
358
359     // If we want to collect rustfix coverage information,
360     // we first make sure that the coverage file does not exist.
361     // It will be created later on.
362     if config.rustfix_coverage {
363         let mut coverage_file_path = config.build_base.clone();
364         coverage_file_path.push("rustfix_missing_coverage.txt");
365         if coverage_file_path.exists() {
366             if let Err(e) = fs::remove_file(&coverage_file_path) {
367                 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
368             }
369         }
370     }
371
372     // sadly osx needs some file descriptor limits raised for running tests in
373     // parallel (especially when we have lots and lots of child processes).
374     // For context, see #8904
375     unsafe {
376         raise_fd_limit::raise_fd_limit();
377     }
378     // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
379     // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
380     env::set_var("__COMPAT_LAYER", "RunAsInvoker");
381
382     // Let tests know which target they're running as
383     env::set_var("TARGET", &config.target);
384
385     let opts = test_opts(&config);
386
387     let mut configs = Vec::new();
388     if let Mode::DebugInfo = config.mode {
389         // Debugging emscripten code doesn't make sense today
390         if !config.target.contains("emscripten") {
391             configs.extend(configure_cdb(&config));
392             configs.extend(configure_gdb(&config));
393             configs.extend(configure_lldb(&config));
394         }
395     } else {
396         configs.push(config.clone());
397     };
398
399     let mut tests = Vec::new();
400     for c in &configs {
401         make_tests(c, &mut tests);
402     }
403
404     let res = test::run_tests_console(&opts, tests);
405     match res {
406         Ok(true) => {}
407         Ok(false) => {
408             // We want to report that the tests failed, but we also want to give
409             // some indication of just what tests we were running. Especially on
410             // CI, where there can be cross-compiled tests for a lot of
411             // architectures, without this critical information it can be quite
412             // easy to miss which tests failed, and as such fail to reproduce
413             // the failure locally.
414
415             eprintln!(
416                 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
417                 config.suite,
418                 config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(),
419                 config.mode,
420                 config.host,
421                 config.target
422             );
423
424             std::process::exit(1);
425         }
426         Err(e) => {
427             // We don't know if tests passed or not, but if there was an error
428             // during testing we don't want to just succeed (we may not have
429             // tested something), so fail.
430             //
431             // This should realistically "never" happen, so don't try to make
432             // this a pretty error message.
433             panic!("I/O failure during tests: {:?}", e);
434         }
435     }
436 }
437
438 fn configure_cdb(config: &Config) -> Option<Config> {
439     config.cdb.as_ref()?;
440
441     Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
442 }
443
444 fn configure_gdb(config: &Config) -> Option<Config> {
445     config.gdb_version?;
446
447     if util::matches_env(&config.target, "msvc") {
448         return None;
449     }
450
451     if config.remote_test_client.is_some() && !config.target.contains("android") {
452         println!(
453             "WARNING: debuginfo tests are not available when \
454              testing with remote"
455         );
456         return None;
457     }
458
459     if config.target.contains("android") {
460         println!(
461             "{} debug-info test uses tcp 5039 port.\
462              please reserve it",
463             config.target
464         );
465
466         // android debug-info test uses remote debugger so, we test 1 thread
467         // at once as they're all sharing the same TCP port to communicate
468         // over.
469         //
470         // we should figure out how to lift this restriction! (run them all
471         // on different ports allocated dynamically).
472         env::set_var("RUST_TEST_THREADS", "1");
473     }
474
475     Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
476 }
477
478 fn configure_lldb(config: &Config) -> Option<Config> {
479     config.lldb_python_dir.as_ref()?;
480
481     if let Some(350) = config.lldb_version {
482         println!(
483             "WARNING: The used version of LLDB (350) has a \
484              known issue that breaks debuginfo tests. See \
485              issue #32520 for more information. Skipping all \
486              LLDB-based tests!",
487         );
488         return None;
489     }
490
491     Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
492 }
493
494 pub fn test_opts(config: &Config) -> test::TestOpts {
495     test::TestOpts {
496         exclude_should_panic: false,
497         filters: config.filters.clone(),
498         filter_exact: config.filter_exact,
499         run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
500         format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
501         logfile: config.logfile.clone(),
502         run_tests: true,
503         bench_benchmarks: true,
504         nocapture: match env::var("RUST_TEST_NOCAPTURE") {
505             Ok(val) => &val != "0",
506             Err(_) => false,
507         },
508         color: config.color,
509         shuffle: false,
510         shuffle_seed: None,
511         test_threads: None,
512         skip: config.skip.clone(),
513         list: false,
514         options: test::Options::new(),
515         time_options: None,
516         force_run_in_process: false,
517     }
518 }
519
520 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
521     debug!("making tests from {:?}", config.src_base.display());
522     let inputs = common_inputs_stamp(config);
523     collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
524         .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
525 }
526
527 /// Returns a stamp constructed from input files common to all test cases.
528 fn common_inputs_stamp(config: &Config) -> Stamp {
529     let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
530
531     let mut stamp = Stamp::from_path(&config.rustc_path);
532
533     // Relevant pretty printer files
534     let pretty_printer_files = [
535         "src/etc/rust_types.py",
536         "src/etc/gdb_load_rust_pretty_printers.py",
537         "src/etc/gdb_lookup.py",
538         "src/etc/gdb_providers.py",
539         "src/etc/lldb_batchmode.py",
540         "src/etc/lldb_lookup.py",
541         "src/etc/lldb_providers.py",
542     ];
543     for file in &pretty_printer_files {
544         let path = rust_src_dir.join(file);
545         stamp.add_path(&path);
546     }
547
548     stamp.add_dir(&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 || runtest::run(config, &testpaths, revision.as_deref())))
805 }
806
807 /// Returns `true` if the given target is an Android target for the
808 /// purposes of GDB testing.
809 fn is_android_gdb_target(target: &str) -> bool {
810     matches!(
811         &target[..],
812         "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
813     )
814 }
815
816 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
817 fn is_pc_windows_msvc_target(target: &str) -> bool {
818     target.ends_with("-pc-windows-msvc")
819 }
820
821 fn find_cdb(target: &str) -> Option<OsString> {
822     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
823         return None;
824     }
825
826     let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
827     let cdb_arch = if cfg!(target_arch = "x86") {
828         "x86"
829     } else if cfg!(target_arch = "x86_64") {
830         "x64"
831     } else if cfg!(target_arch = "aarch64") {
832         "arm64"
833     } else if cfg!(target_arch = "arm") {
834         "arm"
835     } else {
836         return None; // No compatible CDB.exe in the Windows 10 SDK
837     };
838
839     let mut path = PathBuf::new();
840     path.push(pf86);
841     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
842     path.push(cdb_arch);
843     path.push(r"cdb.exe");
844
845     if !path.exists() {
846         return None;
847     }
848
849     Some(path.into_os_string())
850 }
851
852 /// Returns Path to CDB
853 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
854     let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
855
856     let mut version = None;
857     if let Some(cdb) = cdb.as_ref() {
858         if let Ok(output) = Command::new(cdb).arg("/version").output() {
859             if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
860                 version = extract_cdb_version(&first_line);
861             }
862         }
863     }
864
865     (cdb, version)
866 }
867
868 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
869     // Example full_version_line: "cdb version 10.0.18362.1"
870     let version = full_version_line.rsplit(' ').next()?;
871     let mut components = version.split('.');
872     let major: u16 = components.next().unwrap().parse().unwrap();
873     let minor: u16 = components.next().unwrap().parse().unwrap();
874     let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
875     let build: u16 = components.next().unwrap_or("0").parse().unwrap();
876     Some([major, minor, patch, build])
877 }
878
879 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
880 fn analyze_gdb(
881     gdb: Option<String>,
882     target: &str,
883     android_cross_path: &PathBuf,
884 ) -> (Option<String>, Option<u32>, bool) {
885     #[cfg(not(windows))]
886     const GDB_FALLBACK: &str = "gdb";
887     #[cfg(windows)]
888     const GDB_FALLBACK: &str = "gdb.exe";
889
890     const MIN_GDB_WITH_RUST: u32 = 7011010;
891
892     let fallback_gdb = || {
893         if is_android_gdb_target(target) {
894             let mut gdb_path = match android_cross_path.to_str() {
895                 Some(x) => x.to_owned(),
896                 None => panic!("cannot find android cross path"),
897             };
898             gdb_path.push_str("/bin/gdb");
899             gdb_path
900         } else {
901             GDB_FALLBACK.to_owned()
902         }
903     };
904
905     let gdb = match gdb {
906         None => fallback_gdb(),
907         Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
908         Some(ref s) => s.to_owned(),
909     };
910
911     let mut version_line = None;
912     if let Ok(output) = Command::new(&gdb).arg("--version").output() {
913         if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
914             version_line = Some(first_line.to_string());
915         }
916     }
917
918     let version = match version_line {
919         Some(line) => extract_gdb_version(&line),
920         None => return (None, None, false),
921     };
922
923     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
924
925     (Some(gdb), version, gdb_native_rust)
926 }
927
928 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
929     let full_version_line = full_version_line.trim();
930
931     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
932     // of the ? sections being optional
933
934     // We will parse up to 3 digits for each component, ignoring the date
935
936     // We skip text in parentheses.  This avoids accidentally parsing
937     // the openSUSE version, which looks like:
938     //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
939     // This particular form is documented in the GNU coding standards:
940     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
941
942     let unbracketed_part = full_version_line.split('[').next().unwrap();
943     let mut splits = unbracketed_part.trim_end().rsplit(' ');
944     let version_string = splits.next().unwrap();
945
946     let mut splits = version_string.split('.');
947     let major = splits.next().unwrap();
948     let minor = splits.next().unwrap();
949     let patch = splits.next();
950
951     let major: u32 = major.parse().unwrap();
952     let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
953         None => {
954             let minor = minor.parse().unwrap();
955             let patch: u32 = match patch {
956                 Some(patch) => match patch.find(not_a_digit) {
957                     None => patch.parse().unwrap(),
958                     Some(idx) if idx > 3 => 0,
959                     Some(idx) => patch[..idx].parse().unwrap(),
960                 },
961                 None => 0,
962             };
963             (minor, patch)
964         }
965         // There is no patch version after minor-date (e.g. "4-2012").
966         Some(idx) => {
967             let minor = minor[..idx].parse().unwrap();
968             (minor, 0)
969         }
970     };
971
972     Some(((major * 1000) + minor) * 1000 + patch)
973 }
974
975 /// Returns (LLDB version, LLDB is rust-enabled)
976 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
977     // Extract the major LLDB version from the given version string.
978     // LLDB version strings are different for Apple and non-Apple platforms.
979     // The Apple variant looks like this:
980     //
981     // LLDB-179.5 (older versions)
982     // lldb-300.2.51 (new versions)
983     //
984     // We are only interested in the major version number, so this function
985     // will return `Some(179)` and `Some(300)` respectively.
986     //
987     // Upstream versions look like:
988     // lldb version 6.0.1
989     //
990     // There doesn't seem to be a way to correlate the Apple version
991     // with the upstream version, and since the tests were originally
992     // written against Apple versions, we make a fake Apple version by
993     // multiplying the first number by 100.  This is a hack, but
994     // normally fine because the only non-Apple version we test is
995     // rust-enabled.
996
997     let full_version_line = full_version_line.trim();
998
999     if let Some(apple_ver) =
1000         full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1001     {
1002         if let Some(idx) = apple_ver.find(not_a_digit) {
1003             let version: u32 = apple_ver[..idx].parse().unwrap();
1004             return Some((version, full_version_line.contains("rust-enabled")));
1005         }
1006     } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1007         if let Some(idx) = lldb_ver.find(not_a_digit) {
1008             let version: u32 = lldb_ver[..idx].parse().ok()?;
1009             return Some((version * 100, full_version_line.contains("rust-enabled")));
1010         }
1011     }
1012     None
1013 }
1014
1015 fn not_a_digit(c: char) -> bool {
1016     !c.is_digit(10)
1017 }