1 #![crate_name = "compiletest"]
2 // The `test` crate is the only unstable feature
3 // allowed here, just to share similar code.
9 expected_output_path, output_base_dir, output_relative_path, PanicStrategy, UI_EXTENSIONS,
11 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
12 use crate::util::logv;
14 use lazycell::LazyCell;
16 use std::ffi::OsString;
18 use std::io::{self, ErrorKind};
19 use std::path::{Path, PathBuf};
20 use std::process::{Command, Stdio};
21 use std::time::SystemTime;
22 use test::ColorConfig;
26 use self::header::{make_test_description, EarlyProps};
42 tracing_subscriber::fmt::init();
44 let config = parse_config(env::args().collect());
46 if config.valgrind_path.is_none() && config.force_valgrind {
47 panic!("Can't find Valgrind to run Valgrind tests");
50 if !config.has_tidy && config.mode == Mode::Rustdoc {
51 eprintln!("warning: `tidy` is not installed; diffs will not be generated");
58 pub fn parse_config(args: Vec<String>) -> Config {
59 let mut opts = Options::new();
60 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
61 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
62 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
63 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
64 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
65 .reqopt("", "python", "path to python to use for doc tests", "PATH")
66 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
67 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
68 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
69 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
70 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
71 .reqopt("", "src-base", "directory to scan for test files", "PATH")
72 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
73 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
77 "which sort of compile tests to run",
78 "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
79 | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
84 "which suite of compile tests to run. used for nicer error reporting.",
90 "force {check,build,run}-pass tests to this mode.",
91 "check | build | run",
93 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
94 .optflag("", "ignored", "run tests marked as ignored")
95 .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING")
96 .optflag("", "exact", "filters match exactly")
100 "supervisor program to run tests under \
101 (eg. emulator, valgrind)",
104 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
105 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
106 .optflag("", "optimize-tests", "run tests with optimizations enabled")
107 .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
108 .optflag("", "verbose", "run tests verbosely, showing all output")
112 "overwrite stderr/stdout files instead of complaining about a mismatch",
114 .optflag("", "quiet", "print one character per test instead of one line")
115 .optopt("", "color", "coloring: auto, always, never", "WHEN")
116 .optopt("", "logfile", "file to log test execution to", "FILE")
117 .optopt("", "target", "the target to build for", "TARGET")
118 .optopt("", "host", "the host to build for", "HOST")
119 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
120 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
121 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
122 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
123 .optflag("", "system-llvm", "is LLVM the system LLVM")
124 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
125 .optopt("", "adb-path", "path to the android debugger", "PATH")
126 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
127 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
128 .reqopt("", "cc", "path to a C compiler", "PATH")
129 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
130 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
131 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
132 .optopt("", "ar", "path to an archiver", "PATH")
133 .optopt("", "linker", "path to a linker", "PATH")
134 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
135 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
136 .optopt("", "nodejs", "the name of nodejs", "PATH")
137 .optopt("", "npm", "the name of npm", "PATH")
138 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
142 "mode describing what file the actual ui output will be compared to",
148 "enable this to generate a Rustfix coverage file, which is saved in \
149 `./<build_base>/rustfix_missing_coverage.txt`",
151 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
152 .optflag("h", "help", "show this message")
153 .reqopt("", "channel", "current Rust channel", "CHANNEL")
154 .optopt("", "edition", "default Rust edition", "EDITION");
156 let (argv0, args_) = args.split_first().unwrap();
157 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
158 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
159 println!("{}", opts.usage(&message));
164 let matches = &match opts.parse(args_) {
166 Err(f) => panic!("{:?}", f),
169 if matches.opt_present("h") || matches.opt_present("help") {
170 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
171 println!("{}", opts.usage(&message));
176 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
177 match m.opt_str(nm) {
178 Some(s) => PathBuf::from(&s),
179 None => panic!("no option (=path) found for {}", nm),
183 fn make_absolute(path: PathBuf) -> PathBuf {
184 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
187 let target = opt_str2(matches.opt_str("target"));
188 let android_cross_path = opt_path(matches, "android-cross-path");
189 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
190 let (gdb, gdb_version, gdb_native_rust) =
191 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
192 let (lldb_version, lldb_native_rust) = matches
193 .opt_str("lldb-version")
195 .and_then(extract_lldb_version)
196 .map(|(v, b)| (Some(v), b))
197 .unwrap_or((None, false));
198 let color = match matches.opt_str("color").as_deref() {
199 Some("auto") | None => ColorConfig::AutoColor,
200 Some("always") => ColorConfig::AlwaysColor,
201 Some("never") => ColorConfig::NeverColor,
202 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
205 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
207 let src_base = opt_path(matches, "src-base");
208 let run_ignored = matches.opt_present("ignored");
209 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
210 let has_tidy = if mode == Mode::Rustdoc {
213 .stdout(Stdio::null())
215 .map_or(false, |status| status.success())
217 // Avoid spawning an external command when we know tidy won't be used.
221 bless: matches.opt_present("bless"),
222 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
223 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
224 rustc_path: opt_path(matches, "rustc-path"),
225 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
226 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
227 python: matches.opt_str("python").unwrap(),
228 jsondocck_path: matches.opt_str("jsondocck-path"),
229 valgrind_path: matches.opt_str("valgrind-path"),
230 force_valgrind: matches.opt_present("force-valgrind"),
231 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
232 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
233 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
235 build_base: opt_path(matches, "build-base"),
236 stage_id: matches.opt_str("stage-id").unwrap(),
238 suite: matches.opt_str("suite").unwrap(),
241 filters: matches.free.clone(),
242 skip: matches.opt_strs("skip"),
243 filter_exact: matches.opt_present("exact"),
244 force_pass_mode: matches.opt_str("pass").map(|mode| {
245 mode.parse::<PassMode>()
246 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
248 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
250 "always" => Some(true),
251 "never" => Some(false),
252 _ => panic!("unknown `--run` option `{}` given", mode),
254 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
255 runtool: matches.opt_str("runtool"),
256 host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
257 target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
258 optimize_tests: matches.opt_present("optimize-tests"),
259 target_panic: match matches.opt_str("target-panic").as_deref() {
260 Some("unwind") | None => PanicStrategy::Unwind,
261 Some("abort") => PanicStrategy::Abort,
262 _ => panic!("unknown `--target-panic` option `{}` given", mode),
265 host: opt_str2(matches.opt_str("host")),
274 system_llvm: matches.opt_present("system-llvm"),
276 adb_path: opt_str2(matches.opt_str("adb-path")),
277 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
278 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
279 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
280 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
281 lldb_python_dir: matches.opt_str("lldb-python-dir"),
282 verbose: matches.opt_present("verbose"),
283 quiet: matches.opt_present("quiet"),
285 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
286 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
287 rustfix_coverage: matches.opt_present("rustfix-coverage"),
289 channel: matches.opt_str("channel").unwrap(),
290 edition: matches.opt_str("edition"),
292 cc: matches.opt_str("cc").unwrap(),
293 cxx: matches.opt_str("cxx").unwrap(),
294 cflags: matches.opt_str("cflags").unwrap(),
295 cxxflags: matches.opt_str("cxxflags").unwrap(),
296 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
297 linker: matches.opt_str("linker"),
298 llvm_components: matches.opt_str("llvm-components").unwrap(),
299 nodejs: matches.opt_str("nodejs"),
300 npm: matches.opt_str("npm"),
302 force_rerun: matches.opt_present("force-rerun"),
304 target_cfg: LazyCell::new(),
308 pub fn log_config(config: &Config) {
310 logv(c, "configuration:".to_string());
311 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
312 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
313 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
314 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
315 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
316 logv(c, format!("src_base: {:?}", config.src_base.display()));
317 logv(c, format!("build_base: {:?}", config.build_base.display()));
318 logv(c, format!("stage_id: {}", config.stage_id));
319 logv(c, format!("mode: {}", config.mode));
320 logv(c, format!("run_ignored: {}", config.run_ignored));
321 logv(c, format!("filters: {:?}", config.filters));
322 logv(c, format!("skip: {:?}", config.skip));
323 logv(c, format!("filter_exact: {}", config.filter_exact));
326 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
328 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
329 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
330 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
331 logv(c, format!("target: {}", config.target));
332 logv(c, format!("host: {}", config.host));
333 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
334 logv(c, format!("adb_path: {:?}", config.adb_path));
335 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
336 logv(c, format!("adb_device_status: {}", config.adb_device_status));
337 logv(c, format!("ar: {}", config.ar));
338 logv(c, format!("linker: {:?}", config.linker));
339 logv(c, format!("verbose: {}", config.verbose));
340 logv(c, format!("quiet: {}", config.quiet));
341 logv(c, "\n".to_string());
344 pub fn opt_str(maybestr: &Option<String>) -> &str {
351 pub fn opt_str2(maybestr: Option<String>) -> String {
353 None => "(none)".to_owned(),
358 pub fn run_tests(config: Config) {
359 // If we want to collect rustfix coverage information,
360 // we first make sure that the coverage file does not exist.
361 // It will be created later on.
362 if config.rustfix_coverage {
363 let mut coverage_file_path = config.build_base.clone();
364 coverage_file_path.push("rustfix_missing_coverage.txt");
365 if coverage_file_path.exists() {
366 if let Err(e) = fs::remove_file(&coverage_file_path) {
367 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
372 // sadly osx needs some file descriptor limits raised for running tests in
373 // parallel (especially when we have lots and lots of child processes).
374 // For context, see #8904
376 raise_fd_limit::raise_fd_limit();
378 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
379 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
380 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
382 // Let tests know which target they're running as
383 env::set_var("TARGET", &config.target);
385 let opts = test_opts(&config);
387 let mut configs = Vec::new();
388 if let Mode::DebugInfo = config.mode {
389 // Debugging emscripten code doesn't make sense today
390 if !config.target.contains("emscripten") {
391 configs.extend(configure_cdb(&config));
392 configs.extend(configure_gdb(&config));
393 configs.extend(configure_lldb(&config));
396 configs.push(config.clone());
399 let mut tests = Vec::new();
401 make_tests(c, &mut tests);
404 let res = test::run_tests_console(&opts, tests);
408 // We want to report that the tests failed, but we also want to give
409 // some indication of just what tests we were running. Especially on
410 // CI, where there can be cross-compiled tests for a lot of
411 // architectures, without this critical information it can be quite
412 // easy to miss which tests failed, and as such fail to reproduce
413 // the failure locally.
416 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
418 config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(),
424 std::process::exit(1);
427 // We don't know if tests passed or not, but if there was an error
428 // during testing we don't want to just succeed (we may not have
429 // tested something), so fail.
431 // This should realistically "never" happen, so don't try to make
432 // this a pretty error message.
433 panic!("I/O failure during tests: {:?}", e);
438 fn configure_cdb(config: &Config) -> Option<Config> {
439 config.cdb.as_ref()?;
441 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
444 fn configure_gdb(config: &Config) -> Option<Config> {
447 if config.matches_env("msvc") {
451 if config.remote_test_client.is_some() && !config.target.contains("android") {
453 "WARNING: debuginfo tests are not available when \
459 if config.target.contains("android") {
461 "{} debug-info test uses tcp 5039 port.\
466 // android debug-info test uses remote debugger so, we test 1 thread
467 // at once as they're all sharing the same TCP port to communicate
470 // we should figure out how to lift this restriction! (run them all
471 // on different ports allocated dynamically).
472 env::set_var("RUST_TEST_THREADS", "1");
475 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
478 fn configure_lldb(config: &Config) -> Option<Config> {
479 config.lldb_python_dir.as_ref()?;
481 if let Some(350) = config.lldb_version {
483 "WARNING: The used version of LLDB (350) has a \
484 known issue that breaks debuginfo tests. See \
485 issue #32520 for more information. Skipping all \
491 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
494 pub fn test_opts(config: &Config) -> test::TestOpts {
496 exclude_should_panic: false,
497 filters: config.filters.clone(),
498 filter_exact: config.filter_exact,
499 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
500 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
501 logfile: config.logfile.clone(),
503 bench_benchmarks: true,
504 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
505 Ok(val) => &val != "0",
512 skip: config.skip.clone(),
514 options: test::Options::new(),
516 force_run_in_process: false,
520 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
521 debug!("making tests from {:?}", config.src_base.display());
522 let inputs = common_inputs_stamp(config);
523 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
524 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
527 /// Returns a stamp constructed from input files common to all test cases.
528 fn common_inputs_stamp(config: &Config) -> Stamp {
529 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
531 let mut stamp = Stamp::from_path(&config.rustc_path);
533 // Relevant pretty printer files
534 let pretty_printer_files = [
535 "src/etc/rust_types.py",
536 "src/etc/gdb_load_rust_pretty_printers.py",
537 "src/etc/gdb_lookup.py",
538 "src/etc/gdb_providers.py",
539 "src/etc/lldb_batchmode.py",
540 "src/etc/lldb_lookup.py",
541 "src/etc/lldb_providers.py",
543 for file in &pretty_printer_files {
544 let path = rust_src_dir.join(file);
545 stamp.add_path(&path);
548 stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
550 stamp.add_dir(&config.run_lib_path);
552 if let Some(ref rustdoc_path) = config.rustdoc_path {
553 stamp.add_path(&rustdoc_path);
554 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
557 // Compiletest itself.
558 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
563 fn collect_tests_from_dir(
566 relative_dir_path: &Path,
568 tests: &mut Vec<test::TestDescAndFn>,
569 ) -> io::Result<()> {
570 // Ignore directories that contain a file named `compiletest-ignore-dir`.
571 if dir.join("compiletest-ignore-dir").exists() {
575 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
576 let paths = TestPaths {
577 file: dir.to_path_buf(),
578 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
580 tests.extend(make_test(config, &paths, inputs));
584 // If we find a test foo/bar.rs, we have to build the
585 // output directory `$build/foo` so we can write
586 // `$build/foo/bar` into it. We do this *now* in this
587 // sequential loop because otherwise, if we do it in the
588 // tests themselves, they race for the privilege of
589 // creating the directories and sometimes fail randomly.
590 let build_dir = output_relative_path(config, relative_dir_path);
591 fs::create_dir_all(&build_dir).unwrap();
593 // Add each `.rs` file as a test, and recurse further on any
594 // subdirectories we find, except for `aux` directories.
595 for file in fs::read_dir(dir)? {
597 let file_path = file.path();
598 let file_name = file.file_name();
599 if is_test(&file_name) {
600 debug!("found test file: {:?}", file_path.display());
602 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
604 tests.extend(make_test(config, &paths, inputs))
605 } else if file_path.is_dir() {
606 let relative_file_path = relative_dir_path.join(file.file_name());
607 if &file_name != "auxiliary" {
608 debug!("found directory: {:?}", file_path.display());
609 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
612 debug!("found other file/directory: {:?}", file_path.display());
618 /// Returns true if `file_name` looks like a proper test file name.
619 pub fn is_test(file_name: &OsString) -> bool {
620 let file_name = file_name.to_str().unwrap();
622 if !file_name.ends_with(".rs") {
626 // `.`, `#`, and `~` are common temp-file prefixes.
627 let invalid_prefixes = &[".", "#", "~"];
628 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
631 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
632 let test_path = if config.mode == Mode::RunMake {
633 // Parse directives in the Makefile
634 testpaths.file.join("Makefile")
636 PathBuf::from(&testpaths.file)
638 let early_props = EarlyProps::from_file(config, &test_path);
640 // Incremental tests are special, they inherently cannot be run in parallel.
641 // `runtest::run` will be responsible for iterating over revisions.
642 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
645 early_props.revisions.iter().map(Some).collect()
651 std::fs::File::open(&test_path).expect("open test file to parse ignores");
652 let cfg = revision.map(|v| &**v);
653 let test_name = crate::make_test_name(config, testpaths, revision);
654 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
655 // Ignore tests that already run and are up to date with respect to inputs.
656 if !config.force_rerun {
657 desc.ignore |= is_up_to_date(
661 revision.map(|s| s.as_str()),
665 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
670 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
671 output_base_dir(config, testpaths, revision).join("stamp")
674 fn files_related_to_test(
676 testpaths: &TestPaths,
678 revision: Option<&str>,
680 let mut related = vec![];
682 if testpaths.file.is_dir() {
683 // run-make tests use their individual directory
684 for entry in WalkDir::new(&testpaths.file) {
685 let path = entry.unwrap().into_path();
691 related.push(testpaths.file.clone());
694 for aux in &props.aux {
695 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
700 for extension in UI_EXTENSIONS {
701 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
710 testpaths: &TestPaths,
712 revision: Option<&str>,
715 let stamp_name = stamp(config, testpaths, revision);
717 let contents = match fs::read_to_string(&stamp_name) {
719 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
720 Err(_) => return false,
722 let expected_hash = runtest::compute_stamp_hash(config);
723 if contents != expected_hash {
728 let mut inputs = inputs.clone();
729 for path in files_related_to_test(config, testpaths, props, revision) {
730 inputs.add_path(&path);
733 inputs < Stamp::from_path(&stamp_name)
736 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
742 fn from_path(path: &Path) -> Self {
743 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
744 stamp.add_path(path);
748 fn add_path(&mut self, path: &Path) {
749 let modified = fs::metadata(path)
750 .and_then(|metadata| metadata.modified())
751 .unwrap_or(SystemTime::UNIX_EPOCH);
752 self.time = self.time.max(modified);
755 fn add_dir(&mut self, path: &Path) {
756 for entry in WalkDir::new(path) {
757 let entry = entry.unwrap();
758 if entry.file_type().is_file() {
762 .and_then(|metadata| metadata.modified().ok())
763 .unwrap_or(SystemTime::UNIX_EPOCH);
764 self.time = self.time.max(modified);
772 testpaths: &TestPaths,
773 revision: Option<&String>,
774 ) -> test::TestName {
775 // Print the name of the file, relative to the repository root.
776 // `src_base` looks like `/path/to/rust/src/test/ui`
777 let root_directory = config.src_base.parent().unwrap().parent().unwrap().parent().unwrap();
778 let path = testpaths.file.strip_prefix(root_directory).unwrap();
779 let debugger = match config.debugger {
780 Some(d) => format!("-{}", d),
781 None => String::new(),
783 let mode_suffix = match config.compare_mode {
784 Some(ref mode) => format!(" ({})", mode.to_str()),
785 None => String::new(),
788 test::DynTestName(format!(
794 revision.map_or("".to_string(), |rev| format!("#{}", rev))
798 fn make_test_closure(
800 testpaths: &TestPaths,
801 revision: Option<&String>,
803 let config = config.clone();
804 let testpaths = testpaths.clone();
805 let revision = revision.cloned();
806 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
809 /// Returns `true` if the given target is an Android target for the
810 /// purposes of GDB testing.
811 fn is_android_gdb_target(target: &str) -> bool {
814 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
818 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
819 fn is_pc_windows_msvc_target(target: &str) -> bool {
820 target.ends_with("-pc-windows-msvc")
823 fn find_cdb(target: &str) -> Option<OsString> {
824 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
828 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
829 let cdb_arch = if cfg!(target_arch = "x86") {
831 } else if cfg!(target_arch = "x86_64") {
833 } else if cfg!(target_arch = "aarch64") {
835 } else if cfg!(target_arch = "arm") {
838 return None; // No compatible CDB.exe in the Windows 10 SDK
841 let mut path = PathBuf::new();
843 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
845 path.push(r"cdb.exe");
851 Some(path.into_os_string())
854 /// Returns Path to CDB
855 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
856 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
858 let mut version = None;
859 if let Some(cdb) = cdb.as_ref() {
860 if let Ok(output) = Command::new(cdb).arg("/version").output() {
861 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
862 version = extract_cdb_version(&first_line);
870 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
871 // Example full_version_line: "cdb version 10.0.18362.1"
872 let version = full_version_line.rsplit(' ').next()?;
873 let mut components = version.split('.');
874 let major: u16 = components.next().unwrap().parse().unwrap();
875 let minor: u16 = components.next().unwrap().parse().unwrap();
876 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
877 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
878 Some([major, minor, patch, build])
881 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
885 android_cross_path: &PathBuf,
886 ) -> (Option<String>, Option<u32>, bool) {
888 const GDB_FALLBACK: &str = "gdb";
890 const GDB_FALLBACK: &str = "gdb.exe";
892 const MIN_GDB_WITH_RUST: u32 = 7011010;
894 let fallback_gdb = || {
895 if is_android_gdb_target(target) {
896 let mut gdb_path = match android_cross_path.to_str() {
897 Some(x) => x.to_owned(),
898 None => panic!("cannot find android cross path"),
900 gdb_path.push_str("/bin/gdb");
903 GDB_FALLBACK.to_owned()
907 let gdb = match gdb {
908 None => fallback_gdb(),
909 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
910 Some(ref s) => s.to_owned(),
913 let mut version_line = None;
914 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
915 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
916 version_line = Some(first_line.to_string());
920 let version = match version_line {
921 Some(line) => extract_gdb_version(&line),
922 None => return (None, None, false),
925 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
927 (Some(gdb), version, gdb_native_rust)
930 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
931 let full_version_line = full_version_line.trim();
933 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
934 // of the ? sections being optional
936 // We will parse up to 3 digits for each component, ignoring the date
938 // We skip text in parentheses. This avoids accidentally parsing
939 // the openSUSE version, which looks like:
940 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
941 // This particular form is documented in the GNU coding standards:
942 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
944 let unbracketed_part = full_version_line.split('[').next().unwrap();
945 let mut splits = unbracketed_part.trim_end().rsplit(' ');
946 let version_string = splits.next().unwrap();
948 let mut splits = version_string.split('.');
949 let major = splits.next().unwrap();
950 let minor = splits.next().unwrap();
951 let patch = splits.next();
953 let major: u32 = major.parse().unwrap();
954 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
956 let minor = minor.parse().unwrap();
957 let patch: u32 = match patch {
958 Some(patch) => match patch.find(not_a_digit) {
959 None => patch.parse().unwrap(),
960 Some(idx) if idx > 3 => 0,
961 Some(idx) => patch[..idx].parse().unwrap(),
967 // There is no patch version after minor-date (e.g. "4-2012").
969 let minor = minor[..idx].parse().unwrap();
974 Some(((major * 1000) + minor) * 1000 + patch)
977 /// Returns (LLDB version, LLDB is rust-enabled)
978 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
979 // Extract the major LLDB version from the given version string.
980 // LLDB version strings are different for Apple and non-Apple platforms.
981 // The Apple variant looks like this:
983 // LLDB-179.5 (older versions)
984 // lldb-300.2.51 (new versions)
986 // We are only interested in the major version number, so this function
987 // will return `Some(179)` and `Some(300)` respectively.
989 // Upstream versions look like:
990 // lldb version 6.0.1
992 // There doesn't seem to be a way to correlate the Apple version
993 // with the upstream version, and since the tests were originally
994 // written against Apple versions, we make a fake Apple version by
995 // multiplying the first number by 100. This is a hack, but
996 // normally fine because the only non-Apple version we test is
999 let full_version_line = full_version_line.trim();
1001 if let Some(apple_ver) =
1002 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1004 if let Some(idx) = apple_ver.find(not_a_digit) {
1005 let version: u32 = apple_ver[..idx].parse().unwrap();
1006 return Some((version, full_version_line.contains("rust-enabled")));
1008 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1009 if let Some(idx) = lldb_ver.find(not_a_digit) {
1010 let version: u32 = lldb_ver[..idx].parse().ok()?;
1011 return Some((version * 100, full_version_line.contains("rust-enabled")));
1017 fn not_a_digit(c: char) -> bool {