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 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
130 .optopt("", "ar", "path to an archiver", "PATH")
131 .optopt("", "linker", "path to a linker", "PATH")
132 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
133 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
134 .optopt("", "nodejs", "the name of nodejs", "PATH")
135 .optopt("", "npm", "the name of npm", "PATH")
136 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
140 "mode describing what file the actual ui output will be compared to",
146 "enable this to generate a Rustfix coverage file, which is saved in \
147 `./<build_base>/rustfix_missing_coverage.txt`",
149 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
150 .optflag("h", "help", "show this message")
151 .reqopt("", "channel", "current Rust channel", "CHANNEL")
152 .optopt("", "edition", "default Rust edition", "EDITION");
154 let (argv0, args_) = args.split_first().unwrap();
155 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
156 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
157 println!("{}", opts.usage(&message));
162 let matches = &match opts.parse(args_) {
164 Err(f) => panic!("{:?}", f),
167 if matches.opt_present("h") || matches.opt_present("help") {
168 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
169 println!("{}", opts.usage(&message));
174 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
175 match m.opt_str(nm) {
176 Some(s) => PathBuf::from(&s),
177 None => panic!("no option (=path) found for {}", nm),
181 fn make_absolute(path: PathBuf) -> PathBuf {
182 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
185 let target = opt_str2(matches.opt_str("target"));
186 let android_cross_path = opt_path(matches, "android-cross-path");
187 let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
188 let (gdb, gdb_version, gdb_native_rust) =
189 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
190 let (lldb_version, lldb_native_rust) = matches
191 .opt_str("lldb-version")
193 .and_then(extract_lldb_version)
194 .map(|(v, b)| (Some(v), b))
195 .unwrap_or((None, false));
196 let color = match matches.opt_str("color").as_deref() {
197 Some("auto") | None => ColorConfig::AutoColor,
198 Some("always") => ColorConfig::AlwaysColor,
199 Some("never") => ColorConfig::NeverColor,
200 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
203 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
205 let src_base = opt_path(matches, "src-base");
206 let run_ignored = matches.opt_present("ignored");
207 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
208 let has_tidy = if mode == Mode::Rustdoc {
211 .stdout(Stdio::null())
213 .map_or(false, |status| status.success())
215 // Avoid spawning an external command when we know tidy won't be used.
219 bless: matches.opt_present("bless"),
220 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
221 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
222 rustc_path: opt_path(matches, "rustc-path"),
223 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
224 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
225 lldb_python: matches.opt_str("lldb-python").unwrap(),
226 docck_python: matches.opt_str("docck-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 filter_exact: matches.opt_present("exact"),
242 force_pass_mode: matches.opt_str("pass").map(|mode| {
243 mode.parse::<PassMode>()
244 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
246 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
248 "always" => Some(true),
249 "never" => Some(false),
250 _ => panic!("unknown `--run` option `{}` given", mode),
252 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
253 runtool: matches.opt_str("runtool"),
254 host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")),
255 target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")),
256 target_panic: match matches.opt_str("target-panic").as_deref() {
257 Some("unwind") | None => PanicStrategy::Unwind,
258 Some("abort") => PanicStrategy::Abort,
259 _ => panic!("unknown `--target-panic` option `{}` given", mode),
262 host: opt_str2(matches.opt_str("host")),
271 system_llvm: matches.opt_present("system-llvm"),
273 adb_path: opt_str2(matches.opt_str("adb-path")),
274 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
275 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
276 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
277 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
278 lldb_python_dir: matches.opt_str("lldb-python-dir"),
279 verbose: matches.opt_present("verbose"),
280 quiet: matches.opt_present("quiet"),
282 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
283 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
284 rustfix_coverage: matches.opt_present("rustfix-coverage"),
286 channel: matches.opt_str("channel").unwrap(),
287 edition: matches.opt_str("edition"),
289 cc: matches.opt_str("cc").unwrap(),
290 cxx: matches.opt_str("cxx").unwrap(),
291 cflags: matches.opt_str("cflags").unwrap(),
292 cxxflags: matches.opt_str("cxxflags").unwrap(),
293 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
294 linker: matches.opt_str("linker"),
295 llvm_components: matches.opt_str("llvm-components").unwrap(),
296 nodejs: matches.opt_str("nodejs"),
297 npm: matches.opt_str("npm"),
299 force_rerun: matches.opt_present("force-rerun"),
303 pub fn log_config(config: &Config) {
305 logv(c, "configuration:".to_string());
306 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
307 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
308 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
309 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
310 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
311 logv(c, format!("src_base: {:?}", config.src_base.display()));
312 logv(c, format!("build_base: {:?}", config.build_base.display()));
313 logv(c, format!("stage_id: {}", config.stage_id));
314 logv(c, format!("mode: {}", config.mode));
315 logv(c, format!("run_ignored: {}", config.run_ignored));
316 logv(c, format!("filters: {:?}", config.filters));
317 logv(c, format!("filter_exact: {}", config.filter_exact));
320 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
322 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
323 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
324 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
325 logv(c, format!("target: {}", config.target));
326 logv(c, format!("host: {}", config.host));
327 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
328 logv(c, format!("adb_path: {:?}", config.adb_path));
329 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
330 logv(c, format!("adb_device_status: {}", config.adb_device_status));
331 logv(c, format!("ar: {}", config.ar));
332 logv(c, format!("linker: {:?}", config.linker));
333 logv(c, format!("verbose: {}", config.verbose));
334 logv(c, format!("quiet: {}", config.quiet));
335 logv(c, "\n".to_string());
338 pub fn opt_str(maybestr: &Option<String>) -> &str {
345 pub fn opt_str2(maybestr: Option<String>) -> String {
347 None => "(none)".to_owned(),
352 pub fn run_tests(config: Config) {
353 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
354 if let Mode::CodegenUnits = config.mode {
355 let _ = fs::remove_dir_all("tmp/partitioning-tests");
358 // If we want to collect rustfix coverage information,
359 // we first make sure that the coverage file does not exist.
360 // It will be created later on.
361 if config.rustfix_coverage {
362 let mut coverage_file_path = config.build_base.clone();
363 coverage_file_path.push("rustfix_missing_coverage.txt");
364 if coverage_file_path.exists() {
365 if let Err(e) = fs::remove_file(&coverage_file_path) {
366 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
371 // sadly osx needs some file descriptor limits raised for running tests in
372 // parallel (especially when we have lots and lots of child processes).
373 // For context, see #8904
375 raise_fd_limit::raise_fd_limit();
377 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
378 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
379 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
381 // Let tests know which target they're running as
382 env::set_var("TARGET", &config.target);
384 let opts = test_opts(&config);
386 let mut configs = Vec::new();
387 if let Mode::DebugInfo = config.mode {
388 // Debugging emscripten code doesn't make sense today
389 if !config.target.contains("emscripten") {
390 configs.extend(configure_cdb(&config));
391 configs.extend(configure_gdb(&config));
392 configs.extend(configure_lldb(&config));
395 configs.push(config.clone());
398 let mut tests = Vec::new();
400 make_tests(c, &mut tests);
403 let res = test::run_tests_console(&opts, tests);
407 // We want to report that the tests failed, but we also want to give
408 // some indication of just what tests we were running. Especially on
409 // CI, where there can be cross-compiled tests for a lot of
410 // architectures, without this critical information it can be quite
411 // easy to miss which tests failed, and as such fail to reproduce
412 // the failure locally.
415 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
417 config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(),
423 std::process::exit(1);
426 // We don't know if tests passed or not, but if there was an error
427 // during testing we don't want to just succeed (we may not have
428 // tested something), so fail.
430 // This should realistically "never" happen, so don't try to make
431 // this a pretty error message.
432 panic!("I/O failure during tests: {:?}", e);
437 fn configure_cdb(config: &Config) -> Option<Config> {
438 config.cdb.as_ref()?;
440 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
443 fn configure_gdb(config: &Config) -> Option<Config> {
446 if util::matches_env(&config.target, "msvc") {
450 if config.remote_test_client.is_some() && !config.target.contains("android") {
452 "WARNING: debuginfo tests are not available when \
458 if config.target.contains("android") {
460 "{} debug-info test uses tcp 5039 port.\
465 // android debug-info test uses remote debugger so, we test 1 thread
466 // at once as they're all sharing the same TCP port to communicate
469 // we should figure out how to lift this restriction! (run them all
470 // on different ports allocated dynamically).
471 env::set_var("RUST_TEST_THREADS", "1");
474 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
477 fn configure_lldb(config: &Config) -> Option<Config> {
478 config.lldb_python_dir.as_ref()?;
480 if let Some(350) = config.lldb_version {
482 "WARNING: The used version of LLDB (350) has a \
483 known issue that breaks debuginfo tests. See \
484 issue #32520 for more information. Skipping all \
490 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
493 pub fn test_opts(config: &Config) -> test::TestOpts {
495 exclude_should_panic: false,
496 filters: config.filters.clone(),
497 filter_exact: config.filter_exact,
498 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
499 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
500 logfile: config.logfile.clone(),
502 bench_benchmarks: true,
503 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
504 Ok(val) => &val != "0",
513 options: test::Options::new(),
515 force_run_in_process: false,
519 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
520 debug!("making tests from {:?}", config.src_base.display());
521 let inputs = common_inputs_stamp(config);
522 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
523 .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
526 /// Returns a stamp constructed from input files common to all test cases.
527 fn common_inputs_stamp(config: &Config) -> Stamp {
528 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
530 let mut stamp = Stamp::from_path(&config.rustc_path);
532 // Relevant pretty printer files
533 let pretty_printer_files = [
534 "src/etc/rust_types.py",
535 "src/etc/gdb_load_rust_pretty_printers.py",
536 "src/etc/gdb_lookup.py",
537 "src/etc/gdb_providers.py",
538 "src/etc/lldb_batchmode.py",
539 "src/etc/lldb_lookup.py",
540 "src/etc/lldb_providers.py",
542 for file in &pretty_printer_files {
543 let path = rust_src_dir.join(file);
544 stamp.add_path(&path);
547 stamp.add_dir(&config.run_lib_path);
549 if let Some(ref rustdoc_path) = config.rustdoc_path {
550 stamp.add_path(&rustdoc_path);
551 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
554 // Compiletest itself.
555 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
560 fn collect_tests_from_dir(
563 relative_dir_path: &Path,
565 tests: &mut Vec<test::TestDescAndFn>,
566 ) -> io::Result<()> {
567 // Ignore directories that contain a file named `compiletest-ignore-dir`.
568 if dir.join("compiletest-ignore-dir").exists() {
572 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
573 let paths = TestPaths {
574 file: dir.to_path_buf(),
575 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
577 tests.extend(make_test(config, &paths, inputs));
581 // If we find a test foo/bar.rs, we have to build the
582 // output directory `$build/foo` so we can write
583 // `$build/foo/bar` into it. We do this *now* in this
584 // sequential loop because otherwise, if we do it in the
585 // tests themselves, they race for the privilege of
586 // creating the directories and sometimes fail randomly.
587 let build_dir = output_relative_path(config, relative_dir_path);
588 fs::create_dir_all(&build_dir).unwrap();
590 // Add each `.rs` file as a test, and recurse further on any
591 // subdirectories we find, except for `aux` directories.
592 for file in fs::read_dir(dir)? {
594 let file_path = file.path();
595 let file_name = file.file_name();
596 if is_test(&file_name) {
597 debug!("found test file: {:?}", file_path.display());
599 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
600 tests.extend(make_test(config, &paths, inputs))
601 } else if file_path.is_dir() {
602 let relative_file_path = relative_dir_path.join(file.file_name());
603 if &file_name != "auxiliary" {
604 debug!("found directory: {:?}", file_path.display());
605 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
608 debug!("found other file/directory: {:?}", file_path.display());
614 /// Returns true if `file_name` looks like a proper test file name.
615 pub fn is_test(file_name: &OsString) -> bool {
616 let file_name = file_name.to_str().unwrap();
618 if !file_name.ends_with(".rs") {
622 // `.`, `#`, and `~` are common temp-file prefixes.
623 let invalid_prefixes = &[".", "#", "~"];
624 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
627 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
628 let test_path = if config.mode == Mode::RunMake {
629 // Parse directives in the Makefile
630 testpaths.file.join("Makefile")
632 PathBuf::from(&testpaths.file)
634 let early_props = EarlyProps::from_file(config, &test_path);
636 // Incremental tests are special, they inherently cannot be run in parallel.
637 // `runtest::run` will be responsible for iterating over revisions.
638 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
641 early_props.revisions.iter().map(Some).collect()
647 std::fs::File::open(&test_path).expect("open test file to parse ignores");
648 let cfg = revision.map(|v| &**v);
649 let test_name = crate::make_test_name(config, testpaths, revision);
650 let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
651 // Ignore tests that already run and are up to date with respect to inputs.
652 if !config.force_rerun {
653 desc.ignore |= is_up_to_date(
657 revision.map(|s| s.as_str()),
661 test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
666 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
667 output_base_dir(config, testpaths, revision).join("stamp")
672 testpaths: &TestPaths,
674 revision: Option<&str>,
677 let stamp_name = stamp(config, testpaths, revision);
679 let contents = match fs::read_to_string(&stamp_name) {
681 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
682 Err(_) => return false,
684 let expected_hash = runtest::compute_stamp_hash(config);
685 if contents != expected_hash {
690 let mut inputs = inputs.clone();
691 // Use `add_dir` to account for run-make tests, which use their individual directory
692 inputs.add_dir(&testpaths.file);
694 for aux in &props.aux {
695 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
696 inputs.add_path(&path);
700 for extension in UI_EXTENSIONS {
701 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
702 inputs.add_path(path);
705 inputs < Stamp::from_path(&stamp_name)
708 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
714 fn from_path(path: &Path) -> Self {
715 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
716 stamp.add_path(path);
720 fn add_path(&mut self, path: &Path) {
721 let modified = fs::metadata(path)
722 .and_then(|metadata| metadata.modified())
723 .unwrap_or(SystemTime::UNIX_EPOCH);
724 self.time = self.time.max(modified);
727 fn add_dir(&mut self, path: &Path) {
728 for entry in WalkDir::new(path) {
729 let entry = entry.unwrap();
730 if entry.file_type().is_file() {
734 .and_then(|metadata| metadata.modified().ok())
735 .unwrap_or(SystemTime::UNIX_EPOCH);
736 self.time = self.time.max(modified);
744 testpaths: &TestPaths,
745 revision: Option<&String>,
746 ) -> test::TestName {
747 // Convert a complete path to something like
750 let path = PathBuf::from(config.src_base.file_name().unwrap())
751 .join(&testpaths.relative_dir)
752 .join(&testpaths.file.file_name().unwrap());
753 let debugger = match config.debugger {
754 Some(d) => format!("-{}", d),
755 None => String::new(),
757 let mode_suffix = match config.compare_mode {
758 Some(ref mode) => format!(" ({})", mode.to_str()),
759 None => String::new(),
762 test::DynTestName(format!(
768 revision.map_or("".to_string(), |rev| format!("#{}", rev))
772 fn make_test_closure(
774 testpaths: &TestPaths,
775 revision: Option<&String>,
777 let config = config.clone();
778 let testpaths = testpaths.clone();
779 let revision = revision.cloned();
780 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
783 /// Returns `true` if the given target is an Android target for the
784 /// purposes of GDB testing.
785 fn is_android_gdb_target(target: &str) -> bool {
788 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
792 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
793 fn is_pc_windows_msvc_target(target: &str) -> bool {
794 target.ends_with("-pc-windows-msvc")
797 fn find_cdb(target: &str) -> Option<OsString> {
798 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
802 let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
803 let cdb_arch = if cfg!(target_arch = "x86") {
805 } else if cfg!(target_arch = "x86_64") {
807 } else if cfg!(target_arch = "aarch64") {
809 } else if cfg!(target_arch = "arm") {
812 return None; // No compatible CDB.exe in the Windows 10 SDK
815 let mut path = PathBuf::new();
817 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
819 path.push(r"cdb.exe");
825 Some(path.into_os_string())
828 /// Returns Path to CDB
829 fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
830 let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
832 let mut version = None;
833 if let Some(cdb) = cdb.as_ref() {
834 if let Ok(output) = Command::new(cdb).arg("/version").output() {
835 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
836 version = extract_cdb_version(&first_line);
844 fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
845 // Example full_version_line: "cdb version 10.0.18362.1"
846 let version = full_version_line.rsplit(' ').next()?;
847 let mut components = version.split('.');
848 let major: u16 = components.next().unwrap().parse().unwrap();
849 let minor: u16 = components.next().unwrap().parse().unwrap();
850 let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
851 let build: u16 = components.next().unwrap_or("0").parse().unwrap();
852 Some([major, minor, patch, build])
855 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
859 android_cross_path: &PathBuf,
860 ) -> (Option<String>, Option<u32>, bool) {
862 const GDB_FALLBACK: &str = "gdb";
864 const GDB_FALLBACK: &str = "gdb.exe";
866 const MIN_GDB_WITH_RUST: u32 = 7011010;
868 let fallback_gdb = || {
869 if is_android_gdb_target(target) {
870 let mut gdb_path = match android_cross_path.to_str() {
871 Some(x) => x.to_owned(),
872 None => panic!("cannot find android cross path"),
874 gdb_path.push_str("/bin/gdb");
877 GDB_FALLBACK.to_owned()
881 let gdb = match gdb {
882 None => fallback_gdb(),
883 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
884 Some(ref s) => s.to_owned(),
887 let mut version_line = None;
888 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
889 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
890 version_line = Some(first_line.to_string());
894 let version = match version_line {
895 Some(line) => extract_gdb_version(&line),
896 None => return (None, None, false),
899 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
901 (Some(gdb), version, gdb_native_rust)
904 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
905 let full_version_line = full_version_line.trim();
907 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
908 // of the ? sections being optional
910 // We will parse up to 3 digits for each component, ignoring the date
912 // We skip text in parentheses. This avoids accidentally parsing
913 // the openSUSE version, which looks like:
914 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
915 // This particular form is documented in the GNU coding standards:
916 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
918 let unbracketed_part = full_version_line.split('[').next().unwrap();
919 let mut splits = unbracketed_part.trim_end().rsplit(' ');
920 let version_string = splits.next().unwrap();
922 let mut splits = version_string.split('.');
923 let major = splits.next().unwrap();
924 let minor = splits.next().unwrap();
925 let patch = splits.next();
927 let major: u32 = major.parse().unwrap();
928 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
930 let minor = minor.parse().unwrap();
931 let patch: u32 = match patch {
932 Some(patch) => match patch.find(not_a_digit) {
933 None => patch.parse().unwrap(),
934 Some(idx) if idx > 3 => 0,
935 Some(idx) => patch[..idx].parse().unwrap(),
941 // There is no patch version after minor-date (e.g. "4-2012").
943 let minor = minor[..idx].parse().unwrap();
948 Some(((major * 1000) + minor) * 1000 + patch)
951 /// Returns (LLDB version, LLDB is rust-enabled)
952 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
953 // Extract the major LLDB version from the given version string.
954 // LLDB version strings are different for Apple and non-Apple platforms.
955 // The Apple variant looks like this:
957 // LLDB-179.5 (older versions)
958 // lldb-300.2.51 (new versions)
960 // We are only interested in the major version number, so this function
961 // will return `Some(179)` and `Some(300)` respectively.
963 // Upstream versions look like:
964 // lldb version 6.0.1
966 // There doesn't seem to be a way to correlate the Apple version
967 // with the upstream version, and since the tests were originally
968 // written against Apple versions, we make a fake Apple version by
969 // multiplying the first number by 100. This is a hack, but
970 // normally fine because the only non-Apple version we test is
973 let full_version_line = full_version_line.trim();
975 if let Some(apple_ver) =
976 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
978 if let Some(idx) = apple_ver.find(not_a_digit) {
979 let version: u32 = apple_ver[..idx].parse().unwrap();
980 return Some((version, full_version_line.contains("rust-enabled")));
982 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
983 if let Some(idx) = lldb_ver.find(not_a_digit) {
984 let version: u32 = lldb_ver[..idx].parse().ok()?;
985 return Some((version * 100, full_version_line.contains("rust-enabled")));
991 fn not_a_digit(c: char) -> bool {