1 #![crate_name = "compiletest"]
2 // The `test` crate is the only unstable feature
3 // allowed here, just to share similar code.
8 use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
9 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
10 use crate::util::logv;
12 use lazycell::LazyCell;
14 use std::ffi::OsString;
16 use std::io::{self, ErrorKind};
17 use std::path::{Path, PathBuf};
18 use std::process::{Command, Stdio};
19 use std::time::SystemTime;
20 use test::ColorConfig;
24 use self::header::{make_test_description, EarlyProps};
40 tracing_subscriber::fmt::init();
42 let config = parse_config(env::args().collect());
44 if config.valgrind_path.is_none() && config.force_valgrind {
45 panic!("Can't find Valgrind to run Valgrind tests");
48 if !config.has_tidy && config.mode == Mode::Rustdoc {
49 eprintln!("warning: `tidy` is not installed; diffs will not be generated");
56 pub fn parse_config(args: Vec<String>) -> Config {
57 let mut opts = Options::new();
58 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
59 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
60 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
61 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
62 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
63 .reqopt("", "python", "path to python to use for doc tests", "PATH")
64 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
65 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
66 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
67 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
68 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
69 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
70 .reqopt("", "src-base", "directory to scan for test files", "PATH")
71 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
72 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "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 .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).or_else(
205 || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
208 let src_base = opt_path(matches, "src-base");
209 let run_ignored = matches.opt_present("ignored");
210 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
211 let has_tidy = if mode == Mode::Rustdoc {
214 .stdout(Stdio::null())
216 .map_or(false, |status| status.success())
218 // Avoid spawning an external command when we know tidy won't be used.
222 bless: matches.opt_present("bless"),
223 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
224 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
225 rustc_path: opt_path(matches, "rustc-path"),
226 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
227 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
228 python: matches.opt_str("python").unwrap(),
229 jsondocck_path: matches.opt_str("jsondocck-path"),
230 jsondoclint_path: matches.opt_str("jsondoclint-path"),
231 valgrind_path: matches.opt_str("valgrind-path"),
232 force_valgrind: matches.opt_present("force-valgrind"),
233 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
234 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
235 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
237 build_base: opt_path(matches, "build-base"),
238 sysroot_base: opt_path(matches, "sysroot-base"),
239 stage_id: matches.opt_str("stage-id").unwrap(),
241 suite: matches.opt_str("suite").unwrap(),
244 filters: matches.free.clone(),
245 skip: matches.opt_strs("skip"),
246 filter_exact: matches.opt_present("exact"),
247 force_pass_mode: matches.opt_str("pass").map(|mode| {
248 mode.parse::<PassMode>()
249 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
251 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
253 "always" => Some(true),
254 "never" => Some(false),
255 _ => panic!("unknown `--run` option `{}` given", mode),
257 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
258 runtool: matches.opt_str("runtool"),
259 host_rustcflags: matches.opt_strs("host-rustcflags"),
260 target_rustcflags: matches.opt_strs("target-rustcflags"),
261 optimize_tests: matches.opt_present("optimize-tests"),
263 host: opt_str2(matches.opt_str("host")),
272 system_llvm: matches.opt_present("system-llvm"),
274 adb_path: opt_str2(matches.opt_str("adb-path")),
275 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
276 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
277 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
278 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
279 lldb_python_dir: matches.opt_str("lldb-python-dir"),
280 verbose: matches.opt_present("verbose"),
281 quiet: matches.opt_present("quiet"),
283 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
284 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
285 rustfix_coverage: matches.opt_present("rustfix-coverage"),
287 channel: matches.opt_str("channel").unwrap(),
288 edition: matches.opt_str("edition"),
290 cc: matches.opt_str("cc").unwrap(),
291 cxx: matches.opt_str("cxx").unwrap(),
292 cflags: matches.opt_str("cflags").unwrap(),
293 cxxflags: matches.opt_str("cxxflags").unwrap(),
294 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
295 linker: matches.opt_str("linker"),
296 llvm_components: matches.opt_str("llvm-components").unwrap(),
297 nodejs: matches.opt_str("nodejs"),
298 npm: matches.opt_str("npm"),
300 force_rerun: matches.opt_present("force-rerun"),
302 target_cfg: LazyCell::new(),
306 pub fn log_config(config: &Config) {
308 logv(c, "configuration:".to_string());
309 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
310 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
311 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
312 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
313 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
314 logv(c, format!("src_base: {:?}", config.src_base.display()));
315 logv(c, format!("build_base: {:?}", config.build_base.display()));
316 logv(c, format!("stage_id: {}", config.stage_id));
317 logv(c, format!("mode: {}", config.mode));
318 logv(c, format!("run_ignored: {}", config.run_ignored));
319 logv(c, format!("filters: {:?}", config.filters));
320 logv(c, format!("skip: {:?}", config.skip));
321 logv(c, format!("filter_exact: {}", config.filter_exact));
324 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
326 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
327 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
328 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
329 logv(c, format!("target: {}", config.target));
330 logv(c, format!("host: {}", config.host));
331 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
332 logv(c, format!("adb_path: {:?}", config.adb_path));
333 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
334 logv(c, format!("adb_device_status: {}", config.adb_device_status));
335 logv(c, format!("ar: {}", config.ar));
336 logv(c, format!("linker: {:?}", config.linker));
337 logv(c, format!("verbose: {}", config.verbose));
338 logv(c, format!("quiet: {}", config.quiet));
339 logv(c, "\n".to_string());
342 pub fn opt_str(maybestr: &Option<String>) -> &str {
349 pub fn opt_str2(maybestr: Option<String>) -> String {
351 None => "(none)".to_owned(),
356 pub fn run_tests(config: Config) {
357 // If we want to collect rustfix coverage information,
358 // we first make sure that the coverage file does not exist.
359 // It will be created later on.
360 if config.rustfix_coverage {
361 let mut coverage_file_path = config.build_base.clone();
362 coverage_file_path.push("rustfix_missing_coverage.txt");
363 if coverage_file_path.exists() {
364 if let Err(e) = fs::remove_file(&coverage_file_path) {
365 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
370 // sadly osx needs some file descriptor limits raised for running tests in
371 // parallel (especially when we have lots and lots of child processes).
372 // For context, see #8904
374 raise_fd_limit::raise_fd_limit();
376 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
377 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
378 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
380 // Let tests know which target they're running as
381 env::set_var("TARGET", &config.target);
383 let opts = test_opts(&config);
385 let mut configs = Vec::new();
386 if let Mode::DebugInfo = config.mode {
387 // Debugging emscripten code doesn't make sense today
388 if !config.target.contains("emscripten") {
389 configs.extend(configure_cdb(&config));
390 configs.extend(configure_gdb(&config));
391 configs.extend(configure_lldb(&config));
394 configs.push(config.clone());
397 let mut tests = Vec::new();
399 make_tests(c, &mut tests);
402 tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
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,
517 fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
521 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
522 debug!("making tests from {:?}", config.src_base.display());
523 let inputs = common_inputs_stamp(config);
524 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
525 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
528 /// Returns a stamp constructed from input files common to all test cases.
529 fn common_inputs_stamp(config: &Config) -> Stamp {
530 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
532 let mut stamp = Stamp::from_path(&config.rustc_path);
534 // Relevant pretty printer files
535 let pretty_printer_files = [
536 "src/etc/rust_types.py",
537 "src/etc/gdb_load_rust_pretty_printers.py",
538 "src/etc/gdb_lookup.py",
539 "src/etc/gdb_providers.py",
540 "src/etc/lldb_batchmode.py",
541 "src/etc/lldb_lookup.py",
542 "src/etc/lldb_providers.py",
544 for file in &pretty_printer_files {
545 let path = rust_src_dir.join(file);
546 stamp.add_path(&path);
549 stamp.add_dir(&rust_src_dir.join("src/etc/natvis"));
551 stamp.add_dir(&config.run_lib_path);
553 if let Some(ref rustdoc_path) = config.rustdoc_path {
554 stamp.add_path(&rustdoc_path);
555 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
558 // Compiletest itself.
559 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
564 fn collect_tests_from_dir(
567 relative_dir_path: &Path,
569 tests: &mut Vec<test::TestDescAndFn>,
570 ) -> io::Result<()> {
571 // Ignore directories that contain a file named `compiletest-ignore-dir`.
572 if dir.join("compiletest-ignore-dir").exists() {
576 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
577 let paths = TestPaths {
578 file: dir.to_path_buf(),
579 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
581 tests.extend(make_test(config, &paths, inputs));
585 // If we find a test foo/bar.rs, we have to build the
586 // output directory `$build/foo` so we can write
587 // `$build/foo/bar` into it. We do this *now* in this
588 // sequential loop because otherwise, if we do it in the
589 // tests themselves, they race for the privilege of
590 // creating the directories and sometimes fail randomly.
591 let build_dir = output_relative_path(config, relative_dir_path);
592 fs::create_dir_all(&build_dir).unwrap();
594 // Add each `.rs` file as a test, and recurse further on any
595 // subdirectories we find, except for `aux` directories.
596 for file in fs::read_dir(dir)? {
598 let file_path = file.path();
599 let file_name = file.file_name();
600 if is_test(&file_name) {
601 debug!("found test file: {:?}", file_path.display());
603 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
605 tests.extend(make_test(config, &paths, inputs))
606 } else if file_path.is_dir() {
607 let relative_file_path = relative_dir_path.join(file.file_name());
608 if &file_name != "auxiliary" {
609 debug!("found directory: {:?}", file_path.display());
610 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
613 debug!("found other file/directory: {:?}", file_path.display());
619 /// Returns true if `file_name` looks like a proper test file name.
620 pub fn is_test(file_name: &OsString) -> bool {
621 let file_name = file_name.to_str().unwrap();
623 if !file_name.ends_with(".rs") {
627 // `.`, `#`, and `~` are common temp-file prefixes.
628 let invalid_prefixes = &[".", "#", "~"];
629 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
632 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
633 let test_path = if config.mode == Mode::RunMake {
634 // Parse directives in the Makefile
635 testpaths.file.join("Makefile")
637 PathBuf::from(&testpaths.file)
639 let early_props = EarlyProps::from_file(config, &test_path);
641 // Incremental tests are special, they inherently cannot be run in parallel.
642 // `runtest::run` will be responsible for iterating over revisions.
643 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
646 early_props.revisions.iter().map(Some).collect()
652 std::fs::File::open(&test_path).expect("open test file to parse ignores");
653 let cfg = revision.map(|v| &**v);
654 let test_name = crate::make_test_name(config, testpaths, revision);
655 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
656 // Ignore tests that already run and are up to date with respect to inputs.
657 if !config.force_rerun {
658 desc.ignore |= is_up_to_date(
662 revision.map(|s| s.as_str()),
666 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
671 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
672 output_base_dir(config, testpaths, revision).join("stamp")
675 fn files_related_to_test(
677 testpaths: &TestPaths,
679 revision: Option<&str>,
681 let mut related = vec![];
683 if testpaths.file.is_dir() {
684 // run-make tests use their individual directory
685 for entry in WalkDir::new(&testpaths.file) {
686 let path = entry.unwrap().into_path();
692 related.push(testpaths.file.clone());
695 for aux in &props.aux {
696 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
701 for extension in UI_EXTENSIONS {
702 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
711 testpaths: &TestPaths,
713 revision: Option<&str>,
716 let stamp_name = stamp(config, testpaths, revision);
718 let contents = match fs::read_to_string(&stamp_name) {
720 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
721 Err(_) => return false,
723 let expected_hash = runtest::compute_stamp_hash(config);
724 if contents != expected_hash {
729 let mut inputs = inputs.clone();
730 for path in files_related_to_test(config, testpaths, props, revision) {
731 inputs.add_path(&path);
734 inputs < Stamp::from_path(&stamp_name)
737 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
743 fn from_path(path: &Path) -> Self {
744 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
745 stamp.add_path(path);
749 fn add_path(&mut self, path: &Path) {
750 let modified = fs::metadata(path)
751 .and_then(|metadata| metadata.modified())
752 .unwrap_or(SystemTime::UNIX_EPOCH);
753 self.time = self.time.max(modified);
756 fn add_dir(&mut self, path: &Path) {
757 for entry in WalkDir::new(path) {
758 let entry = entry.unwrap();
759 if entry.file_type().is_file() {
763 .and_then(|metadata| metadata.modified().ok())
764 .unwrap_or(SystemTime::UNIX_EPOCH);
765 self.time = self.time.max(modified);
773 testpaths: &TestPaths,
774 revision: Option<&String>,
775 ) -> test::TestName {
776 // Print the name of the file, relative to the repository root.
777 // `src_base` looks like `/path/to/rust/tests/ui`
778 let root_directory = config.src_base.parent().unwrap().parent().unwrap().parent().unwrap();
779 let path = testpaths.file.strip_prefix(root_directory).unwrap();
780 let debugger = match config.debugger {
781 Some(d) => format!("-{}", d),
782 None => String::new(),
784 let mode_suffix = match config.compare_mode {
785 Some(ref mode) => format!(" ({})", mode.to_str()),
786 None => String::new(),
789 test::DynTestName(format!(
795 revision.map_or("".to_string(), |rev| format!("#{}", rev))
799 fn make_test_closure(
801 testpaths: &TestPaths,
802 revision: Option<&String>,
804 let config = config.clone();
805 let testpaths = testpaths.clone();
806 let revision = revision.cloned();
807 test::DynTestFn(Box::new(move || {
808 runtest::run(config, &testpaths, revision.as_deref());
813 /// Returns `true` if the given target is an Android target for the
814 /// purposes of GDB testing.
815 fn is_android_gdb_target(target: &str) -> bool {
818 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
822 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
823 fn is_pc_windows_msvc_target(target: &str) -> bool {
824 target.ends_with("-pc-windows-msvc")
827 fn find_cdb(target: &str) -> Option<OsString> {
828 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
832 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
833 let cdb_arch = if cfg!(target_arch = "x86") {
835 } else if cfg!(target_arch = "x86_64") {
837 } else if cfg!(target_arch = "aarch64") {
839 } else if cfg!(target_arch = "arm") {
842 return None; // No compatible CDB.exe in the Windows 10 SDK
845 let mut path = PathBuf::new();
847 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
849 path.push(r"cdb.exe");
855 Some(path.into_os_string())
858 /// Returns Path to CDB
859 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
860 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
862 let mut version = None;
863 if let Some(cdb) = cdb.as_ref() {
864 if let Ok(output) = Command::new(cdb).arg("/version").output() {
865 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
866 version = extract_cdb_version(&first_line);
874 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
875 // Example full_version_line: "cdb version 10.0.18362.1"
876 let version = full_version_line.rsplit(' ').next()?;
877 let mut components = version.split('.');
878 let major: u16 = components.next().unwrap().parse().unwrap();
879 let minor: u16 = components.next().unwrap().parse().unwrap();
880 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
881 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
882 Some([major, minor, patch, build])
885 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
889 android_cross_path: &PathBuf,
890 ) -> (Option<String>, Option<u32>, bool) {
892 const GDB_FALLBACK: &str = "gdb";
894 const GDB_FALLBACK: &str = "gdb.exe";
896 const MIN_GDB_WITH_RUST: u32 = 7011010;
898 let fallback_gdb = || {
899 if is_android_gdb_target(target) {
900 let mut gdb_path = match android_cross_path.to_str() {
901 Some(x) => x.to_owned(),
902 None => panic!("cannot find android cross path"),
904 gdb_path.push_str("/bin/gdb");
907 GDB_FALLBACK.to_owned()
911 let gdb = match gdb {
912 None => fallback_gdb(),
913 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
914 Some(ref s) => s.to_owned(),
917 let mut version_line = None;
918 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
919 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
920 version_line = Some(first_line.to_string());
924 let version = match version_line {
925 Some(line) => extract_gdb_version(&line),
926 None => return (None, None, false),
929 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
931 (Some(gdb), version, gdb_native_rust)
934 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
935 let full_version_line = full_version_line.trim();
937 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
938 // of the ? sections being optional
940 // We will parse up to 3 digits for each component, ignoring the date
942 // We skip text in parentheses. This avoids accidentally parsing
943 // the openSUSE version, which looks like:
944 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
945 // This particular form is documented in the GNU coding standards:
946 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
948 let unbracketed_part = full_version_line.split('[').next().unwrap();
949 let mut splits = unbracketed_part.trim_end().rsplit(' ');
950 let version_string = splits.next().unwrap();
952 let mut splits = version_string.split('.');
953 let major = splits.next().unwrap();
954 let minor = splits.next().unwrap();
955 let patch = splits.next();
957 let major: u32 = major.parse().unwrap();
958 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
960 let minor = minor.parse().unwrap();
961 let patch: u32 = match patch {
962 Some(patch) => match patch.find(not_a_digit) {
963 None => patch.parse().unwrap(),
964 Some(idx) if idx > 3 => 0,
965 Some(idx) => patch[..idx].parse().unwrap(),
971 // There is no patch version after minor-date (e.g. "4-2012").
973 let minor = minor[..idx].parse().unwrap();
978 Some(((major * 1000) + minor) * 1000 + patch)
981 /// Returns (LLDB version, LLDB is rust-enabled)
982 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
983 // Extract the major LLDB version from the given version string.
984 // LLDB version strings are different for Apple and non-Apple platforms.
985 // The Apple variant looks like this:
987 // LLDB-179.5 (older versions)
988 // lldb-300.2.51 (new versions)
990 // We are only interested in the major version number, so this function
991 // will return `Some(179)` and `Some(300)` respectively.
993 // Upstream versions look like:
994 // lldb version 6.0.1
996 // There doesn't seem to be a way to correlate the Apple version
997 // with the upstream version, and since the tests were originally
998 // written against Apple versions, we make a fake Apple version by
999 // multiplying the first number by 100. This is a hack, but
1000 // normally fine because the only non-Apple version we test is
1003 let full_version_line = full_version_line.trim();
1005 if let Some(apple_ver) =
1006 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
1008 if let Some(idx) = apple_ver.find(not_a_digit) {
1009 let version: u32 = apple_ver[..idx].parse().unwrap();
1010 return Some((version, full_version_line.contains("rust-enabled")));
1012 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
1013 if let Some(idx) = lldb_ver.find(not_a_digit) {
1014 let version: u32 = lldb_ver[..idx].parse().ok()?;
1015 return Some((version * 100, full_version_line.contains("rust-enabled")));
1021 fn not_a_digit(c: char) -> bool {