1 #![crate_name = "compiletest"]
3 #![feature(vec_remove_item)]
7 use crate::common::{CompareMode, PassMode};
8 use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
9 use crate::common::{Config, TestPaths};
10 use crate::common::{DebugInfoCdb, DebugInfoGdbLldb, DebugInfoGdb, DebugInfoLldb, Mode, Pretty};
13 use std::ffi::OsString;
15 use std::io::{self, ErrorKind};
16 use std::path::{Path, PathBuf};
17 use std::process::Command;
18 use std::time::SystemTime;
19 use test::ColorConfig;
20 use crate::util::logv;
26 use self::header::{EarlyProps, Ignore};
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");
53 pub fn parse_config(args: Vec<String>) -> Config {
54 let mut opts = Options::new();
58 "path to host shared libraries",
63 "path to target shared libraries",
69 "path to rustc to use for compiling",
75 "path to rustdoc to use for compiling",
81 "path to python to use for doc tests",
87 "path to python to use for doc tests",
93 "path to Valgrind executable for Valgrind tests",
99 "fail if Valgrind tests cannot be run under Valgrind",
103 "run-clang-based-tests-with",
104 "path to Clang executable",
110 "path to LLVM's FileCheck binary",
113 .reqopt("", "src-base", "directory to scan for test files", "PATH")
117 "directory to deposit test outputs",
123 "the target-stage identifier",
129 "which sort of compile tests to run",
130 "(compile-fail|run-fail|run-pass-valgrind|pretty|debug-info|incremental|mir-opt)",
135 "force {check,build,run}-pass tests to this mode.",
136 "check | build | run"
138 .optflag("", "ignored", "run tests marked as ignored")
139 .optflag("", "exact", "filters match exactly")
143 "supervisor program to run tests under \
144 (eg. emulator, valgrind)",
150 "flags to pass to rustc for host",
156 "flags to pass to rustc for target",
159 .optflag("", "verbose", "run tests verbosely, showing all output")
163 "overwrite stderr/stdout files instead of complaining about a mismatch",
168 "print one character per test instead of one line",
170 .optopt("", "color", "coloring: auto, always, never", "WHEN")
171 .optopt("", "logfile", "file to log test execution to", "FILE")
172 .optopt("", "target", "the target to build for", "TARGET")
173 .optopt("", "host", "the host to build for", "HOST")
177 "path to CDB to use for CDB debuginfo tests",
183 "path to GDB to use for GDB debuginfo tests",
189 "the version of LLDB used",
195 "the version of LLVM used",
198 .optflag("", "system-llvm", "is LLVM the system LLVM")
201 "android-cross-path",
202 "Android NDK standalone path",
205 .optopt("", "adb-path", "path to the android debugger", "PATH")
209 "path to tests for the android debugger",
215 "directory containing LLDB's python module",
218 .reqopt("", "cc", "path to a C compiler", "PATH")
219 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
220 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
221 .optopt("", "ar", "path to an archiver", "PATH")
222 .optopt("", "linker", "path to a linker", "PATH")
226 "list of LLVM components built in",
229 .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
230 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
231 .optopt("", "nodejs", "the name of nodejs", "PATH")
234 "remote-test-client",
235 "path to the remote test client",
241 "mode describing what file the actual ui output will be compared to",
247 "enable this to generate a Rustfix coverage file, which is saved in \
248 `./<build_base>/rustfix_missing_coverage.txt`",
250 .optflag("h", "help", "show this message");
252 let (argv0, args_) = args.split_first().unwrap();
253 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
254 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
255 println!("{}", opts.usage(&message));
260 let matches = &match opts.parse(args_) {
262 Err(f) => panic!("{:?}", f),
265 if matches.opt_present("h") || matches.opt_present("help") {
266 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
267 println!("{}", opts.usage(&message));
272 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
273 match m.opt_str(nm) {
274 Some(s) => PathBuf::from(&s),
275 None => panic!("no option (=path) found for {}", nm),
279 fn make_absolute(path: PathBuf) -> PathBuf {
280 if path.is_relative() {
281 env::current_dir().unwrap().join(path)
287 let target = opt_str2(matches.opt_str("target"));
288 let android_cross_path = opt_path(matches, "android-cross-path");
289 let cdb = analyze_cdb(matches.opt_str("cdb"), &target);
290 let (gdb, gdb_version, gdb_native_rust) = analyze_gdb(matches.opt_str("gdb"), &target,
291 &android_cross_path);
292 let (lldb_version, lldb_native_rust) = extract_lldb_version(matches.opt_str("lldb-version"));
294 let color = match matches.opt_str("color").as_ref().map(|x| &**x) {
295 Some("auto") | None => ColorConfig::AutoColor,
296 Some("always") => ColorConfig::AlwaysColor,
297 Some("never") => ColorConfig::NeverColor,
299 "argument for --color must be auto, always, or never, but found `{}`",
304 let src_base = opt_path(matches, "src-base");
305 let run_ignored = matches.opt_present("ignored");
307 bless: matches.opt_present("bless"),
308 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
309 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
310 rustc_path: opt_path(matches, "rustc-path"),
311 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
312 lldb_python: matches.opt_str("lldb-python").unwrap(),
313 docck_python: matches.opt_str("docck-python").unwrap(),
314 valgrind_path: matches.opt_str("valgrind-path"),
315 force_valgrind: matches.opt_present("force-valgrind"),
316 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
317 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
318 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
320 build_base: opt_path(matches, "build-base"),
321 stage_id: matches.opt_str("stage-id").unwrap(),
326 .expect("invalid mode"),
328 filter: matches.free.first().cloned(),
329 filter_exact: matches.opt_present("exact"),
330 force_pass_mode: matches.opt_str("pass").map(|mode|
331 mode.parse::<PassMode>()
332 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
334 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
335 runtool: matches.opt_str("runtool"),
336 host_rustcflags: matches.opt_str("host-rustcflags"),
337 target_rustcflags: matches.opt_str("target-rustcflags"),
339 host: opt_str2(matches.opt_str("host")),
346 llvm_version: matches.opt_str("llvm-version"),
347 system_llvm: matches.opt_present("system-llvm"),
349 adb_path: opt_str2(matches.opt_str("adb-path")),
350 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
351 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
352 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
353 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
354 lldb_python_dir: matches.opt_str("lldb-python-dir"),
355 verbose: matches.opt_present("verbose"),
356 quiet: matches.opt_present("quiet"),
358 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
359 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
360 rustfix_coverage: matches.opt_present("rustfix-coverage"),
362 cc: matches.opt_str("cc").unwrap(),
363 cxx: matches.opt_str("cxx").unwrap(),
364 cflags: matches.opt_str("cflags").unwrap(),
365 ar: matches.opt_str("ar").unwrap_or("ar".into()),
366 linker: matches.opt_str("linker"),
367 llvm_components: matches.opt_str("llvm-components").unwrap(),
368 llvm_cxxflags: matches.opt_str("llvm-cxxflags").unwrap(),
369 nodejs: matches.opt_str("nodejs"),
373 pub fn log_config(config: &Config) {
375 logv(c, "configuration:".to_string());
378 format!("compile_lib_path: {:?}", config.compile_lib_path),
380 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
381 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
382 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
383 logv(c, format!("src_base: {:?}", config.src_base.display()));
384 logv(c, format!("build_base: {:?}", config.build_base.display()));
385 logv(c, format!("stage_id: {}", config.stage_id));
386 logv(c, format!("mode: {}", config.mode));
387 logv(c, format!("run_ignored: {}", config.run_ignored));
392 opt_str(&config.filter.as_ref().map(|re| re.to_owned()))
395 logv(c, format!("filter_exact: {}", config.filter_exact));
397 "force_pass_mode: {}",
398 opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),
400 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
403 format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)),
407 format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)),
409 logv(c, format!("target: {}", config.target));
410 logv(c, format!("host: {}", config.host));
414 "android-cross-path: {:?}",
415 config.android_cross_path.display()
418 logv(c, format!("adb_path: {:?}", config.adb_path));
419 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
422 format!("adb_device_status: {}", config.adb_device_status),
424 logv(c, format!("ar: {}", config.ar));
425 logv(c, format!("linker: {:?}", config.linker));
426 logv(c, format!("verbose: {}", config.verbose));
427 logv(c, format!("quiet: {}", config.quiet));
428 logv(c, "\n".to_string());
431 pub fn opt_str(maybestr: &Option<String>) -> &str {
438 pub fn opt_str2(maybestr: Option<String>) -> String {
440 None => "(none)".to_owned(),
445 pub fn run_tests(config: &Config) {
446 if config.target.contains("android") {
447 if config.mode == DebugInfoGdb || config.mode == DebugInfoGdbLldb {
449 "{} debug-info test uses tcp 5039 port.\
454 // android debug-info test uses remote debugger so, we test 1 thread
455 // at once as they're all sharing the same TCP port to communicate
458 // we should figure out how to lift this restriction! (run them all
459 // on different ports allocated dynamically).
460 env::set_var("RUST_TEST_THREADS", "1");
465 // Note that we don't need to emit the gdb warning when
466 // DebugInfoGdbLldb, so it is ok to list that here.
467 DebugInfoGdbLldb | DebugInfoLldb => {
468 if let Some(lldb_version) = config.lldb_version.as_ref() {
469 if is_blacklisted_lldb_version(&lldb_version[..]) {
471 "WARNING: The used version of LLDB ({}) has a \
472 known issue that breaks debuginfo tests. See \
473 issue #32520 for more information. Skipping all \
481 // Some older versions of LLDB seem to have problems with multiple
482 // instances running in parallel, so only run one test thread at a
484 env::set_var("RUST_TEST_THREADS", "1");
488 if config.remote_test_client.is_some() && !config.target.contains("android") {
490 "WARNING: debuginfo tests are not available when \
497 DebugInfoCdb | _ => { /* proceed */ }
500 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
501 if let Mode::CodegenUnits = config.mode {
502 let _ = fs::remove_dir_all("tmp/partitioning-tests");
505 // If we want to collect rustfix coverage information,
506 // we first make sure that the coverage file does not exist.
507 // It will be created later on.
508 if config.rustfix_coverage {
509 let mut coverage_file_path = config.build_base.clone();
510 coverage_file_path.push("rustfix_missing_coverage.txt");
511 if coverage_file_path.exists() {
512 if let Err(e) = fs::remove_file(&coverage_file_path) {
513 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
518 let opts = test_opts(config);
519 let tests = make_tests(config);
520 // sadly osx needs some file descriptor limits raised for running tests in
521 // parallel (especially when we have lots and lots of child processes).
522 // For context, see #8904
524 raise_fd_limit::raise_fd_limit();
526 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
527 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
528 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
530 // Let tests know which target they're running as
531 env::set_var("TARGET", &config.target);
533 let res = test::run_tests_console(&opts, tests);
536 Ok(false) => panic!("Some tests failed"),
538 println!("I/O failure during tests: {:?}", e);
543 pub fn test_opts(config: &Config) -> test::TestOpts {
545 exclude_should_panic: false,
546 filter: config.filter.clone(),
547 filter_exact: config.filter_exact,
548 run_ignored: if config.run_ignored {
549 test::RunIgnored::Yes
553 format: if config.quiet {
554 test::OutputFormat::Terse
556 test::OutputFormat::Pretty
558 logfile: config.logfile.clone(),
560 bench_benchmarks: true,
561 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
562 Ok(val) => &val != "0",
569 options: test::Options::new(),
573 pub fn make_tests(config: &Config) -> Vec<test::TestDescAndFn> {
574 debug!("making tests from {:?}", config.src_base.display());
575 let mut tests = Vec::new();
576 collect_tests_from_dir(
586 fn collect_tests_from_dir(
590 relative_dir_path: &Path,
591 tests: &mut Vec<test::TestDescAndFn>,
592 ) -> io::Result<()> {
593 // Ignore directories that contain a file named `compiletest-ignore-dir`.
594 if dir.join("compiletest-ignore-dir").exists() {
598 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
599 let paths = TestPaths {
600 file: dir.to_path_buf(),
601 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
603 tests.extend(make_test(config, &paths));
607 // If we find a test foo/bar.rs, we have to build the
608 // output directory `$build/foo` so we can write
609 // `$build/foo/bar` into it. We do this *now* in this
610 // sequential loop because otherwise, if we do it in the
611 // tests themselves, they race for the privilege of
612 // creating the directories and sometimes fail randomly.
613 let build_dir = output_relative_path(config, relative_dir_path);
614 fs::create_dir_all(&build_dir).unwrap();
616 // Add each `.rs` file as a test, and recurse further on any
617 // subdirectories we find, except for `aux` directories.
618 for file in fs::read_dir(dir)? {
620 let file_path = file.path();
621 let file_name = file.file_name();
622 if is_test(&file_name) {
623 debug!("found test file: {:?}", file_path.display());
624 let paths = TestPaths {
626 relative_dir: relative_dir_path.to_path_buf(),
628 tests.extend(make_test(config, &paths))
629 } else if file_path.is_dir() {
630 let relative_file_path = relative_dir_path.join(file.file_name());
631 if &file_name != "auxiliary" {
632 debug!("found directory: {:?}", file_path.display());
633 collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?;
636 debug!("found other file/directory: {:?}", file_path.display());
643 /// Returns true if `file_name` looks like a proper test file name.
644 pub fn is_test(file_name: &OsString) -> bool {
645 let file_name = file_name.to_str().unwrap();
647 if !file_name.ends_with(".rs") {
651 // `.`, `#`, and `~` are common temp-file prefixes.
652 let invalid_prefixes = &[".", "#", "~"];
653 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
656 pub fn make_test(config: &Config, testpaths: &TestPaths) -> Vec<test::TestDescAndFn> {
657 let early_props = if config.mode == Mode::RunMake {
658 // Allow `ignore` directives to be in the Makefile.
659 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
661 EarlyProps::from_file(config, &testpaths.file)
664 // The `should-fail` annotation doesn't apply to pretty tests,
665 // since we run the pretty printer across all tests by default.
666 // If desired, we could add a `should-fail-pretty` annotation.
667 let should_panic = match config.mode {
668 Pretty => test::ShouldPanic::No,
669 _ => if early_props.should_fail {
670 test::ShouldPanic::Yes
672 test::ShouldPanic::No
676 // Incremental tests are special, they inherently cannot be run in parallel.
677 // `runtest::run` will be responsible for iterating over revisions.
678 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
681 early_props.revisions.iter().map(|r| Some(r)).collect()
686 // Debugging emscripten code doesn't make sense today
687 let ignore = early_props.ignore == Ignore::Ignore
692 revision.map(|s| s.as_str()),
694 || ((config.mode == DebugInfoGdbLldb || config.mode == DebugInfoCdb ||
695 config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
696 && config.target.contains("emscripten"))
697 || (config.mode == DebugInfoGdb && !early_props.ignore.can_run_gdb())
698 || (config.mode == DebugInfoLldb && !early_props.ignore.can_run_lldb());
699 test::TestDescAndFn {
700 desc: test::TestDesc {
701 name: make_test_name(config, testpaths, revision),
706 testfn: make_test_closure(config, early_props.ignore, testpaths, revision),
712 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
713 output_base_dir(config, testpaths, revision).join("stamp")
718 testpaths: &TestPaths,
720 revision: Option<&str>,
722 let stamp_name = stamp(config, testpaths, revision);
724 let contents = match fs::read_to_string(&stamp_name) {
726 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
727 Err(_) => return true,
729 let expected_hash = runtest::compute_stamp_hash(config);
730 if contents != expected_hash {
735 let rust_src_dir = config
736 .find_rust_src_root()
737 .expect("Could not find Rust source root");
738 let stamp = Stamp::from_path(&stamp_name);
739 let mut inputs = vec![Stamp::from_path(&testpaths.file), Stamp::from_path(&config.rustc_path)];
745 Stamp::from_path(&testpaths.file.parent().unwrap().join("auxiliary").join(aux))
748 // Relevant pretty printer files
749 let pretty_printer_files = [
750 "src/etc/debugger_pretty_printers_common.py",
751 "src/etc/gdb_load_rust_pretty_printers.py",
752 "src/etc/gdb_rust_pretty_printing.py",
753 "src/etc/lldb_batchmode.py",
754 "src/etc/lldb_rust_formatters.py",
756 inputs.extend(pretty_printer_files.iter().map(|pretty_printer_file| {
757 Stamp::from_path(&rust_src_dir.join(pretty_printer_file))
759 inputs.extend(Stamp::from_dir(&config.run_lib_path));
760 if let Some(ref rustdoc_path) = config.rustdoc_path {
761 inputs.push(Stamp::from_path(&rustdoc_path));
762 inputs.push(Stamp::from_path(&rust_src_dir.join("src/etc/htmldocck.py")));
766 inputs.extend(UI_EXTENSIONS.iter().map(|extension| {
767 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
768 Stamp::from_path(path)
771 // Compiletest itself.
772 inputs.extend(Stamp::from_dir(&rust_src_dir.join("src/tools/compiletest/")));
774 inputs.iter().any(|input| input > &stamp)
777 #[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
784 fn from_path(p: &Path) -> Self {
785 let time = fs::metadata(p)
786 .and_then(|metadata| metadata.modified())
787 .unwrap_or(SystemTime::UNIX_EPOCH);
795 fn from_dir(path: &Path) -> impl Iterator<Item = Stamp> {
798 .map(|entry| entry.unwrap())
799 .filter(|entry| entry.file_type().is_file())
801 let time = (|| -> io::Result<_> { entry.metadata()?.modified() })();
804 time: time.unwrap_or(SystemTime::UNIX_EPOCH),
805 file: entry.path().into(),
813 testpaths: &TestPaths,
814 revision: Option<&String>,
815 ) -> test::TestName {
816 // Convert a complete path to something like
819 let path = PathBuf::from(config.src_base.file_name().unwrap())
820 .join(&testpaths.relative_dir)
821 .join(&testpaths.file.file_name().unwrap());
822 let mode_suffix = match config.compare_mode {
823 Some(ref mode) => format!(" ({})", mode.to_str()),
824 None => String::new(),
826 test::DynTestName(format!(
831 revision.map_or("".to_string(), |rev| format!("#{}", rev))
835 fn make_test_closure(
838 testpaths: &TestPaths,
839 revision: Option<&String>,
841 let mut config = config.clone();
842 if config.mode == DebugInfoGdbLldb {
843 // If both gdb and lldb were ignored, then the test as a whole
845 if !ignore.can_run_gdb() {
846 config.mode = DebugInfoLldb;
847 } else if !ignore.can_run_lldb() {
848 config.mode = DebugInfoGdb;
852 let testpaths = testpaths.clone();
853 let revision = revision.cloned();
854 test::DynTestFn(Box::new(move || {
855 runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str()))
859 /// Returns `true` if the given target is an Android target for the
860 /// purposes of GDB testing.
861 fn is_android_gdb_target(target: &String) -> bool {
863 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
868 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
869 fn is_pc_windows_msvc_target(target: &String) -> bool {
870 target.ends_with("-pc-windows-msvc")
873 fn find_cdb(target: &String) -> Option<OsString> {
874 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
878 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
879 let cdb_arch = if cfg!(target_arch="x86") {
881 } else if cfg!(target_arch="x86_64") {
883 } else if cfg!(target_arch="aarch64") {
885 } else if cfg!(target_arch="arm") {
888 return None; // No compatible CDB.exe in the Windows 10 SDK
891 let mut path = PathBuf::new();
893 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
895 path.push(r"cdb.exe");
901 Some(path.into_os_string())
904 /// Returns Path to CDB
905 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
906 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
909 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
910 fn analyze_gdb(gdb: Option<String>, target: &String, android_cross_path: &PathBuf)
911 -> (Option<String>, Option<u32>, bool) {
913 const GDB_FALLBACK: &str = "gdb";
915 const GDB_FALLBACK: &str = "gdb.exe";
917 const MIN_GDB_WITH_RUST: u32 = 7011010;
919 let fallback_gdb = || {
920 if is_android_gdb_target(target) {
921 let mut gdb_path = match android_cross_path.to_str() {
922 Some(x) => x.to_owned(),
923 None => panic!("cannot find android cross path"),
925 gdb_path.push_str("/bin/gdb");
928 GDB_FALLBACK.to_owned()
932 let gdb = match gdb {
933 None => fallback_gdb(),
934 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
935 Some(ref s) => s.to_owned(),
938 let mut version_line = None;
939 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
940 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
941 version_line = Some(first_line.to_string());
945 let version = match version_line {
946 Some(line) => extract_gdb_version(&line),
947 None => return (None, None, false),
950 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
952 (Some(gdb), version, gdb_native_rust)
955 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
956 let full_version_line = full_version_line.trim();
958 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
959 // of the ? sections being optional
961 // We will parse up to 3 digits for minor and patch, ignoring the date
962 // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
964 // don't start parsing in the middle of a number
965 let mut prev_was_digit = false;
966 for (pos, c) in full_version_line.char_indices() {
967 if prev_was_digit || !c.is_digit(10) {
968 prev_was_digit = c.is_digit(10);
972 prev_was_digit = true;
974 let line = &full_version_line[pos..];
976 let next_split = match line.find(|c: char| !c.is_digit(10)) {
978 None => continue, // no minor version
981 if line.as_bytes()[next_split] != b'.' {
982 continue; // no minor version
985 let major = &line[..next_split];
986 let line = &line[next_split + 1..];
988 let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
989 Some(idx) => if line.as_bytes()[idx] == b'.' {
990 let patch = &line[idx + 1..];
992 let patch_len = patch
993 .find(|c: char| !c.is_digit(10))
994 .unwrap_or_else(|| patch.len());
995 let patch = &patch[..patch_len];
996 let patch = if patch_len > 3 || patch_len == 0 {
1002 (&line[..idx], patch)
1004 (&line[..idx], None)
1006 None => (line, None),
1009 if major.len() != 1 || minor.is_empty() {
1013 let major: u32 = major.parse().unwrap();
1014 let minor: u32 = minor.parse().unwrap();
1015 let patch: u32 = patch.unwrap_or("0").parse().unwrap();
1017 return Some(((major * 1000) + minor) * 1000 + patch);
1023 /// Returns (LLDB version, LLDB is rust-enabled)
1024 fn extract_lldb_version(full_version_line: Option<String>) -> (Option<String>, bool) {
1025 // Extract the major LLDB version from the given version string.
1026 // LLDB version strings are different for Apple and non-Apple platforms.
1027 // The Apple variant looks like this:
1029 // LLDB-179.5 (older versions)
1030 // lldb-300.2.51 (new versions)
1032 // We are only interested in the major version number, so this function
1033 // will return `Some("179")` and `Some("300")` respectively.
1035 // Upstream versions look like:
1036 // lldb version 6.0.1
1038 // There doesn't seem to be a way to correlate the Apple version
1039 // with the upstream version, and since the tests were originally
1040 // written against Apple versions, we make a fake Apple version by
1041 // multiplying the first number by 100. This is a hack, but
1042 // normally fine because the only non-Apple version we test is
1045 if let Some(ref full_version_line) = full_version_line {
1046 if !full_version_line.trim().is_empty() {
1047 let full_version_line = full_version_line.trim();
1049 for (pos, l) in full_version_line.char_indices() {
1050 if l != 'l' && l != 'L' {
1053 if pos + 5 >= full_version_line.len() {
1056 let l = full_version_line[pos + 1..].chars().next().unwrap();
1057 if l != 'l' && l != 'L' {
1060 let d = full_version_line[pos + 2..].chars().next().unwrap();
1061 if d != 'd' && d != 'D' {
1064 let b = full_version_line[pos + 3..].chars().next().unwrap();
1065 if b != 'b' && b != 'B' {
1068 let dash = full_version_line[pos + 4..].chars().next().unwrap();
1073 let vers = full_version_line[pos + 5..]
1075 .take_while(|c| c.is_digit(10))
1076 .collect::<String>();
1077 if !vers.is_empty() {
1078 return (Some(vers), full_version_line.contains("rust-enabled"));
1082 if full_version_line.starts_with("lldb version ") {
1083 let vers = full_version_line[13..]
1085 .take_while(|c| c.is_digit(10))
1086 .collect::<String>();
1087 if !vers.is_empty() {
1088 return (Some(vers + "00"), full_version_line.contains("rust-enabled"));
1096 fn is_blacklisted_lldb_version(version: &str) -> bool {