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, Pretty, TestPaths};
10 use crate::util::logv;
14 use std::ffi::OsString;
16 use std::io::{self, ErrorKind};
17 use std::path::{Path, PathBuf};
18 use std::process::Command;
19 use std::time::SystemTime;
20 use test::ColorConfig;
23 use self::header::EarlyProps;
40 let config = parse_config(env::args().collect());
42 if config.valgrind_path.is_none() && config.force_valgrind {
43 panic!("Can't find Valgrind to run Valgrind tests");
50 pub fn parse_config(args: Vec<String>) -> Config {
51 let mut opts = Options::new();
52 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
53 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
54 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
55 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
56 .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH")
57 .reqopt("", "lldb-python", "path to python to use for doc tests", "PATH")
58 .reqopt("", "docck-python", "path to python to use for doc tests", "PATH")
59 .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
60 .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
61 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
62 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
63 .reqopt("", "src-base", "directory to scan for test files", "PATH")
64 .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
65 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
69 "which sort of compile tests to run",
70 "compile-fail | run-fail | run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
71 codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
76 "force {check,build,run}-pass tests to this mode.",
77 "check | build | run",
79 .optflag("", "ignored", "run tests marked as ignored")
80 .optflag("", "exact", "filters match exactly")
84 "supervisor program to run tests under \
85 (eg. emulator, valgrind)",
88 .optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
89 .optopt("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
90 .optflag("", "verbose", "run tests verbosely, showing all output")
94 "overwrite stderr/stdout files instead of complaining about a mismatch",
96 .optflag("", "quiet", "print one character per test instead of one line")
97 .optopt("", "color", "coloring: auto, always, never", "WHEN")
98 .optopt("", "logfile", "file to log test execution to", "FILE")
99 .optopt("", "target", "the target to build for", "TARGET")
100 .optopt("", "host", "the host to build for", "HOST")
101 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
102 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
103 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
104 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
105 .optflag("", "system-llvm", "is LLVM the system LLVM")
106 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
107 .optopt("", "adb-path", "path to the android debugger", "PATH")
108 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
109 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
110 .reqopt("", "cc", "path to a C compiler", "PATH")
111 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
112 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
113 .optopt("", "ar", "path to an archiver", "PATH")
114 .optopt("", "linker", "path to a linker", "PATH")
115 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
116 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
117 .optopt("", "nodejs", "the name of nodejs", "PATH")
118 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
122 "mode describing what file the actual ui output will be compared to",
128 "enable this to generate a Rustfix coverage file, which is saved in \
129 `./<build_base>/rustfix_missing_coverage.txt`",
131 .optflag("h", "help", "show this message");
133 let (argv0, args_) = args.split_first().unwrap();
134 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
135 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
136 println!("{}", opts.usage(&message));
141 let matches = &match opts.parse(args_) {
143 Err(f) => panic!("{:?}", f),
146 if matches.opt_present("h") || matches.opt_present("help") {
147 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
148 println!("{}", opts.usage(&message));
153 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
154 match m.opt_str(nm) {
155 Some(s) => PathBuf::from(&s),
156 None => panic!("no option (=path) found for {}", nm),
160 fn make_absolute(path: PathBuf) -> PathBuf {
161 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
164 let target = opt_str2(matches.opt_str("target"));
165 let android_cross_path = opt_path(matches, "android-cross-path");
166 let cdb = analyze_cdb(matches.opt_str("cdb"), &target);
167 let (gdb, gdb_version, gdb_native_rust) =
168 analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
169 let (lldb_version, lldb_native_rust) = matches
170 .opt_str("lldb-version")
172 .and_then(extract_lldb_version)
173 .map(|(v, b)| (Some(v), b))
174 .unwrap_or((None, false));
175 let color = match matches.opt_str("color").as_deref() {
176 Some("auto") | None => ColorConfig::AutoColor,
177 Some("always") => ColorConfig::AlwaysColor,
178 Some("never") => ColorConfig::NeverColor,
179 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
182 matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version);
184 let src_base = opt_path(matches, "src-base");
185 let run_ignored = matches.opt_present("ignored");
187 bless: matches.opt_present("bless"),
188 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
189 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
190 rustc_path: opt_path(matches, "rustc-path"),
191 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
192 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
193 lldb_python: matches.opt_str("lldb-python").unwrap(),
194 docck_python: matches.opt_str("docck-python").unwrap(),
195 valgrind_path: matches.opt_str("valgrind-path"),
196 force_valgrind: matches.opt_present("force-valgrind"),
197 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
198 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
199 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
201 build_base: opt_path(matches, "build-base"),
202 stage_id: matches.opt_str("stage-id").unwrap(),
203 mode: matches.opt_str("mode").unwrap().parse().expect("invalid mode"),
206 filter: matches.free.first().cloned(),
207 filter_exact: matches.opt_present("exact"),
208 force_pass_mode: matches.opt_str("pass").map(|mode| {
209 mode.parse::<PassMode>()
210 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
212 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
213 runtool: matches.opt_str("runtool"),
214 host_rustcflags: matches.opt_str("host-rustcflags"),
215 target_rustcflags: matches.opt_str("target-rustcflags"),
217 host: opt_str2(matches.opt_str("host")),
225 system_llvm: matches.opt_present("system-llvm"),
227 adb_path: opt_str2(matches.opt_str("adb-path")),
228 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
229 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
230 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
231 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
232 lldb_python_dir: matches.opt_str("lldb-python-dir"),
233 verbose: matches.opt_present("verbose"),
234 quiet: matches.opt_present("quiet"),
236 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
237 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
238 rustfix_coverage: matches.opt_present("rustfix-coverage"),
240 cc: matches.opt_str("cc").unwrap(),
241 cxx: matches.opt_str("cxx").unwrap(),
242 cflags: matches.opt_str("cflags").unwrap(),
243 ar: matches.opt_str("ar").unwrap_or("ar".into()),
244 linker: matches.opt_str("linker"),
245 llvm_components: matches.opt_str("llvm-components").unwrap(),
246 nodejs: matches.opt_str("nodejs"),
250 pub fn log_config(config: &Config) {
252 logv(c, "configuration:".to_string());
253 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
254 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
255 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
256 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
257 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
258 logv(c, format!("src_base: {:?}", config.src_base.display()));
259 logv(c, format!("build_base: {:?}", config.build_base.display()));
260 logv(c, format!("stage_id: {}", config.stage_id));
261 logv(c, format!("mode: {}", config.mode));
262 logv(c, format!("run_ignored: {}", config.run_ignored));
263 logv(c, format!("filter: {}", opt_str(&config.filter)));
264 logv(c, format!("filter_exact: {}", config.filter_exact));
267 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
269 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
270 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
271 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
272 logv(c, format!("target: {}", config.target));
273 logv(c, format!("host: {}", config.host));
274 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
275 logv(c, format!("adb_path: {:?}", config.adb_path));
276 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
277 logv(c, format!("adb_device_status: {}", config.adb_device_status));
278 logv(c, format!("ar: {}", config.ar));
279 logv(c, format!("linker: {:?}", config.linker));
280 logv(c, format!("verbose: {}", config.verbose));
281 logv(c, format!("quiet: {}", config.quiet));
282 logv(c, "\n".to_string());
285 pub fn opt_str(maybestr: &Option<String>) -> &str {
292 pub fn opt_str2(maybestr: Option<String>) -> String {
294 None => "(none)".to_owned(),
299 pub fn run_tests(config: Config) {
300 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
301 if let Mode::CodegenUnits = config.mode {
302 let _ = fs::remove_dir_all("tmp/partitioning-tests");
305 // If we want to collect rustfix coverage information,
306 // we first make sure that the coverage file does not exist.
307 // It will be created later on.
308 if config.rustfix_coverage {
309 let mut coverage_file_path = config.build_base.clone();
310 coverage_file_path.push("rustfix_missing_coverage.txt");
311 if coverage_file_path.exists() {
312 if let Err(e) = fs::remove_file(&coverage_file_path) {
313 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
318 // sadly osx needs some file descriptor limits raised for running tests in
319 // parallel (especially when we have lots and lots of child processes).
320 // For context, see #8904
322 raise_fd_limit::raise_fd_limit();
324 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
325 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
326 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
328 // Let tests know which target they're running as
329 env::set_var("TARGET", &config.target);
331 let opts = test_opts(&config);
333 let mut configs = Vec::new();
334 if let Mode::DebugInfo = config.mode {
335 // Debugging emscripten code doesn't make sense today
336 if !config.target.contains("emscripten") {
337 configs.extend(configure_cdb(&config));
338 configs.extend(configure_gdb(&config));
339 configs.extend(configure_lldb(&config));
342 configs.push(config);
345 let mut tests = Vec::new();
347 make_tests(c, &mut tests);
350 let res = test::run_tests_console(&opts, tests);
353 Ok(false) => panic!("Some tests failed"),
355 // We don't know if tests passed or not, but if there was an error
356 // during testing we don't want to just suceeed (we may not have
357 // tested something), so fail.
358 panic!("I/O failure during tests: {:?}", e);
363 fn configure_cdb(config: &Config) -> Option<Config> {
364 if config.cdb.is_none() {
368 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
371 fn configure_gdb(config: &Config) -> Option<Config> {
372 if config.gdb_version.is_none() {
376 if util::matches_env(&config.target, "msvc") {
380 if config.remote_test_client.is_some() && !config.target.contains("android") {
382 "WARNING: debuginfo tests are not available when \
388 if config.target.contains("android") {
390 "{} debug-info test uses tcp 5039 port.\
395 // android debug-info test uses remote debugger so, we test 1 thread
396 // at once as they're all sharing the same TCP port to communicate
399 // we should figure out how to lift this restriction! (run them all
400 // on different ports allocated dynamically).
401 env::set_var("RUST_TEST_THREADS", "1");
404 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
407 fn configure_lldb(config: &Config) -> Option<Config> {
408 if config.lldb_python_dir.is_none() {
412 if let Some(350) = config.lldb_version {
414 "WARNING: The used version of LLDB (350) has a \
415 known issue that breaks debuginfo tests. See \
416 issue #32520 for more information. Skipping all \
422 // Some older versions of LLDB seem to have problems with multiple
423 // instances running in parallel, so only run one test thread at a
425 env::set_var("RUST_TEST_THREADS", "1");
427 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
430 pub fn test_opts(config: &Config) -> test::TestOpts {
432 exclude_should_panic: false,
433 filter: config.filter.clone(),
434 filter_exact: config.filter_exact,
435 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
436 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
437 logfile: config.logfile.clone(),
439 bench_benchmarks: true,
440 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
441 Ok(val) => &val != "0",
448 options: test::Options::new(),
450 force_run_in_process: false,
454 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
455 debug!("making tests from {:?}", config.src_base.display());
456 let inputs = common_inputs_stamp(config);
457 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
458 .expect(&format!("Could not read tests from {}", config.src_base.display()));
461 /// Returns a stamp constructed from input files common to all test cases.
462 fn common_inputs_stamp(config: &Config) -> Stamp {
463 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
465 let mut stamp = Stamp::from_path(&config.rustc_path);
467 // Relevant pretty printer files
468 let pretty_printer_files = [
469 "src/etc/rust_types.py",
470 "src/etc/gdb_load_rust_pretty_printers.py",
471 "src/etc/gdb_lookup.py",
472 "src/etc/gdb_providers.py",
473 "src/etc/lldb_batchmode.py",
474 "src/etc/lldb_lookup.py",
475 "src/etc/lldb_providers.py",
477 for file in &pretty_printer_files {
478 let path = rust_src_dir.join(file);
479 stamp.add_path(&path);
482 stamp.add_dir(&config.run_lib_path);
484 if let Some(ref rustdoc_path) = config.rustdoc_path {
485 stamp.add_path(&rustdoc_path);
486 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
488 // FIXME(richkadel): Do I need to add an `if let Some(rust_demangler_path) contribution to the
489 // stamp here as well?
491 // Compiletest itself.
492 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
497 fn collect_tests_from_dir(
500 relative_dir_path: &Path,
502 tests: &mut Vec<test::TestDescAndFn>,
503 ) -> io::Result<()> {
504 // Ignore directories that contain a file named `compiletest-ignore-dir`.
505 if dir.join("compiletest-ignore-dir").exists() {
509 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
510 let paths = TestPaths {
511 file: dir.to_path_buf(),
512 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
514 tests.extend(make_test(config, &paths, inputs));
518 // If we find a test foo/bar.rs, we have to build the
519 // output directory `$build/foo` so we can write
520 // `$build/foo/bar` into it. We do this *now* in this
521 // sequential loop because otherwise, if we do it in the
522 // tests themselves, they race for the privilege of
523 // creating the directories and sometimes fail randomly.
524 let build_dir = output_relative_path(config, relative_dir_path);
525 fs::create_dir_all(&build_dir).unwrap();
527 // Add each `.rs` file as a test, and recurse further on any
528 // subdirectories we find, except for `aux` directories.
529 for file in fs::read_dir(dir)? {
531 let file_path = file.path();
532 let file_name = file.file_name();
533 if is_test(&file_name) {
534 debug!("found test file: {:?}", file_path.display());
536 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
537 tests.extend(make_test(config, &paths, inputs))
538 } else if file_path.is_dir() {
539 let relative_file_path = relative_dir_path.join(file.file_name());
540 if &file_name != "auxiliary" {
541 debug!("found directory: {:?}", file_path.display());
542 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
545 debug!("found other file/directory: {:?}", file_path.display());
551 /// Returns true if `file_name` looks like a proper test file name.
552 pub fn is_test(file_name: &OsString) -> bool {
553 let file_name = file_name.to_str().unwrap();
555 if !file_name.ends_with(".rs") {
559 // `.`, `#`, and `~` are common temp-file prefixes.
560 let invalid_prefixes = &[".", "#", "~"];
561 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
564 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
565 let early_props = if config.mode == Mode::RunMake {
566 // Allow `ignore` directives to be in the Makefile.
567 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
569 EarlyProps::from_file(config, &testpaths.file)
572 // The `should-fail` annotation doesn't apply to pretty tests,
573 // since we run the pretty printer across all tests by default.
574 // If desired, we could add a `should-fail-pretty` annotation.
575 let should_panic = match config.mode {
576 Pretty => test::ShouldPanic::No,
578 if early_props.should_fail {
579 test::ShouldPanic::Yes
581 test::ShouldPanic::No
586 // Incremental tests are special, they inherently cannot be run in parallel.
587 // `runtest::run` will be responsible for iterating over revisions.
588 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
591 early_props.revisions.iter().map(|r| Some(r)).collect()
596 let ignore = early_props.ignore
597 // Ignore tests that already run and are up to date with respect to inputs.
602 revision.map(|s| s.as_str()),
605 test::TestDescAndFn {
606 desc: test::TestDesc {
607 name: make_test_name(config, testpaths, revision),
611 test_type: test::TestType::Unknown,
613 testfn: make_test_closure(config, testpaths, revision),
619 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
620 output_base_dir(config, testpaths, revision).join("stamp")
625 testpaths: &TestPaths,
627 revision: Option<&str>,
630 let stamp_name = stamp(config, testpaths, revision);
632 let contents = match fs::read_to_string(&stamp_name) {
634 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
635 Err(_) => return false,
637 let expected_hash = runtest::compute_stamp_hash(config);
638 if contents != expected_hash {
643 let mut inputs = inputs.clone();
644 // Use `add_dir` to account for run-make tests, which use their individual directory
645 inputs.add_dir(&testpaths.file);
647 for aux in &props.aux {
648 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
649 inputs.add_path(&path);
653 for extension in UI_EXTENSIONS {
654 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
655 inputs.add_path(path);
658 inputs < Stamp::from_path(&stamp_name)
661 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
667 fn from_path(path: &Path) -> Self {
668 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
669 stamp.add_path(path);
673 fn add_path(&mut self, path: &Path) {
674 let modified = fs::metadata(path)
675 .and_then(|metadata| metadata.modified())
676 .unwrap_or(SystemTime::UNIX_EPOCH);
677 self.time = self.time.max(modified);
680 fn add_dir(&mut self, path: &Path) {
681 for entry in WalkDir::new(path) {
682 let entry = entry.unwrap();
683 if entry.file_type().is_file() {
687 .and_then(|metadata| metadata.modified().ok())
688 .unwrap_or(SystemTime::UNIX_EPOCH);
689 self.time = self.time.max(modified);
697 testpaths: &TestPaths,
698 revision: Option<&String>,
699 ) -> test::TestName {
700 // Convert a complete path to something like
703 let path = PathBuf::from(config.src_base.file_name().unwrap())
704 .join(&testpaths.relative_dir)
705 .join(&testpaths.file.file_name().unwrap());
706 let debugger = match config.debugger {
707 Some(d) => format!("-{}", d),
708 None => String::new(),
710 let mode_suffix = match config.compare_mode {
711 Some(ref mode) => format!(" ({})", mode.to_str()),
712 None => String::new(),
715 test::DynTestName(format!(
721 revision.map_or("".to_string(), |rev| format!("#{}", rev))
725 fn make_test_closure(
727 testpaths: &TestPaths,
728 revision: Option<&String>,
730 let config = config.clone();
731 let testpaths = testpaths.clone();
732 let revision = revision.cloned();
733 test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref())))
736 /// Returns `true` if the given target is an Android target for the
737 /// purposes of GDB testing.
738 fn is_android_gdb_target(target: &String) -> bool {
740 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
745 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
746 fn is_pc_windows_msvc_target(target: &String) -> bool {
747 target.ends_with("-pc-windows-msvc")
750 fn find_cdb(target: &String) -> Option<OsString> {
751 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
755 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
756 let cdb_arch = if cfg!(target_arch = "x86") {
758 } else if cfg!(target_arch = "x86_64") {
760 } else if cfg!(target_arch = "aarch64") {
762 } else if cfg!(target_arch = "arm") {
765 return None; // No compatible CDB.exe in the Windows 10 SDK
768 let mut path = PathBuf::new();
770 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
772 path.push(r"cdb.exe");
778 Some(path.into_os_string())
781 /// Returns Path to CDB
782 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
783 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
786 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
790 android_cross_path: &PathBuf,
791 ) -> (Option<String>, Option<u32>, bool) {
793 const GDB_FALLBACK: &str = "gdb";
795 const GDB_FALLBACK: &str = "gdb.exe";
797 const MIN_GDB_WITH_RUST: u32 = 7011010;
799 let fallback_gdb = || {
800 if is_android_gdb_target(target) {
801 let mut gdb_path = match android_cross_path.to_str() {
802 Some(x) => x.to_owned(),
803 None => panic!("cannot find android cross path"),
805 gdb_path.push_str("/bin/gdb");
808 GDB_FALLBACK.to_owned()
812 let gdb = match gdb {
813 None => fallback_gdb(),
814 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
815 Some(ref s) => s.to_owned(),
818 let mut version_line = None;
819 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
820 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
821 version_line = Some(first_line.to_string());
825 let version = match version_line {
826 Some(line) => extract_gdb_version(&line),
827 None => return (None, None, false),
830 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
832 (Some(gdb), version, gdb_native_rust)
835 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
836 let full_version_line = full_version_line.trim();
838 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
839 // of the ? sections being optional
841 // We will parse up to 3 digits for each component, ignoring the date
843 // We skip text in parentheses. This avoids accidentally parsing
844 // the openSUSE version, which looks like:
845 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
846 // This particular form is documented in the GNU coding standards:
847 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
849 let mut splits = full_version_line.rsplit(' ');
850 let version_string = splits.next().unwrap();
852 let mut splits = version_string.split('.');
853 let major = splits.next().unwrap();
854 let minor = splits.next().unwrap();
855 let patch = splits.next();
857 let major: u32 = major.parse().unwrap();
858 let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
860 let minor = minor.parse().unwrap();
861 let patch: u32 = match patch {
862 Some(patch) => match patch.find(not_a_digit) {
863 None => patch.parse().unwrap(),
864 Some(idx) if idx > 3 => 0,
865 Some(idx) => patch[..idx].parse().unwrap(),
871 // There is no patch version after minor-date (e.g. "4-2012").
873 let minor = minor[..idx].parse().unwrap();
878 Some(((major * 1000) + minor) * 1000 + patch)
881 /// Returns (LLDB version, LLDB is rust-enabled)
882 fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> {
883 // Extract the major LLDB version from the given version string.
884 // LLDB version strings are different for Apple and non-Apple platforms.
885 // The Apple variant looks like this:
887 // LLDB-179.5 (older versions)
888 // lldb-300.2.51 (new versions)
890 // We are only interested in the major version number, so this function
891 // will return `Some(179)` and `Some(300)` respectively.
893 // Upstream versions look like:
894 // lldb version 6.0.1
896 // There doesn't seem to be a way to correlate the Apple version
897 // with the upstream version, and since the tests were originally
898 // written against Apple versions, we make a fake Apple version by
899 // multiplying the first number by 100. This is a hack, but
900 // normally fine because the only non-Apple version we test is
903 let full_version_line = full_version_line.trim();
905 if let Some(apple_ver) =
906 full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
908 if let Some(idx) = apple_ver.find(not_a_digit) {
909 let version: u32 = apple_ver[..idx].parse().unwrap();
910 return Some((version, full_version_line.contains("rust-enabled")));
912 } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
913 if let Some(idx) = lldb_ver.find(not_a_digit) {
914 let version: u32 = lldb_ver[..idx].parse().unwrap();
915 return Some((version * 100, full_version_line.contains("rust-enabled")));
921 fn not_a_digit(c: char) -> bool {