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("", "lldb-python", "path to python to use for doc tests", "PATH")
65 .reqopt("", "docck-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 .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 .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort")
106 .optflag("", "verbose", "run tests verbosely, showing all output")
110 "overwrite stderr/stdout files instead of complaining about a mismatch",
112 .optflag("", "quiet", "print one character per test instead of one line")
113 .optopt("", "color", "coloring: auto, always, never", "WHEN")
114 .optopt("", "logfile", "file to log test execution to", "FILE")
115 .optopt("", "target", "the target to build for", "TARGET")
116 .optopt("", "host", "the host to build for", "HOST")
117 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
118 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
119 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
120 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
121 .optflag("", "system-llvm", "is LLVM the system LLVM")
122 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
123 .optopt("", "adb-path", "path to the android debugger", "PATH")
124 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
125 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
126 .reqopt("", "cc", "path to a C compiler", "PATH")
127 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
128 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
129 .optopt("", "ar", "path to an archiver", "PATH")
130 .optopt("", "linker", "path to a linker", "PATH")
131 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
132 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
133 .optopt("", "nodejs", "the name of nodejs", "PATH")
134 .optopt("", "npm", "the name of npm", "PATH")
135 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
139 "mode describing what file the actual ui output will be compared to",
145 "enable this to generate a Rustfix coverage file, which is saved in \
146 `./<build_base>/rustfix_missing_coverage.txt`",
148 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
149 .optflag("h", "help", "show this message")
150 .reqopt("", "channel", "current Rust channel", "CHANNEL")
151 .optopt("", "edition", "default Rust edition", "EDITION");
153 let (argv0, args_) = args.split_first().unwrap();
154 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
155 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
156 println!("{}", opts.usage(&message));
161 let matches = &match opts.parse(args_) {
163 Err(f) => panic!("{:?}", f),
166 if matches.opt_present("h") || matches.opt_present("help") {
167 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
168 println!("{}", opts.usage(&message));
173 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
174 match m.opt_str(nm) {
175 Some(s) => PathBuf::from(&s),
176 None => panic!("no option (=path) found for {}", nm),
180 fn make_absolute(path: PathBuf) -> PathBuf {
181 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
184 let target = opt_str2(matches.opt_str("target"));
185 let android_cross_path = opt_path(matches, "android-cross-path");
186 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
187 let (gdb, gdb_version, gdb_native_rust) =
188 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
189 let (lldb_version, lldb_native_rust) = matches
190 .opt_str("lldb-version")
192 .and_then(extract_lldb_version)
193 .map(|(v, b)| (Some(v), b))
194 .unwrap_or((None, false));
195 let color = match matches.opt_str("color").as_deref() {
196 Some("auto") | None => ColorConfig::AutoColor,
197 Some("always") => ColorConfig::AlwaysColor,
198 Some("never") => ColorConfig::NeverColor,
199 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
202 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
204 let src_base = opt_path(matches, "src-base");
205 let run_ignored = matches.opt_present("ignored");
206 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
207 let has_tidy = if mode == Mode::Rustdoc {
210 .stdout(Stdio::null())
212 .map_or(false, |status| status.success())
214 // Avoid spawning an external command when we know tidy won't be used.
218 bless: matches.opt_present("bless"),
219 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
220 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
221 rustc_path: opt_path(matches, "rustc-path"),
222 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
223 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
224 lldb_python: matches.opt_str("lldb-python").unwrap(),
225 docck_python: matches.opt_str("docck-python").unwrap(),
226 jsondocck_path: matches.opt_str("jsondocck-path"),
227 valgrind_path: matches.opt_str("valgrind-path"),
228 force_valgrind: matches.opt_present("force-valgrind"),
229 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
230 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
231 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
233 build_base: opt_path(matches, "build-base"),
234 stage_id: matches.opt_str("stage-id").unwrap(),
236 suite: matches.opt_str("suite").unwrap(),
239 filters: matches.free.clone(),
240 filter_exact: matches.opt_present("exact"),
241 force_pass_mode: matches.opt_str("pass").map(|mode| {
242 mode.parse::<PassMode>()
243 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
245 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
247 "always" => Some(true),
248 "never" => Some(false),
249 _ => panic!("unknown `--run` option `{}` given", mode),
251 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
252 runtool: matches.opt_str("runtool"),
253 host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
254 target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
255 target_panic: match matches.opt_str("target-panic").as_deref() {
256 Some("unwind") | None => PanicStrategy::Unwind,
257 Some("abort") => PanicStrategy::Abort,
258 _ => panic!("unknown `--target-panic` option `{}` given", mode),
261 host: opt_str2(matches.opt_str("host")),
270 system_llvm: matches.opt_present("system-llvm"),
272 adb_path: opt_str2(matches.opt_str("adb-path")),
273 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
274 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
275 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
276 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
277 lldb_python_dir: matches.opt_str("lldb-python-dir"),
278 verbose: matches.opt_present("verbose"),
279 quiet: matches.opt_present("quiet"),
281 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
282 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
283 rustfix_coverage: matches.opt_present("rustfix-coverage"),
285 channel: matches.opt_str("channel").unwrap(),
286 edition: matches.opt_str("edition"),
288 cc: matches.opt_str("cc").unwrap(),
289 cxx: matches.opt_str("cxx").unwrap(),
290 cflags: matches.opt_str("cflags").unwrap(),
291 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
292 linker: matches.opt_str("linker"),
293 llvm_components: matches.opt_str("llvm-components").unwrap(),
294 nodejs: matches.opt_str("nodejs"),
295 npm: matches.opt_str("npm"),
297 force_rerun: matches.opt_present("force-rerun"),
301 pub fn log_config(config: &Config) {
303 logv(c, "configuration:".to_string());
304 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
305 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
306 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
307 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
308 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
309 logv(c, format!("src_base: {:?}", config.src_base.display()));
310 logv(c, format!("build_base: {:?}", config.build_base.display()));
311 logv(c, format!("stage_id: {}", config.stage_id));
312 logv(c, format!("mode: {}", config.mode));
313 logv(c, format!("run_ignored: {}", config.run_ignored));
314 logv(c, format!("filters: {:?}", config.filters));
315 logv(c, format!("filter_exact: {}", config.filter_exact));
318 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
320 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
321 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
322 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
323 logv(c, format!("target: {}", config.target));
324 logv(c, format!("host: {}", config.host));
325 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
326 logv(c, format!("adb_path: {:?}", config.adb_path));
327 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
328 logv(c, format!("adb_device_status: {}", config.adb_device_status));
329 logv(c, format!("ar: {}", config.ar));
330 logv(c, format!("linker: {:?}", config.linker));
331 logv(c, format!("verbose: {}", config.verbose));
332 logv(c, format!("quiet: {}", config.quiet));
333 logv(c, "\n".to_string());
336 pub fn opt_str(maybestr: &Option<String>) -> &str {
343 pub fn opt_str2(maybestr: Option<String>) -> String {
345 None => "(none)".to_owned(),
350 pub fn run_tests(config: Config) {
351 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
352 if let Mode::CodegenUnits = config.mode {
353 let _ = fs::remove_dir_all("tmp/partitioning-tests");
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 older versions of LLDB seem to have problems with multiple
489 // instances running in parallel, so only run one test thread at a
491 env::set_var("RUST_TEST_THREADS", "1");
493 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
496 pub fn test_opts(config: &Config) -> test::TestOpts {
498 exclude_should_panic: false,
499 filters: config.filters.clone(),
500 filter_exact: config.filter_exact,
501 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
502 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
503 logfile: config.logfile.clone(),
505 bench_benchmarks: true,
506 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
507 Ok(val) => &val != "0",
516 options: test::Options::new(),
518 force_run_in_process: false,
522 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
523 debug!("making tests from {:?}", config.src_base.display());
524 let inputs = common_inputs_stamp(config);
525 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
526 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
529 /// Returns a stamp constructed from input files common to all test cases.
530 fn common_inputs_stamp(config: &Config) -> Stamp {
531 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
533 let mut stamp = Stamp::from_path(&config.rustc_path);
535 // Relevant pretty printer files
536 let pretty_printer_files = [
537 "src/etc/rust_types.py",
538 "src/etc/gdb_load_rust_pretty_printers.py",
539 "src/etc/gdb_lookup.py",
540 "src/etc/gdb_providers.py",
541 "src/etc/lldb_batchmode.py",
542 "src/etc/lldb_lookup.py",
543 "src/etc/lldb_providers.py",
545 for file in &pretty_printer_files {
546 let path = rust_src_dir.join(file);
547 stamp.add_path(&path);
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() };
603 tests.extend(make_test(config, &paths, inputs))
604 } else if file_path.is_dir() {
605 let relative_file_path = relative_dir_path.join(file.file_name());
606 if &file_name != "auxiliary" {
607 debug!("found directory: {:?}", file_path.display());
608 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
611 debug!("found other file/directory: {:?}", file_path.display());
617 /// Returns true if `file_name` looks like a proper test file name.
618 pub fn is_test(file_name: &OsString) -> bool {
619 let file_name = file_name.to_str().unwrap();
621 if !file_name.ends_with(".rs") {
625 // `.`, `#`, and `~` are common temp-file prefixes.
626 let invalid_prefixes = &[".", "#", "~"];
627 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
630 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
631 let test_path = if config.mode == Mode::RunMake {
632 // Parse directives in the Makefile
633 testpaths.file.join("Makefile")
635 PathBuf::from(&testpaths.file)
637 let early_props = EarlyProps::from_file(config, &test_path);
639 // Incremental tests are special, they inherently cannot be run in parallel.
640 // `runtest::run` will be responsible for iterating over revisions.
641 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
644 early_props.revisions.iter().map(Some).collect()
650 std::fs::File::open(&test_path).expect("open test file to parse ignores");
651 let cfg = revision.map(|v| &**v);
652 let test_name = crate::make_test_name(config, testpaths, revision);
653 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
654 // Ignore tests that already run and are up to date with respect to inputs.
655 if !config.force_rerun {
656 desc.ignore |= is_up_to_date(
660 revision.map(|s| s.as_str()),
664 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
669 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
670 output_base_dir(config, testpaths, revision).join("stamp")
675 testpaths: &TestPaths,
677 revision: Option<&str>,
680 let stamp_name = stamp(config, testpaths, revision);
682 let contents = match fs::read_to_string(&stamp_name) {
684 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
685 Err(_) => return false,
687 let expected_hash = runtest::compute_stamp_hash(config);
688 if contents != expected_hash {
693 let mut inputs = inputs.clone();
694 // Use `add_dir` to account for run-make tests, which use their individual directory
695 inputs.add_dir(&testpaths.file);
697 for aux in &props.aux {
698 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
699 inputs.add_path(&path);
703 for extension in UI_EXTENSIONS {
704 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
705 inputs.add_path(path);
708 inputs < Stamp::from_path(&stamp_name)
711 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
717 fn from_path(path: &Path) -> Self {
718 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
719 stamp.add_path(path);
723 fn add_path(&mut self, path: &Path) {
724 let modified = fs::metadata(path)
725 .and_then(|metadata| metadata.modified())
726 .unwrap_or(SystemTime::UNIX_EPOCH);
727 self.time = self.time.max(modified);
730 fn add_dir(&mut self, path: &Path) {
731 for entry in WalkDir::new(path) {
732 let entry = entry.unwrap();
733 if entry.file_type().is_file() {
737 .and_then(|metadata| metadata.modified().ok())
738 .unwrap_or(SystemTime::UNIX_EPOCH);
739 self.time = self.time.max(modified);
747 testpaths: &TestPaths,
748 revision: Option<&String>,
749 ) -> test::TestName {
750 // Convert a complete path to something like
753 let path = PathBuf::from(config.src_base.file_name().unwrap())
754 .join(&testpaths.relative_dir)
755 .join(&testpaths.file.file_name().unwrap());
756 let debugger = match config.debugger {
757 Some(d) => format!("-{}", d),
758 None => String::new(),
760 let mode_suffix = match config.compare_mode {
761 Some(ref mode) => format!(" ({})", mode.to_str()),
762 None => String::new(),
765 test::DynTestName(format!(
771 revision.map_or("".to_string(), |rev| format!("#{}", rev))
775 fn make_test_closure(
777 testpaths: &TestPaths,
778 revision: Option<&String>,
780 let config = config.clone();
781 let testpaths = testpaths.clone();
782 let revision = revision.cloned();
783 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
786 /// Returns `true` if the given target is an Android target for the
787 /// purposes of GDB testing.
788 fn is_android_gdb_target(target: &str) -> bool {
791 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
795 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
796 fn is_pc_windows_msvc_target(target: &str) -> bool {
797 target.ends_with("-pc-windows-msvc")
800 fn find_cdb(target: &str) -> Option<OsString> {
801 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
805 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
806 let cdb_arch = if cfg!(target_arch = "x86") {
808 } else if cfg!(target_arch = "x86_64") {
810 } else if cfg!(target_arch = "aarch64") {
812 } else if cfg!(target_arch = "arm") {
815 return None; // No compatible CDB.exe in the Windows 10 SDK
818 let mut path = PathBuf::new();
820 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
822 path.push(r"cdb.exe");
828 Some(path.into_os_string())
831 /// Returns Path to CDB
832 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
833 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
835 let mut version = None;
836 if let Some(cdb) = cdb.as_ref() {
837 if let Ok(output) = Command::new(cdb).arg("/version").output() {
838 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
839 version = extract_cdb_version(&first_line);
847 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
848 // Example full_version_line: "cdb version 10.0.18362.1"
849 let version = full_version_line.rsplit(' ').next()?;
850 let mut components = version.split('.');
851 let major: u16 = components.next().unwrap().parse().unwrap();
852 let minor: u16 = components.next().unwrap().parse().unwrap();
853 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
854 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
855 Some([major, minor, patch, build])
858 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
862 android_cross_path: &PathBuf,
863 ) -> (Option<String>, Option<u32>, bool) {
865 const GDB_FALLBACK: &str = "gdb";
867 const GDB_FALLBACK: &str = "gdb.exe";
869 const MIN_GDB_WITH_RUST: u32 = 7011010;
871 let fallback_gdb = || {
872 if is_android_gdb_target(target) {
873 let mut gdb_path = match android_cross_path.to_str() {
874 Some(x) => x.to_owned(),
875 None => panic!("cannot find android cross path"),
877 gdb_path.push_str("/bin/gdb");
880 GDB_FALLBACK.to_owned()
884 let gdb = match gdb {
885 None => fallback_gdb(),
886 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
887 Some(ref s) => s.to_owned(),
890 let mut version_line = None;
891 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
892 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
893 version_line = Some(first_line.to_string());
897 let version = match version_line {
898 Some(line) => extract_gdb_version(&line),
899 None => return (None, None, false),
902 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
904 (Some(gdb), version, gdb_native_rust)
907 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
908 let full_version_line = full_version_line.trim();
910 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
911 // of the ? sections being optional
913 // We will parse up to 3 digits for each component, ignoring the date
915 // We skip text in parentheses. This avoids accidentally parsing
916 // the openSUSE version, which looks like:
917 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
918 // This particular form is documented in the GNU coding standards:
919 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
921 let unbracketed_part = full_version_line.split('[').next().unwrap();
922 let mut splits = unbracketed_part.trim_end().rsplit(' ');
923 let version_string = splits.next().unwrap();
925 let mut splits = version_string.split('.');
926 let major = splits.next().unwrap();
927 let minor = splits.next().unwrap();
928 let patch = splits.next();
930 let major: u32 = major.parse().unwrap();
931 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
933 let minor = minor.parse().unwrap();
934 let patch: u32 = match patch {
935 Some(patch) => match patch.find(not_a_digit) {
936 None => patch.parse().unwrap(),
937 Some(idx) if idx > 3 => 0,
938 Some(idx) => patch[..idx].parse().unwrap(),
944 // There is no patch version after minor-date (e.g. "4-2012").
946 let minor = minor[..idx].parse().unwrap();
951 Some(((major * 1000) + minor) * 1000 + patch)
954 /// Returns (LLDB version, LLDB is rust-enabled)
955 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
956 // Extract the major LLDB version from the given version string.
957 // LLDB version strings are different for Apple and non-Apple platforms.
958 // The Apple variant looks like this:
960 // LLDB-179.5 (older versions)
961 // lldb-300.2.51 (new versions)
963 // We are only interested in the major version number, so this function
964 // will return `Some(179)` and `Some(300)` respectively.
966 // Upstream versions look like:
967 // lldb version 6.0.1
969 // There doesn't seem to be a way to correlate the Apple version
970 // with the upstream version, and since the tests were originally
971 // written against Apple versions, we make a fake Apple version by
972 // multiplying the first number by 100. This is a hack, but
973 // normally fine because the only non-Apple version we test is
976 let full_version_line = full_version_line.trim();
978 if let Some(apple_ver) =
979 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
981 if let Some(idx) = apple_ver.find(not_a_digit) {
982 let version: u32 = apple_ver[..idx].parse().unwrap();
983 return Some((version, full_version_line.contains("rust-enabled")));
985 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
986 if let Some(idx) = lldb_ver.find(not_a_digit) {
987 let version: u32 = lldb_ver[..idx].parse().ok()?;
988 return Some((version * 100, full_version_line.contains("rust-enabled")));
994 fn not_a_digit(c: char) -> bool {