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