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;
15 use std::ffi::OsString;
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;
25 use self::header::{make_test_description, EarlyProps};
41 tracing_subscriber::fmt::init();
43 let config = parse_config(env::args().collect());
45 if config.valgrind_path.is_none() && config.force_valgrind {
46 panic!("Can't find Valgrind to run Valgrind tests");
49 if !config.has_tidy && config.mode == Mode::Rustdoc {
50 eprintln!("warning: `tidy` is not installed; diffs will not be generated");
57 pub fn parse_config(args: Vec<String>) -> Config {
58 let mut opts = Options::new();
59 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
60 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
61 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
62 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
63 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
64 .reqopt("", "python", "path to python to use for doc tests", "PATH")
65 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
66 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
67 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
68 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
69 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
70 .reqopt("", "src-base", "directory to scan for test files", "PATH")
71 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
72 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
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",
83 "which suite of compile tests to run. used for nicer error reporting.",
89 "force {check,build,run}-pass tests to this mode.",
90 "check | build | run",
92 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
93 .optflag("", "ignored", "run tests marked as ignored")
94 .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING")
95 .optflag("", "exact", "filters match exactly")
99 "supervisor program to run tests under \
100 (eg. emulator, valgrind)",
103 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
104 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
105 .optflag("", "optimize-tests", "run tests with optimizations enabled")
106 .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
107 .optflag("", "verbose", "run tests verbosely, showing all output")
111 "overwrite stderr/stdout files instead of complaining about a mismatch",
113 .optflag("", "quiet", "print one character per test instead of one line")
114 .optopt("", "color", "coloring: auto, always, never", "WHEN")
115 .optopt("", "logfile", "file to log test execution to", "FILE")
116 .optopt("", "target", "the target to build for", "TARGET")
117 .optopt("", "host", "the host to build for", "HOST")
118 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
119 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
120 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
121 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
122 .optflag("", "system-llvm", "is LLVM the system LLVM")
123 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
124 .optopt("", "adb-path", "path to the android debugger", "PATH")
125 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
126 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
127 .reqopt("", "cc", "path to a C compiler", "PATH")
128 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
129 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
130 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
131 .optopt("", "ar", "path to an archiver", "PATH")
132 .optopt("", "linker", "path to a linker", "PATH")
133 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
134 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
135 .optopt("", "nodejs", "the name of nodejs", "PATH")
136 .optopt("", "npm", "the name of npm", "PATH")
137 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
141 "mode describing what file the actual ui output will be compared to",
147 "enable this to generate a Rustfix coverage file, which is saved in \
148 `./<build_base>/rustfix_missing_coverage.txt`",
150 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
151 .optflag("h", "help", "show this message")
152 .reqopt("", "channel", "current Rust channel", "CHANNEL")
153 .optopt("", "edition", "default Rust edition", "EDITION");
155 let (argv0, args_) = args.split_first().unwrap();
156 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
157 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
158 println!("{}", opts.usage(&message));
163 let matches = &match opts.parse(args_) {
165 Err(f) => panic!("{:?}", f),
168 if matches.opt_present("h") || matches.opt_present("help") {
169 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
170 println!("{}", opts.usage(&message));
175 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
176 match m.opt_str(nm) {
177 Some(s) => PathBuf::from(&s),
178 None => panic!("no option (=path) found for {}", nm),
182 fn make_absolute(path: PathBuf) -> PathBuf {
183 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
186 let target = opt_str2(matches.opt_str("target"));
187 let android_cross_path = opt_path(matches, "android-cross-path");
188 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
189 let (gdb, gdb_version, gdb_native_rust) =
190 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
191 let (lldb_version, lldb_native_rust) = matches
192 .opt_str("lldb-version")
194 .and_then(extract_lldb_version)
195 .map(|(v, b)| (Some(v), b))
196 .unwrap_or((None, false));
197 let color = match matches.opt_str("color").as_deref() {
198 Some("auto") | None => ColorConfig::AutoColor,
199 Some("always") => ColorConfig::AlwaysColor,
200 Some("never") => ColorConfig::NeverColor,
201 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
204 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
206 let src_base = opt_path(matches, "src-base");
207 let run_ignored = matches.opt_present("ignored");
208 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
209 let has_tidy = if mode == Mode::Rustdoc {
212 .stdout(Stdio::null())
214 .map_or(false, |status| status.success())
216 // Avoid spawning an external command when we know tidy won't be used.
220 bless: matches.opt_present("bless"),
221 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
222 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
223 rustc_path: opt_path(matches, "rustc-path"),
224 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
225 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
226 python: matches.opt_str("python").unwrap(),
227 jsondocck_path: matches.opt_str("jsondocck-path"),
228 valgrind_path: matches.opt_str("valgrind-path"),
229 force_valgrind: matches.opt_present("force-valgrind"),
230 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
231 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
232 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
234 build_base: opt_path(matches, "build-base"),
235 stage_id: matches.opt_str("stage-id").unwrap(),
237 suite: matches.opt_str("suite").unwrap(),
240 filters: matches.free.clone(),
241 skip: matches.opt_strs("skip"),
242 filter_exact: matches.opt_present("exact"),
243 force_pass_mode: matches.opt_str("pass").map(|mode| {
244 mode.parse::<PassMode>()
245 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
247 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
249 "always" => Some(true),
250 "never" => Some(false),
251 _ => panic!("unknown `--run` option `{}` given", mode),
253 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
254 runtool: matches.opt_str("runtool"),
255 host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
256 target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
257 optimize_tests: matches.opt_present("optimize-tests"),
258 target_panic: match matches.opt_str("target-panic").as_deref() {
259 Some("unwind") | None => PanicStrategy::Unwind,
260 Some("abort") => PanicStrategy::Abort,
261 _ => panic!("unknown `--target-panic` option `{}` given", mode),
264 host: opt_str2(matches.opt_str("host")),
273 system_llvm: matches.opt_present("system-llvm"),
275 adb_path: opt_str2(matches.opt_str("adb-path")),
276 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
277 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
278 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
279 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
280 lldb_python_dir: matches.opt_str("lldb-python-dir"),
281 verbose: matches.opt_present("verbose"),
282 quiet: matches.opt_present("quiet"),
284 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
285 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
286 rustfix_coverage: matches.opt_present("rustfix-coverage"),
288 channel: matches.opt_str("channel").unwrap(),
289 edition: matches.opt_str("edition"),
291 cc: matches.opt_str("cc").unwrap(),
292 cxx: matches.opt_str("cxx").unwrap(),
293 cflags: matches.opt_str("cflags").unwrap(),
294 cxxflags: matches.opt_str("cxxflags").unwrap(),
295 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
296 linker: matches.opt_str("linker"),
297 llvm_components: matches.opt_str("llvm-components").unwrap(),
298 nodejs: matches.opt_str("nodejs"),
299 npm: matches.opt_str("npm"),
301 force_rerun: matches.opt_present("force-rerun"),
305 pub fn log_config(config: &Config) {
307 logv(c, "configuration:".to_string());
308 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
309 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
310 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
311 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
312 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
313 logv(c, format!("src_base: {:?}", config.src_base.display()));
314 logv(c, format!("build_base: {:?}", config.build_base.display()));
315 logv(c, format!("stage_id: {}", config.stage_id));
316 logv(c, format!("mode: {}", config.mode));
317 logv(c, format!("run_ignored: {}", config.run_ignored));
318 logv(c, format!("filters: {:?}", config.filters));
319 logv(c, format!("skip: {:?}", config.skip));
320 logv(c, format!("filter_exact: {}", config.filter_exact));
323 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
325 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
326 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
327 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
328 logv(c, format!("target: {}", config.target));
329 logv(c, format!("host: {}", config.host));
330 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
331 logv(c, format!("adb_path: {:?}", config.adb_path));
332 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
333 logv(c, format!("adb_device_status: {}", config.adb_device_status));
334 logv(c, format!("ar: {}", config.ar));
335 logv(c, format!("linker: {:?}", config.linker));
336 logv(c, format!("verbose: {}", config.verbose));
337 logv(c, format!("quiet: {}", config.quiet));
338 logv(c, "\n".to_string());
341 pub fn opt_str(maybestr: &Option<String>) -> &str {
348 pub fn opt_str2(maybestr: Option<String>) -> String {
350 None => "(none)".to_owned(),
355 pub fn run_tests(config: Config) {
356 // If we want to collect rustfix coverage information,
357 // we first make sure that the coverage file does not exist.
358 // It will be created later on.
359 if config.rustfix_coverage {
360 let mut coverage_file_path = config.build_base.clone();
361 coverage_file_path.push("rustfix_missing_coverage.txt");
362 if coverage_file_path.exists() {
363 if let Err(e) = fs::remove_file(&coverage_file_path) {
364 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
369 // sadly osx needs some file descriptor limits raised for running tests in
370 // parallel (especially when we have lots and lots of child processes).
371 // For context, see #8904
373 raise_fd_limit::raise_fd_limit();
375 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
376 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
377 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
379 // Let tests know which target they're running as
380 env::set_var("TARGET", &config.target);
382 let opts = test_opts(&config);
384 let mut configs = Vec::new();
385 if let Mode::DebugInfo = config.mode {
386 // Debugging emscripten code doesn't make sense today
387 if !config.target.contains("emscripten") {
388 configs.extend(configure_cdb(&config));
389 configs.extend(configure_gdb(&config));
390 configs.extend(configure_lldb(&config));
393 configs.push(config.clone());
396 let mut tests = Vec::new();
398 make_tests(c, &mut tests);
401 let res = test::run_tests_console(&opts, tests);
405 // We want to report that the tests failed, but we also want to give
406 // some indication of just what tests we were running. Especially on
407 // CI, where there can be cross-compiled tests for a lot of
408 // architectures, without this critical information it can be quite
409 // easy to miss which tests failed, and as such fail to reproduce
410 // the failure locally.
413 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
415 config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(),
421 std::process::exit(1);
424 // We don't know if tests passed or not, but if there was an error
425 // during testing we don't want to just succeed (we may not have
426 // tested something), so fail.
428 // This should realistically "never" happen, so don't try to make
429 // this a pretty error message.
430 panic!("I/O failure during tests: {:?}", e);
435 fn configure_cdb(config: &Config) -> Option<Config> {
436 config.cdb.as_ref()?;
438 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
441 fn configure_gdb(config: &Config) -> Option<Config> {
444 if util::matches_env(&config.target, "msvc") {
448 if config.remote_test_client.is_some() && !config.target.contains("android") {
450 "WARNING: debuginfo tests are not available when \
456 if config.target.contains("android") {
458 "{} debug-info test uses tcp 5039 port.\
463 // android debug-info test uses remote debugger so, we test 1 thread
464 // at once as they're all sharing the same TCP port to communicate
467 // we should figure out how to lift this restriction! (run them all
468 // on different ports allocated dynamically).
469 env::set_var("RUST_TEST_THREADS", "1");
472 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
475 fn configure_lldb(config: &Config) -> Option<Config> {
476 config.lldb_python_dir.as_ref()?;
478 if let Some(350) = config.lldb_version {
480 "WARNING: The used version of LLDB (350) has a \
481 known issue that breaks debuginfo tests. See \
482 issue #32520 for more information. Skipping all \
488 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
491 pub fn test_opts(config: &Config) -> test::TestOpts {
493 exclude_should_panic: false,
494 filters: config.filters.clone(),
495 filter_exact: config.filter_exact,
496 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
497 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
498 logfile: config.logfile.clone(),
500 bench_benchmarks: true,
501 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
502 Ok(val) => &val != "0",
509 skip: config.skip.clone(),
511 options: test::Options::new(),
513 force_run_in_process: false,
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()));
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");
528 let mut stamp = Stamp::from_path(&config.rustc_path);
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",
540 for file in &pretty_printer_files {
541 let path = rust_src_dir.join(file);
542 stamp.add_path(&path);
545 stamp.add_dir(&config.run_lib_path);
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"));
552 // Compiletest itself.
553 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
558 fn collect_tests_from_dir(
561 relative_dir_path: &Path,
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() {
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(),
575 tests.extend(make_test(config, &paths, inputs));
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();
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)? {
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());
597 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
599 tests.extend(make_test(config, &paths, inputs))
600 } else if file_path.is_dir() {
601 let relative_file_path = relative_dir_path.join(file.file_name());
602 if &file_name != "auxiliary" {
603 debug!("found directory: {:?}", file_path.display());
604 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
607 debug!("found other file/directory: {:?}", file_path.display());
613 /// Returns true if `file_name` looks like a proper test file name.
614 pub fn is_test(file_name: &OsString) -> bool {
615 let file_name = file_name.to_str().unwrap();
617 if !file_name.ends_with(".rs") {
621 // `.`, `#`, and `~` are common temp-file prefixes.
622 let invalid_prefixes = &[".", "#", "~"];
623 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
626 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
627 let test_path = if config.mode == Mode::RunMake {
628 // Parse directives in the Makefile
629 testpaths.file.join("Makefile")
631 PathBuf::from(&testpaths.file)
633 let early_props = EarlyProps::from_file(config, &test_path);
635 // Incremental tests are special, they inherently cannot be run in parallel.
636 // `runtest::run` will be responsible for iterating over revisions.
637 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
640 early_props.revisions.iter().map(Some).collect()
646 std::fs::File::open(&test_path).expect("open test file to parse ignores");
647 let cfg = revision.map(|v| &**v);
648 let test_name = crate::make_test_name(config, testpaths, revision);
649 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
650 // Ignore tests that already run and are up to date with respect to inputs.
651 if !config.force_rerun {
652 desc.ignore |= is_up_to_date(
656 revision.map(|s| s.as_str()),
660 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
665 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
666 output_base_dir(config, testpaths, revision).join("stamp")
669 fn files_related_to_test(
671 testpaths: &TestPaths,
673 revision: Option<&str>,
675 let mut related = vec![];
677 if testpaths.file.is_dir() {
678 // run-make tests use their individual directory
679 for entry in WalkDir::new(&testpaths.file) {
680 let path = entry.unwrap().into_path();
686 related.push(testpaths.file.clone());
689 for aux in &props.aux {
690 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
695 for extension in UI_EXTENSIONS {
696 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
705 testpaths: &TestPaths,
707 revision: Option<&str>,
710 let stamp_name = stamp(config, testpaths, revision);
712 let contents = match fs::read_to_string(&stamp_name) {
714 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
715 Err(_) => return false,
717 let expected_hash = runtest::compute_stamp_hash(config);
718 if contents != expected_hash {
723 let mut inputs = inputs.clone();
724 for path in files_related_to_test(config, testpaths, props, revision) {
725 inputs.add_path(&path);
728 inputs < Stamp::from_path(&stamp_name)
731 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
737 fn from_path(path: &Path) -> Self {
738 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
739 stamp.add_path(path);
743 fn add_path(&mut self, path: &Path) {
744 let modified = fs::metadata(path)
745 .and_then(|metadata| metadata.modified())
746 .unwrap_or(SystemTime::UNIX_EPOCH);
747 self.time = self.time.max(modified);
750 fn add_dir(&mut self, path: &Path) {
751 for entry in WalkDir::new(path) {
752 let entry = entry.unwrap();
753 if entry.file_type().is_file() {
757 .and_then(|metadata| metadata.modified().ok())
758 .unwrap_or(SystemTime::UNIX_EPOCH);
759 self.time = self.time.max(modified);
767 testpaths: &TestPaths,
768 revision: Option<&String>,
769 ) -> test::TestName {
770 // Print the name of the file, relative to the repository root.
771 // `src_base` looks like `/path/to/rust/src/test/ui`
772 let root_directory = config.src_base.parent().unwrap().parent().unwrap().parent().unwrap();
773 let path = testpaths.file.strip_prefix(root_directory).unwrap();
774 let debugger = match config.debugger {
775 Some(d) => format!("-{}", d),
776 None => String::new(),
778 let mode_suffix = match config.compare_mode {
779 Some(ref mode) => format!(" ({})", mode.to_str()),
780 None => String::new(),
783 test::DynTestName(format!(
789 revision.map_or("".to_string(), |rev| format!("#{}", rev))
793 fn make_test_closure(
795 testpaths: &TestPaths,
796 revision: Option<&String>,
798 let config = config.clone();
799 let testpaths = testpaths.clone();
800 let revision = revision.cloned();
801 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
804 /// Returns `true` if the given target is an Android target for the
805 /// purposes of GDB testing.
806 fn is_android_gdb_target(target: &str) -> bool {
809 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
813 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
814 fn is_pc_windows_msvc_target(target: &str) -> bool {
815 target.ends_with("-pc-windows-msvc")
818 fn find_cdb(target: &str) -> Option<OsString> {
819 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
823 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
824 let cdb_arch = if cfg!(target_arch = "x86") {
826 } else if cfg!(target_arch = "x86_64") {
828 } else if cfg!(target_arch = "aarch64") {
830 } else if cfg!(target_arch = "arm") {
833 return None; // No compatible CDB.exe in the Windows 10 SDK
836 let mut path = PathBuf::new();
838 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
840 path.push(r"cdb.exe");
846 Some(path.into_os_string())
849 /// Returns Path to CDB
850 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
851 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
853 let mut version = None;
854 if let Some(cdb) = cdb.as_ref() {
855 if let Ok(output) = Command::new(cdb).arg("/version").output() {
856 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
857 version = extract_cdb_version(&first_line);
865 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
866 // Example full_version_line: "cdb version 10.0.18362.1"
867 let version = full_version_line.rsplit(' ').next()?;
868 let mut components = version.split('.');
869 let major: u16 = components.next().unwrap().parse().unwrap();
870 let minor: u16 = components.next().unwrap().parse().unwrap();
871 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
872 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
873 Some([major, minor, patch, build])
876 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
880 android_cross_path: &PathBuf,
881 ) -> (Option<String>, Option<u32>, bool) {
883 const GDB_FALLBACK: &str = "gdb";
885 const GDB_FALLBACK: &str = "gdb.exe";
887 const MIN_GDB_WITH_RUST: u32 = 7011010;
889 let fallback_gdb = || {
890 if is_android_gdb_target(target) {
891 let mut gdb_path = match android_cross_path.to_str() {
892 Some(x) => x.to_owned(),
893 None => panic!("cannot find android cross path"),
895 gdb_path.push_str("/bin/gdb");
898 GDB_FALLBACK.to_owned()
902 let gdb = match gdb {
903 None => fallback_gdb(),
904 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
905 Some(ref s) => s.to_owned(),
908 let mut version_line = None;
909 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
910 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
911 version_line = Some(first_line.to_string());
915 let version = match version_line {
916 Some(line) => extract_gdb_version(&line),
917 None => return (None, None, false),
920 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
922 (Some(gdb), version, gdb_native_rust)
925 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
926 let full_version_line = full_version_line.trim();
928 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
929 // of the ? sections being optional
931 // We will parse up to 3 digits for each component, ignoring the date
933 // We skip text in parentheses. This avoids accidentally parsing
934 // the openSUSE version, which looks like:
935 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
936 // This particular form is documented in the GNU coding standards:
937 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
939 let unbracketed_part = full_version_line.split('[').next().unwrap();
940 let mut splits = unbracketed_part.trim_end().rsplit(' ');
941 let version_string = splits.next().unwrap();
943 let mut splits = version_string.split('.');
944 let major = splits.next().unwrap();
945 let minor = splits.next().unwrap();
946 let patch = splits.next();
948 let major: u32 = major.parse().unwrap();
949 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
951 let minor = minor.parse().unwrap();
952 let patch: u32 = match patch {
953 Some(patch) => match patch.find(not_a_digit) {
954 None => patch.parse().unwrap(),
955 Some(idx) if idx > 3 => 0,
956 Some(idx) => patch[..idx].parse().unwrap(),
962 // There is no patch version after minor-date (e.g. "4-2012").
964 let minor = minor[..idx].parse().unwrap();
969 Some(((major * 1000) + minor) * 1000 + patch)
972 /// Returns (LLDB version, LLDB is rust-enabled)
973 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
974 // Extract the major LLDB version from the given version string.
975 // LLDB version strings are different for Apple and non-Apple platforms.
976 // The Apple variant looks like this:
978 // LLDB-179.5 (older versions)
979 // lldb-300.2.51 (new versions)
981 // We are only interested in the major version number, so this function
982 // will return `Some(179)` and `Some(300)` respectively.
984 // Upstream versions look like:
985 // lldb version 6.0.1
987 // There doesn't seem to be a way to correlate the Apple version
988 // with the upstream version, and since the tests were originally
989 // written against Apple versions, we make a fake Apple version by
990 // multiplying the first number by 100. This is a hack, but
991 // normally fine because the only non-Apple version we test is
994 let full_version_line = full_version_line.trim();
996 if let Some(apple_ver) =
997 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
999 if let Some(idx) = apple_ver.find(not_a_digit) {
1000 let version: u32 = apple_ver[..idx].parse().unwrap();
1001 return Some((version, full_version_line.contains("rust-enabled")));
1003 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1004 if let Some(idx) = lldb_ver.find(not_a_digit) {
1005 let version: u32 = lldb_ver[..idx].parse().ok()?;
1006 return Some((version * 100, full_version_line.contains("rust-enabled")));
1012 fn not_a_digit(c: char) -> bool {