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) = extract_lldb_version(matches.opt_str("lldb-version"));
171 let color = match matches.opt_str("color").as_ref().map(|x| &**x) {
172 Some("auto") | None => ColorConfig::AutoColor,
173 Some("always") => ColorConfig::AlwaysColor,
174 Some("never") => ColorConfig::NeverColor,
175 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
178 let src_base = opt_path(matches, "src-base");
179 let run_ignored = matches.opt_present("ignored");
181 bless: matches.opt_present("bless"),
182 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
183 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
184 rustc_path: opt_path(matches, "rustc-path"),
185 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
186 rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from),
187 lldb_python: matches.opt_str("lldb-python").unwrap(),
188 docck_python: matches.opt_str("docck-python").unwrap(),
189 valgrind_path: matches.opt_str("valgrind-path"),
190 force_valgrind: matches.opt_present("force-valgrind"),
191 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
192 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
193 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
195 build_base: opt_path(matches, "build-base"),
196 stage_id: matches.opt_str("stage-id").unwrap(),
197 mode: matches.opt_str("mode").unwrap().parse().expect("invalid mode"),
200 filter: matches.free.first().cloned(),
201 filter_exact: matches.opt_present("exact"),
202 force_pass_mode: matches.opt_str("pass").map(|mode| {
203 mode.parse::<PassMode>()
204 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
206 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
207 runtool: matches.opt_str("runtool"),
208 host_rustcflags: matches.opt_str("host-rustcflags"),
209 target_rustcflags: matches.opt_str("target-rustcflags"),
211 host: opt_str2(matches.opt_str("host")),
218 llvm_version: matches.opt_str("llvm-version"),
219 system_llvm: matches.opt_present("system-llvm"),
221 adb_path: opt_str2(matches.opt_str("adb-path")),
222 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
223 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
224 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
225 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
226 lldb_python_dir: matches.opt_str("lldb-python-dir"),
227 verbose: matches.opt_present("verbose"),
228 quiet: matches.opt_present("quiet"),
230 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
231 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
232 rustfix_coverage: matches.opt_present("rustfix-coverage"),
234 cc: matches.opt_str("cc").unwrap(),
235 cxx: matches.opt_str("cxx").unwrap(),
236 cflags: matches.opt_str("cflags").unwrap(),
237 ar: matches.opt_str("ar").unwrap_or("ar".into()),
238 linker: matches.opt_str("linker"),
239 llvm_components: matches.opt_str("llvm-components").unwrap(),
240 nodejs: matches.opt_str("nodejs"),
244 pub fn log_config(config: &Config) {
246 logv(c, "configuration:".to_string());
247 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
248 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
249 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
250 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
251 logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path));
252 logv(c, format!("src_base: {:?}", config.src_base.display()));
253 logv(c, format!("build_base: {:?}", config.build_base.display()));
254 logv(c, format!("stage_id: {}", config.stage_id));
255 logv(c, format!("mode: {}", config.mode));
256 logv(c, format!("run_ignored: {}", config.run_ignored));
257 logv(c, format!("filter: {}", opt_str(&config.filter.as_ref().map(|re| re.to_owned()))));
258 logv(c, format!("filter_exact: {}", config.filter_exact));
261 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
263 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
264 logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
265 logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
266 logv(c, format!("target: {}", config.target));
267 logv(c, format!("host: {}", config.host));
268 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
269 logv(c, format!("adb_path: {:?}", config.adb_path));
270 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
271 logv(c, format!("adb_device_status: {}", config.adb_device_status));
272 logv(c, format!("ar: {}", config.ar));
273 logv(c, format!("linker: {:?}", config.linker));
274 logv(c, format!("verbose: {}", config.verbose));
275 logv(c, format!("quiet: {}", config.quiet));
276 logv(c, "\n".to_string());
279 pub fn opt_str(maybestr: &Option<String>) -> &str {
286 pub fn opt_str2(maybestr: Option<String>) -> String {
288 None => "(none)".to_owned(),
293 pub fn run_tests(config: Config) {
294 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
295 if let Mode::CodegenUnits = config.mode {
296 let _ = fs::remove_dir_all("tmp/partitioning-tests");
299 // If we want to collect rustfix coverage information,
300 // we first make sure that the coverage file does not exist.
301 // It will be created later on.
302 if config.rustfix_coverage {
303 let mut coverage_file_path = config.build_base.clone();
304 coverage_file_path.push("rustfix_missing_coverage.txt");
305 if coverage_file_path.exists() {
306 if let Err(e) = fs::remove_file(&coverage_file_path) {
307 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
312 // sadly osx needs some file descriptor limits raised for running tests in
313 // parallel (especially when we have lots and lots of child processes).
314 // For context, see #8904
316 raise_fd_limit::raise_fd_limit();
318 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
319 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
320 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
322 // Let tests know which target they're running as
323 env::set_var("TARGET", &config.target);
325 let opts = test_opts(&config);
327 let mut configs = Vec::new();
328 if let Mode::DebugInfo = config.mode {
329 // Debugging emscripten code doesn't make sense today
330 if !config.target.contains("emscripten") {
331 configs.extend(configure_cdb(&config));
332 configs.extend(configure_gdb(&config));
333 configs.extend(configure_lldb(&config));
336 configs.push(config);
339 let mut tests = Vec::new();
341 make_tests(c, &mut tests);
344 let res = test::run_tests_console(&opts, tests);
347 Ok(false) => panic!("Some tests failed"),
349 // We don't know if tests passed or not, but if there was an error
350 // during testing we don't want to just suceeed (we may not have
351 // tested something), so fail.
352 panic!("I/O failure during tests: {:?}", e);
357 fn configure_cdb(config: &Config) -> Option<Config> {
358 if config.cdb.is_none() {
362 Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() })
365 fn configure_gdb(config: &Config) -> Option<Config> {
366 if config.gdb_version.is_none() {
370 if util::matches_env(&config.target, "msvc") {
374 if config.remote_test_client.is_some() && !config.target.contains("android") {
376 "WARNING: debuginfo tests are not available when \
382 if config.target.contains("android") {
384 "{} debug-info test uses tcp 5039 port.\
389 // android debug-info test uses remote debugger so, we test 1 thread
390 // at once as they're all sharing the same TCP port to communicate
393 // we should figure out how to lift this restriction! (run them all
394 // on different ports allocated dynamically).
395 env::set_var("RUST_TEST_THREADS", "1");
398 Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() })
401 fn configure_lldb(config: &Config) -> Option<Config> {
402 if config.lldb_python_dir.is_none() {
406 if let Some(lldb_version) = config.lldb_version.as_ref() {
407 if lldb_version == "350" {
409 "WARNING: The used version of LLDB ({}) has a \
410 known issue that breaks debuginfo tests. See \
411 issue #32520 for more information. Skipping all \
419 // Some older versions of LLDB seem to have problems with multiple
420 // instances running in parallel, so only run one test thread at a
422 env::set_var("RUST_TEST_THREADS", "1");
424 Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() })
427 pub fn test_opts(config: &Config) -> test::TestOpts {
429 exclude_should_panic: false,
430 filter: config.filter.clone(),
431 filter_exact: config.filter_exact,
432 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
433 format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
434 logfile: config.logfile.clone(),
436 bench_benchmarks: true,
437 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
438 Ok(val) => &val != "0",
445 options: test::Options::new(),
447 force_run_in_process: false,
451 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
452 debug!("making tests from {:?}", config.src_base.display());
453 let inputs = common_inputs_stamp(config);
454 collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
455 .expect(&format!("Could not read tests from {}", config.src_base.display()));
458 /// Returns a stamp constructed from input files common to all test cases.
459 fn common_inputs_stamp(config: &Config) -> Stamp {
460 let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root");
462 let mut stamp = Stamp::from_path(&config.rustc_path);
464 // Relevant pretty printer files
465 let pretty_printer_files = [
466 "src/etc/rust_types.py",
467 "src/etc/gdb_load_rust_pretty_printers.py",
468 "src/etc/gdb_lookup.py",
469 "src/etc/gdb_providers.py",
470 "src/etc/lldb_batchmode.py",
471 "src/etc/lldb_lookup.py",
472 "src/etc/lldb_providers.py",
474 for file in &pretty_printer_files {
475 let path = rust_src_dir.join(file);
476 stamp.add_path(&path);
479 stamp.add_dir(&config.run_lib_path);
481 if let Some(ref rustdoc_path) = config.rustdoc_path {
482 stamp.add_path(&rustdoc_path);
483 stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
485 // FIXME(richkadel): Do I need to add an `if let Some(rust_demangler_path) contribution to the
486 // stamp here as well?
488 // Compiletest itself.
489 stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
494 fn collect_tests_from_dir(
497 relative_dir_path: &Path,
499 tests: &mut Vec<test::TestDescAndFn>,
500 ) -> io::Result<()> {
501 // Ignore directories that contain a file named `compiletest-ignore-dir`.
502 if dir.join("compiletest-ignore-dir").exists() {
506 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
507 let paths = TestPaths {
508 file: dir.to_path_buf(),
509 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
511 tests.extend(make_test(config, &paths, inputs));
515 // If we find a test foo/bar.rs, we have to build the
516 // output directory `$build/foo` so we can write
517 // `$build/foo/bar` into it. We do this *now* in this
518 // sequential loop because otherwise, if we do it in the
519 // tests themselves, they race for the privilege of
520 // creating the directories and sometimes fail randomly.
521 let build_dir = output_relative_path(config, relative_dir_path);
522 fs::create_dir_all(&build_dir).unwrap();
524 // Add each `.rs` file as a test, and recurse further on any
525 // subdirectories we find, except for `aux` directories.
526 for file in fs::read_dir(dir)? {
528 let file_path = file.path();
529 let file_name = file.file_name();
530 if is_test(&file_name) {
531 debug!("found test file: {:?}", file_path.display());
533 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
534 tests.extend(make_test(config, &paths, inputs))
535 } else if file_path.is_dir() {
536 let relative_file_path = relative_dir_path.join(file.file_name());
537 if &file_name != "auxiliary" {
538 debug!("found directory: {:?}", file_path.display());
539 collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
542 debug!("found other file/directory: {:?}", file_path.display());
548 /// Returns true if `file_name` looks like a proper test file name.
549 pub fn is_test(file_name: &OsString) -> bool {
550 let file_name = file_name.to_str().unwrap();
552 if !file_name.ends_with(".rs") {
556 // `.`, `#`, and `~` are common temp-file prefixes.
557 let invalid_prefixes = &[".", "#", "~"];
558 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
561 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
562 let early_props = if config.mode == Mode::RunMake {
563 // Allow `ignore` directives to be in the Makefile.
564 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
566 EarlyProps::from_file(config, &testpaths.file)
569 // The `should-fail` annotation doesn't apply to pretty tests,
570 // since we run the pretty printer across all tests by default.
571 // If desired, we could add a `should-fail-pretty` annotation.
572 let should_panic = match config.mode {
573 Pretty => test::ShouldPanic::No,
575 if early_props.should_fail {
576 test::ShouldPanic::Yes
578 test::ShouldPanic::No
583 // Incremental tests are special, they inherently cannot be run in parallel.
584 // `runtest::run` will be responsible for iterating over revisions.
585 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
588 early_props.revisions.iter().map(|r| Some(r)).collect()
593 let ignore = early_props.ignore
594 // Ignore tests that already run and are up to date with respect to inputs.
599 revision.map(|s| s.as_str()),
602 test::TestDescAndFn {
603 desc: test::TestDesc {
604 name: make_test_name(config, testpaths, revision),
608 test_type: test::TestType::Unknown,
610 testfn: make_test_closure(config, testpaths, revision),
616 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
617 output_base_dir(config, testpaths, revision).join("stamp")
622 testpaths: &TestPaths,
624 revision: Option<&str>,
627 let stamp_name = stamp(config, testpaths, revision);
629 let contents = match fs::read_to_string(&stamp_name) {
631 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
632 Err(_) => return false,
634 let expected_hash = runtest::compute_stamp_hash(config);
635 if contents != expected_hash {
640 let mut inputs = inputs.clone();
641 // Use `add_dir` to account for run-make tests, which use their individual directory
642 inputs.add_dir(&testpaths.file);
644 for aux in &props.aux {
645 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
646 inputs.add_path(&path);
650 for extension in UI_EXTENSIONS {
651 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
652 inputs.add_path(path);
655 inputs < Stamp::from_path(&stamp_name)
658 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
664 fn from_path(path: &Path) -> Self {
665 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
666 stamp.add_path(path);
670 fn add_path(&mut self, path: &Path) {
671 let modified = fs::metadata(path)
672 .and_then(|metadata| metadata.modified())
673 .unwrap_or(SystemTime::UNIX_EPOCH);
674 self.time = self.time.max(modified);
677 fn add_dir(&mut self, path: &Path) {
678 for entry in WalkDir::new(path) {
679 let entry = entry.unwrap();
680 if entry.file_type().is_file() {
684 .and_then(|metadata| metadata.modified().ok())
685 .unwrap_or(SystemTime::UNIX_EPOCH);
686 self.time = self.time.max(modified);
694 testpaths: &TestPaths,
695 revision: Option<&String>,
696 ) -> test::TestName {
697 // Convert a complete path to something like
700 let path = PathBuf::from(config.src_base.file_name().unwrap())
701 .join(&testpaths.relative_dir)
702 .join(&testpaths.file.file_name().unwrap());
703 let debugger = match config.debugger {
704 Some(d) => format!("-{}", d),
705 None => String::new(),
707 let mode_suffix = match config.compare_mode {
708 Some(ref mode) => format!(" ({})", mode.to_str()),
709 None => String::new(),
712 test::DynTestName(format!(
718 revision.map_or("".to_string(), |rev| format!("#{}", rev))
722 fn make_test_closure(
724 testpaths: &TestPaths,
725 revision: Option<&String>,
727 let config = config.clone();
728 let testpaths = testpaths.clone();
729 let revision = revision.cloned();
730 test::DynTestFn(Box::new(move || {
731 runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str()))
735 /// Returns `true` if the given target is an Android target for the
736 /// purposes of GDB testing.
737 fn is_android_gdb_target(target: &String) -> bool {
739 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
744 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
745 fn is_pc_windows_msvc_target(target: &String) -> bool {
746 target.ends_with("-pc-windows-msvc")
749 fn find_cdb(target: &String) -> Option<OsString> {
750 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
754 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
755 let cdb_arch = if cfg!(target_arch = "x86") {
757 } else if cfg!(target_arch = "x86_64") {
759 } else if cfg!(target_arch = "aarch64") {
761 } else if cfg!(target_arch = "arm") {
764 return None; // No compatible CDB.exe in the Windows 10 SDK
767 let mut path = PathBuf::new();
769 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
771 path.push(r"cdb.exe");
777 Some(path.into_os_string())
780 /// Returns Path to CDB
781 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
782 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
785 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
789 android_cross_path: &PathBuf,
790 ) -> (Option<String>, Option<u32>, bool) {
792 const GDB_FALLBACK: &str = "gdb";
794 const GDB_FALLBACK: &str = "gdb.exe";
796 const MIN_GDB_WITH_RUST: u32 = 7011010;
798 let fallback_gdb = || {
799 if is_android_gdb_target(target) {
800 let mut gdb_path = match android_cross_path.to_str() {
801 Some(x) => x.to_owned(),
802 None => panic!("cannot find android cross path"),
804 gdb_path.push_str("/bin/gdb");
807 GDB_FALLBACK.to_owned()
811 let gdb = match gdb {
812 None => fallback_gdb(),
813 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
814 Some(ref s) => s.to_owned(),
817 let mut version_line = None;
818 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
819 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
820 version_line = Some(first_line.to_string());
824 let version = match version_line {
825 Some(line) => extract_gdb_version(&line),
826 None => return (None, None, false),
829 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
831 (Some(gdb), version, gdb_native_rust)
834 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
835 let full_version_line = full_version_line.trim();
837 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
838 // of the ? sections being optional
840 // We will parse up to 3 digits for each component, ignoring the date
842 // We skip text in parentheses. This avoids accidentally parsing
843 // the openSUSE version, which looks like:
844 // GNU gdb (GDB; openSUSE Leap 15.0) 8.1
845 // This particular form is documented in the GNU coding standards:
846 // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
848 // don't start parsing in the middle of a number
849 let mut prev_was_digit = false;
850 let mut in_parens = false;
851 for (pos, c) in full_version_line.char_indices() {
862 if prev_was_digit || !c.is_digit(10) {
863 prev_was_digit = c.is_digit(10);
867 prev_was_digit = true;
869 let line = &full_version_line[pos..];
871 let next_split = match line.find(|c: char| !c.is_digit(10)) {
873 None => continue, // no minor version
876 if line.as_bytes()[next_split] != b'.' {
877 continue; // no minor version
880 let major = &line[..next_split];
881 let line = &line[next_split + 1..];
883 let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
885 if line.as_bytes()[idx] == b'.' {
886 let patch = &line[idx + 1..];
889 patch.find(|c: char| !c.is_digit(10)).unwrap_or_else(|| patch.len());
890 let patch = &patch[..patch_len];
891 let patch = if patch_len > 3 || patch_len == 0 { None } else { Some(patch) };
893 (&line[..idx], patch)
898 None => (line, None),
901 if minor.is_empty() {
905 let major: u32 = major.parse().unwrap();
906 let minor: u32 = minor.parse().unwrap();
907 let patch: u32 = patch.unwrap_or("0").parse().unwrap();
909 return Some(((major * 1000) + minor) * 1000 + patch);
915 /// Returns (LLDB version, LLDB is rust-enabled)
916 fn extract_lldb_version(full_version_line: Option<String>) -> (Option<String>, bool) {
917 // Extract the major LLDB version from the given version string.
918 // LLDB version strings are different for Apple and non-Apple platforms.
919 // The Apple variant looks like this:
921 // LLDB-179.5 (older versions)
922 // lldb-300.2.51 (new versions)
924 // We are only interested in the major version number, so this function
925 // will return `Some("179")` and `Some("300")` respectively.
927 // Upstream versions look like:
928 // lldb version 6.0.1
930 // There doesn't seem to be a way to correlate the Apple version
931 // with the upstream version, and since the tests were originally
932 // written against Apple versions, we make a fake Apple version by
933 // multiplying the first number by 100. This is a hack, but
934 // normally fine because the only non-Apple version we test is
937 if let Some(ref full_version_line) = full_version_line {
938 if !full_version_line.trim().is_empty() {
939 let full_version_line = full_version_line.trim();
941 for (pos, l) in full_version_line.char_indices() {
942 if l != 'l' && l != 'L' {
945 if pos + 5 >= full_version_line.len() {
948 let l = full_version_line[pos + 1..].chars().next().unwrap();
949 if l != 'l' && l != 'L' {
952 let d = full_version_line[pos + 2..].chars().next().unwrap();
953 if d != 'd' && d != 'D' {
956 let b = full_version_line[pos + 3..].chars().next().unwrap();
957 if b != 'b' && b != 'B' {
960 let dash = full_version_line[pos + 4..].chars().next().unwrap();
965 let vers = full_version_line[pos + 5..]
967 .take_while(|c| c.is_digit(10))
968 .collect::<String>();
969 if !vers.is_empty() {
970 return (Some(vers), full_version_line.contains("rust-enabled"));
974 if full_version_line.starts_with("lldb version ") {
975 let vers = full_version_line[13..]
977 .take_while(|c| c.is_digit(10))
978 .collect::<String>();
979 if !vers.is_empty() {
980 return (Some(vers + "00"), full_version_line.contains("rust-enabled"));