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