]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
Auto merge of #87820 - elichai:patch-2, r=kennytm
[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 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("", "lldb-python", "path to python to use for doc tests", "PATH")
64         .reqopt("", "docck-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         .optflag("", "exact", "filters match exactly")
95         .optopt(
96             "",
97             "runtool",
98             "supervisor program to run tests under \
99              (eg. emulator, valgrind)",
100             "PROGRAM",
101         )
102         .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
103         .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
104         .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
105         .optflag("", "verbose", "run tests verbosely, showing all output")
106         .optflag(
107             "",
108             "bless",
109             "overwrite stderr/stdout files instead of complaining about a mismatch",
110         )
111         .optflag("", "quiet", "print one character per test instead of one line")
112         .optopt("", "color", "coloring: auto, always, never", "WHEN")
113         .optopt("", "logfile", "file to log test execution to", "FILE")
114         .optopt("", "target", "the target to build for", "TARGET")
115         .optopt("", "host", "the host to build for", "HOST")
116         .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
117         .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
118         .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
119         .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
120         .optflag("", "system-llvm", "is LLVM the system LLVM")
121         .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
122         .optopt("", "adb-path", "path to the android debugger", "PATH")
123         .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
124         .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
125         .reqopt("", "cc", "path to a C compiler", "PATH")
126         .reqopt("", "cxx", "path to a C++ compiler", "PATH")
127         .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
128         .optopt("", "ar", "path to an archiver", "PATH")
129         .optopt("", "linker", "path to a linker", "PATH")
130         .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
131         .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
132         .optopt("", "nodejs", "the name of nodejs", "PATH")
133         .optopt("", "npm", "the name of npm", "PATH")
134         .optopt("", "remote-test-client", "path to the remote test client", "PATH")
135         .optopt(
136             "",
137             "compare-mode",
138             "mode describing what file the actual ui output will be compared to",
139             "COMPARE MODE",
140         )
141         .optflag(
142             "",
143             "rustfix-coverage",
144             "enable this to generate a Rustfix coverage file, which is saved in \
145                 `./<build_base>/rustfix_missing_coverage.txt`",
146         )
147         .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
148         .optflag("h", "help", "show this message")
149         .reqopt("", "channel", "current Rust channel", "CHANNEL");
150
151     let (argv0, args_) = args.split_first().unwrap();
152     if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
153         let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
154         println!("{}", opts.usage(&message));
155         println!();
156         panic!()
157     }
158
159     let matches = &match opts.parse(args_) {
160         Ok(m) => m,
161         Err(f) => panic!("{:?}", f),
162     };
163
164     if matches.opt_present("h") || matches.opt_present("help") {
165         let message = format!("Usage: {} [OPTIONS]  [TESTNAME...]", argv0);
166         println!("{}", opts.usage(&message));
167         println!();
168         panic!()
169     }
170
171     fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
172         match m.opt_str(nm) {
173             Some(s) => PathBuf::from(&s),
174             None => panic!("no option (=path) found for {}", nm),
175         }
176     }
177
178     fn make_absolute(path: PathBuf) -> PathBuf {
179         if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
180     }
181
182     let target = opt_str2(matches.opt_str("target"));
183     let android_cross_path = opt_path(matches, "android-cross-path");
184     let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
185     let (gdb, gdb_version, gdb_native_rust) =
186         analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
187     let (lldb_version, lldb_native_rust) = matches
188         .opt_str("lldb-version")
189         .as_deref()
190         .and_then(extract_lldb_version)
191         .map(|(v, b)| (Some(v), b))
192         .unwrap_or((None, false));
193     let color = match matches.opt_str("color").as_deref() {
194         Some("auto") | None => ColorConfig::AutoColor,
195         Some("always") => ColorConfig::AlwaysColor,
196         Some("never") => ColorConfig::NeverColor,
197         Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
198     };
199     let llvm_version =
200         matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
201
202     let src_base = opt_path(matches, "src-base");
203     let run_ignored = matches.opt_present("ignored");
204     let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
205     let has_tidy = if mode == Mode::Rustdoc {
206         Command::new("tidy")
207             .arg("--version")
208             .stdout(Stdio::null())
209             .status()
210             .map_or(false, |status| status.success())
211     } else {
212         // Avoid spawning an external command when we know tidy won't be used.
213         false
214     };
215     Config {
216         bless: matches.opt_present("bless"),
217         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
218         run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
219         rustc_path: opt_path(matches, "rustc-path"),
220         rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
221         rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
222         lldb_python: matches.opt_str("lldb-python").unwrap(),
223         docck_python: matches.opt_str("docck-python").unwrap(),
224         jsondocck_path: matches.opt_str("jsondocck-path"),
225         valgrind_path: matches.opt_str("valgrind-path"),
226         force_valgrind: matches.opt_present("force-valgrind"),
227         run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
228         llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
229         llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
230         src_base,
231         build_base: opt_path(matches, "build-base"),
232         stage_id: matches.opt_str("stage-id").unwrap(),
233         mode,
234         suite: matches.opt_str("suite").unwrap(),
235         debugger: None,
236         run_ignored,
237         filters: matches.free.clone(),
238         filter_exact: matches.opt_present("exact"),
239         force_pass_mode: matches.opt_str("pass").map(|mode| {
240             mode.parse::<PassMode>()
241                 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
242         }),
243         run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
244             "auto" => None,
245             "always" => Some(true),
246             "never" => Some(false),
247             _ => panic!("unknown `--run` option `{}` given", mode),
248         }),
249         logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
250         runtool: matches.opt_str("runtool"),
251         host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
252         target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
253         target_panic: match matches.opt_str("target-panic").as_deref() {
254             Some("unwind") | None => PanicStrategy::Unwind,
255             Some("abort") => PanicStrategy::Abort,
256             _ => panic!("unknown `--target-panic` option `{}` given", mode),
257         },
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
285         cc: matches.opt_str("cc").unwrap(),
286         cxx: matches.opt_str("cxx").unwrap(),
287         cflags: matches.opt_str("cflags").unwrap(),
288         ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
289         linker: matches.opt_str("linker"),
290         llvm_components: matches.opt_str("llvm-components").unwrap(),
291         nodejs: matches.opt_str("nodejs"),
292         npm: matches.opt_str("npm"),
293
294         force_rerun: matches.opt_present("force-rerun"),
295     }
296 }
297
298 pub fn log_config(config: &Config) {
299     let c = config;
300     logv(c, "configuration:".to_string());
301     logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
302     logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
303     logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
304     logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
305     logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
306     logv(c, format!("src_base: {:?}", config.src_base.display()));
307     logv(c, format!("build_base: {:?}", config.build_base.display()));
308     logv(c, format!("stage_id: {}", config.stage_id));
309     logv(c, format!("mode: {}", config.mode));
310     logv(c, format!("run_ignored: {}", config.run_ignored));
311     logv(c, format!("filters: {:?}", config.filters));
312     logv(c, format!("filter_exact: {}", config.filter_exact));
313     logv(
314         c,
315         format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
316     );
317     logv(c, format!("runtool: {}", opt_str(&config.runtool)));
318     logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
319     logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
320     logv(c, format!("target: {}", config.target));
321     logv(c, format!("host: {}", config.host));
322     logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
323     logv(c, format!("adb_path: {:?}", config.adb_path));
324     logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
325     logv(c, format!("adb_device_status: {}", config.adb_device_status));
326     logv(c, format!("ar: {}", config.ar));
327     logv(c, format!("linker: {:?}", config.linker));
328     logv(c, format!("verbose: {}", config.verbose));
329     logv(c, format!("quiet: {}", config.quiet));
330     logv(c, "\n".to_string());
331 }
332
333 pub fn opt_str(maybestr: &Option<String>) -> &str {
334     match *maybestr {
335         None => "(none)",
336         Some(ref s) => s,
337     }
338 }
339
340 pub fn opt_str2(maybestr: Option<String>) -> String {
341     match maybestr {
342         None => "(none)".to_owned(),
343         Some(s) => s,
344     }
345 }
346
347 pub fn run_tests(config: Config) {
348     // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
349     if let Mode::CodegenUnits = config.mode {
350         let _ = fs::remove_dir_all("tmp/partitioning-tests");
351     }
352
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 util::matches_env(&config.target, "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 older versions of LLDB seem to have problems with multiple
486     // instances running in parallel, so only run one test thread at a
487     // time.
488     env::set_var("RUST_TEST_THREADS", "1");
489
490     Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
491 }
492
493 pub fn test_opts(config: &Config) -> test::TestOpts {
494     test::TestOpts {
495         exclude_should_panic: false,
496         filters: config.filters.clone(),
497         filter_exact: config.filter_exact,
498         run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
499         format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
500         logfile: config.logfile.clone(),
501         run_tests: true,
502         bench_benchmarks: true,
503         nocapture: match env::var("RUST_TEST_NOCAPTURE") {
504             Ok(val) => &val != "0",
505             Err(_) => false,
506         },
507         color: config.color,
508         test_threads: None,
509         skip: vec![],
510         list: false,
511         options: test::Options::new(),
512         time_options: None,
513         force_run_in_process: false,
514     }
515 }
516
517 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
518     debug!("making tests from {:?}", config.src_base.display());
519     let inputs = common_inputs_stamp(config);
520     collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
521         .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
522 }
523
524 /// Returns a stamp constructed from input files common to all test cases.
525 fn common_inputs_stamp(config: &Config) -> Stamp {
526     let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
527
528     let mut stamp = Stamp::from_path(&config.rustc_path);
529
530     // Relevant pretty printer files
531     let pretty_printer_files = [
532         "src/etc/rust_types.py",
533         "src/etc/gdb_load_rust_pretty_printers.py",
534         "src/etc/gdb_lookup.py",
535         "src/etc/gdb_providers.py",
536         "src/etc/lldb_batchmode.py",
537         "src/etc/lldb_lookup.py",
538         "src/etc/lldb_providers.py",
539     ];
540     for file in &pretty_printer_files {
541         let path = rust_src_dir.join(file);
542         stamp.add_path(&path);
543     }
544
545     stamp.add_dir(&config.run_lib_path);
546
547     if let Some(ref rustdoc_path) = config.rustdoc_path {
548         stamp.add_path(&rustdoc_path);
549         stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
550     }
551
552     // Compiletest itself.
553     stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
554
555     stamp
556 }
557
558 fn collect_tests_from_dir(
559     config: &Config,
560     dir: &Path,
561     relative_dir_path: &Path,
562     inputs: &Stamp,
563     tests: &mut Vec<test::TestDescAndFn>,
564 ) -> io::Result<()> {
565     // Ignore directories that contain a file named `compiletest-ignore-dir`.
566     if dir.join("compiletest-ignore-dir").exists() {
567         return Ok(());
568     }
569
570     if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
571         let paths = TestPaths {
572             file: dir.to_path_buf(),
573             relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
574         };
575         tests.extend(make_test(config, &paths, inputs));
576         return Ok(());
577     }
578
579     // If we find a test foo/bar.rs, we have to build the
580     // output directory `$build/foo` so we can write
581     // `$build/foo/bar` into it. We do this *now* in this
582     // sequential loop because otherwise, if we do it in the
583     // tests themselves, they race for the privilege of
584     // creating the directories and sometimes fail randomly.
585     let build_dir = output_relative_path(config, relative_dir_path);
586     fs::create_dir_all(&build_dir).unwrap();
587
588     // Add each `.rs` file as a test, and recurse further on any
589     // subdirectories we find, except for `aux` directories.
590     for file in fs::read_dir(dir)? {
591         let file = file?;
592         let file_path = file.path();
593         let file_name = file.file_name();
594         if is_test(&file_name) {
595             debug!("found test file: {:?}", file_path.display());
596             let paths =
597                 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
598             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 is_up_to_date(
669     config: &Config,
670     testpaths: &TestPaths,
671     props: &EarlyProps,
672     revision: Option<&str>,
673     inputs: &Stamp,
674 ) -> bool {
675     let stamp_name = stamp(config, testpaths, revision);
676     // Check hash.
677     let contents = match fs::read_to_string(&stamp_name) {
678         Ok(f) => f,
679         Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
680         Err(_) => return false,
681     };
682     let expected_hash = runtest::compute_stamp_hash(config);
683     if contents != expected_hash {
684         return false;
685     }
686
687     // Check timestamps.
688     let mut inputs = inputs.clone();
689     // Use `add_dir` to account for run-make tests, which use their individual directory
690     inputs.add_dir(&testpaths.file);
691
692     for aux in &props.aux {
693         let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
694         inputs.add_path(&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         inputs.add_path(path);
701     }
702
703     inputs < Stamp::from_path(&stamp_name)
704 }
705
706 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
707 struct Stamp {
708     time: SystemTime,
709 }
710
711 impl Stamp {
712     fn from_path(path: &Path) -> Self {
713         let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
714         stamp.add_path(path);
715         stamp
716     }
717
718     fn add_path(&mut self, path: &Path) {
719         let modified = fs::metadata(path)
720             .and_then(|metadata| metadata.modified())
721             .unwrap_or(SystemTime::UNIX_EPOCH);
722         self.time = self.time.max(modified);
723     }
724
725     fn add_dir(&mut self, path: &Path) {
726         for entry in WalkDir::new(path) {
727             let entry = entry.unwrap();
728             if entry.file_type().is_file() {
729                 let modified = entry
730                     .metadata()
731                     .ok()
732                     .and_then(|metadata| metadata.modified().ok())
733                     .unwrap_or(SystemTime::UNIX_EPOCH);
734                 self.time = self.time.max(modified);
735             }
736         }
737     }
738 }
739
740 fn make_test_name(
741     config: &Config,
742     testpaths: &TestPaths,
743     revision: Option<&String>,
744 ) -> test::TestName {
745     // Convert a complete path to something like
746     //
747     //    ui/foo/bar/baz.rs
748     let path = PathBuf::from(config.src_base.file_name().unwrap())
749         .join(&testpaths.relative_dir)
750         .join(&testpaths.file.file_name().unwrap());
751     let debugger = match config.debugger {
752         Some(d) => format!("-{}", d),
753         None => String::new(),
754     };
755     let mode_suffix = match config.compare_mode {
756         Some(ref mode) => format!(" ({})", mode.to_str()),
757         None => String::new(),
758     };
759
760     test::DynTestName(format!(
761         "[{}{}{}] {}{}",
762         config.mode,
763         debugger,
764         mode_suffix,
765         path.display(),
766         revision.map_or("".to_string(), |rev| format!("#{}", rev))
767     ))
768 }
769
770 fn make_test_closure(
771     config: &Config,
772     testpaths: &TestPaths,
773     revision: Option<&String>,
774 ) -> test::TestFn {
775     let config = config.clone();
776     let testpaths = testpaths.clone();
777     let revision = revision.cloned();
778     test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
779 }
780
781 /// Returns `true` if the given target is an Android target for the
782 /// purposes of GDB testing.
783 fn is_android_gdb_target(target: &str) -> bool {
784     matches!(
785         &target[..],
786         "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
787     )
788 }
789
790 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
791 fn is_pc_windows_msvc_target(target: &str) -> bool {
792     target.ends_with("-pc-windows-msvc")
793 }
794
795 fn find_cdb(target: &str) -> Option<OsString> {
796     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
797         return None;
798     }
799
800     let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
801     let cdb_arch = if cfg!(target_arch = "x86") {
802         "x86"
803     } else if cfg!(target_arch = "x86_64") {
804         "x64"
805     } else if cfg!(target_arch = "aarch64") {
806         "arm64"
807     } else if cfg!(target_arch = "arm") {
808         "arm"
809     } else {
810         return None; // No compatible CDB.exe in the Windows 10 SDK
811     };
812
813     let mut path = PathBuf::new();
814     path.push(pf86);
815     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
816     path.push(cdb_arch);
817     path.push(r"cdb.exe");
818
819     if !path.exists() {
820         return None;
821     }
822
823     Some(path.into_os_string())
824 }
825
826 /// Returns Path to CDB
827 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
828     let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
829
830     let mut version = None;
831     if let Some(cdb) = cdb.as_ref() {
832         if let Ok(output) = Command::new(cdb).arg("/version").output() {
833             if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
834                 version = extract_cdb_version(&first_line);
835             }
836         }
837     }
838
839     (cdb, version)
840 }
841
842 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
843     // Example full_version_line: "cdb version 10.0.18362.1"
844     let version = full_version_line.rsplit(' ').next()?;
845     let mut components = version.split('.');
846     let major: u16 = components.next().unwrap().parse().unwrap();
847     let minor: u16 = components.next().unwrap().parse().unwrap();
848     let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
849     let build: u16 = components.next().unwrap_or("0").parse().unwrap();
850     Some([major, minor, patch, build])
851 }
852
853 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
854 fn analyze_gdb(
855     gdb: Option<String>,
856     target: &str,
857     android_cross_path: &PathBuf,
858 ) -> (Option<String>, Option<u32>, bool) {
859     #[cfg(not(windows))]
860     const GDB_FALLBACK: &str = "gdb";
861     #[cfg(windows)]
862     const GDB_FALLBACK: &str = "gdb.exe";
863
864     const MIN_GDB_WITH_RUST: u32 = 7011010;
865
866     let fallback_gdb = || {
867         if is_android_gdb_target(target) {
868             let mut gdb_path = match android_cross_path.to_str() {
869                 Some(x) => x.to_owned(),
870                 None => panic!("cannot find android cross path"),
871             };
872             gdb_path.push_str("/bin/gdb");
873             gdb_path
874         } else {
875             GDB_FALLBACK.to_owned()
876         }
877     };
878
879     let gdb = match gdb {
880         None => fallback_gdb(),
881         Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
882         Some(ref s) => s.to_owned(),
883     };
884
885     let mut version_line = None;
886     if let Ok(output) = Command::new(&gdb).arg("--version").output() {
887         if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
888             version_line = Some(first_line.to_string());
889         }
890     }
891
892     let version = match version_line {
893         Some(line) => extract_gdb_version(&line),
894         None => return (None, None, false),
895     };
896
897     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
898
899     (Some(gdb), version, gdb_native_rust)
900 }
901
902 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
903     let full_version_line = full_version_line.trim();
904
905     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
906     // of the ? sections being optional
907
908     // We will parse up to 3 digits for each component, ignoring the date
909
910     // We skip text in parentheses.  This avoids accidentally parsing
911     // the openSUSE version, which looks like:
912     //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
913     // This particular form is documented in the GNU coding standards:
914     // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
915
916     let unbracketed_part = full_version_line.split('[').next().unwrap();
917     let mut splits = unbracketed_part.trim_end().rsplit(' ');
918     let version_string = splits.next().unwrap();
919
920     let mut splits = version_string.split('.');
921     let major = splits.next().unwrap();
922     let minor = splits.next().unwrap();
923     let patch = splits.next();
924
925     let major: u32 = major.parse().unwrap();
926     let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
927         None => {
928             let minor = minor.parse().unwrap();
929             let patch: u32 = match patch {
930                 Some(patch) => match patch.find(not_a_digit) {
931                     None => patch.parse().unwrap(),
932                     Some(idx) if idx > 3 => 0,
933                     Some(idx) => patch[..idx].parse().unwrap(),
934                 },
935                 None => 0,
936             };
937             (minor, patch)
938         }
939         // There is no patch version after minor-date (e.g. "4-2012").
940         Some(idx) => {
941             let minor = minor[..idx].parse().unwrap();
942             (minor, 0)
943         }
944     };
945
946     Some(((major * 1000) + minor) * 1000 + patch)
947 }
948
949 /// Returns (LLDB version, LLDB is rust-enabled)
950 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
951     // Extract the major LLDB version from the given version string.
952     // LLDB version strings are different for Apple and non-Apple platforms.
953     // The Apple variant looks like this:
954     //
955     // LLDB-179.5 (older versions)
956     // lldb-300.2.51 (new versions)
957     //
958     // We are only interested in the major version number, so this function
959     // will return `Some(179)` and `Some(300)` respectively.
960     //
961     // Upstream versions look like:
962     // lldb version 6.0.1
963     //
964     // There doesn't seem to be a way to correlate the Apple version
965     // with the upstream version, and since the tests were originally
966     // written against Apple versions, we make a fake Apple version by
967     // multiplying the first number by 100.  This is a hack, but
968     // normally fine because the only non-Apple version we test is
969     // rust-enabled.
970
971     let full_version_line = full_version_line.trim();
972
973     if let Some(apple_ver) =
974         full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
975     {
976         if let Some(idx) = apple_ver.find(not_a_digit) {
977             let version: u32 = apple_ver[..idx].parse().unwrap();
978             return Some((version, full_version_line.contains("rust-enabled")));
979         }
980     } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
981         if let Some(idx) = lldb_ver.find(not_a_digit) {
982             let version: u32 = lldb_ver[..idx].parse().ok()?;
983             return Some((version * 100, full_version_line.contains("rust-enabled")));
984         }
985     }
986     None
987 }
988
989 fn not_a_digit(c: char) -> bool {
990     !c.is_digit(10)
991 }