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};
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();
55 "path to host shared libraries",
60 "path to target shared libraries",
66 "path to rustc to use for compiling",
72 "path to rustdoc to use for compiling",
78 "path to python to use for doc tests",
84 "path to python to use for doc tests",
90 "path to Valgrind executable for Valgrind tests",
96 "fail if Valgrind tests cannot be run under Valgrind",
100 "run-clang-based-tests-with",
101 "path to Clang executable",
107 "path to LLVM's FileCheck binary",
110 .reqopt("", "src-base", "directory to scan for test files", "PATH")
114 "directory to deposit test outputs",
120 "the target-stage identifier",
126 "which sort of compile tests to run",
127 "(compile-fail|run-fail|run-pass-valgrind|pretty|debug-info|incremental|mir-opt)",
132 "force {check,build,run}-pass tests to this mode.",
133 "check | build | run"
135 .optflag("", "ignored", "run tests marked as ignored")
136 .optflag("", "exact", "filters match exactly")
140 "supervisor program to run tests under \
141 (eg. emulator, valgrind)",
147 "flags to pass to rustc for host",
153 "flags to pass to rustc for target",
156 .optflag("", "verbose", "run tests verbosely, showing all output")
160 "overwrite stderr/stdout files instead of complaining about a mismatch",
165 "print one character per test instead of one line",
167 .optopt("", "color", "coloring: auto, always, never", "WHEN")
168 .optopt("", "logfile", "file to log test execution to", "FILE")
169 .optopt("", "target", "the target to build for", "TARGET")
170 .optopt("", "host", "the host to build for", "HOST")
174 "path to CDB to use for CDB debuginfo tests",
180 "path to GDB to use for GDB debuginfo tests",
186 "the version of LLDB used",
192 "the version of LLVM used",
195 .optflag("", "system-llvm", "is LLVM the system LLVM")
198 "android-cross-path",
199 "Android NDK standalone path",
202 .optopt("", "adb-path", "path to the android debugger", "PATH")
206 "path to tests for the android debugger",
212 "directory containing LLDB's python module",
215 .reqopt("", "cc", "path to a C compiler", "PATH")
216 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
217 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
218 .optopt("", "ar", "path to an archiver", "PATH")
219 .optopt("", "linker", "path to a linker", "PATH")
223 "list of LLVM components built in",
226 .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
227 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
228 .optopt("", "nodejs", "the name of nodejs", "PATH")
231 "remote-test-client",
232 "path to the remote test client",
238 "mode describing what file the actual ui output will be compared to",
244 "enable this to generate a Rustfix coverage file, which is saved in \
245 `./<build_base>/rustfix_missing_coverage.txt`",
247 .optflag("h", "help", "show this message");
249 let (argv0, args_) = args.split_first().unwrap();
250 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
251 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
252 println!("{}", opts.usage(&message));
257 let matches = &match opts.parse(args_) {
259 Err(f) => panic!("{:?}", f),
262 if matches.opt_present("h") || matches.opt_present("help") {
263 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
264 println!("{}", opts.usage(&message));
269 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
270 match m.opt_str(nm) {
271 Some(s) => PathBuf::from(&s),
272 None => panic!("no option (=path) found for {}", nm),
276 fn make_absolute(path: PathBuf) -> PathBuf {
277 if path.is_relative() {
278 env::current_dir().unwrap().join(path)
284 let target = opt_str2(matches.opt_str("target"));
285 let android_cross_path = opt_path(matches, "android-cross-path");
286 let cdb = analyze_cdb(matches.opt_str("cdb"), &target);
287 let (gdb, gdb_version, gdb_native_rust) = analyze_gdb(matches.opt_str("gdb"), &target,
288 &android_cross_path);
289 let (lldb_version, lldb_native_rust) = extract_lldb_version(matches.opt_str("lldb-version"));
291 let color = match matches.opt_str("color").as_ref().map(|x| &**x) {
292 Some("auto") | None => ColorConfig::AutoColor,
293 Some("always") => ColorConfig::AlwaysColor,
294 Some("never") => ColorConfig::NeverColor,
296 "argument for --color must be auto, always, or never, but found `{}`",
301 let src_base = opt_path(matches, "src-base");
302 let run_ignored = matches.opt_present("ignored");
304 bless: matches.opt_present("bless"),
305 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
306 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
307 rustc_path: opt_path(matches, "rustc-path"),
308 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
309 lldb_python: matches.opt_str("lldb-python").unwrap(),
310 docck_python: matches.opt_str("docck-python").unwrap(),
311 valgrind_path: matches.opt_str("valgrind-path"),
312 force_valgrind: matches.opt_present("force-valgrind"),
313 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
314 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
315 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
317 build_base: opt_path(matches, "build-base"),
318 stage_id: matches.opt_str("stage-id").unwrap(),
323 .expect("invalid mode"),
325 filter: matches.free.first().cloned(),
326 filter_exact: matches.opt_present("exact"),
327 force_pass_mode: matches.opt_str("pass").map(|mode|
328 mode.parse::<PassMode>()
329 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
331 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
332 runtool: matches.opt_str("runtool"),
333 host_rustcflags: matches.opt_str("host-rustcflags"),
334 target_rustcflags: matches.opt_str("target-rustcflags"),
336 host: opt_str2(matches.opt_str("host")),
343 llvm_version: matches.opt_str("llvm-version"),
344 system_llvm: matches.opt_present("system-llvm"),
345 android_cross_path: android_cross_path,
346 adb_path: opt_str2(matches.opt_str("adb-path")),
347 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
348 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
349 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
350 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
351 lldb_python_dir: matches.opt_str("lldb-python-dir"),
352 verbose: matches.opt_present("verbose"),
353 quiet: matches.opt_present("quiet"),
355 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
356 compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
357 rustfix_coverage: matches.opt_present("rustfix-coverage"),
359 cc: matches.opt_str("cc").unwrap(),
360 cxx: matches.opt_str("cxx").unwrap(),
361 cflags: matches.opt_str("cflags").unwrap(),
362 ar: matches.opt_str("ar").unwrap_or("ar".into()),
363 linker: matches.opt_str("linker"),
364 llvm_components: matches.opt_str("llvm-components").unwrap(),
365 llvm_cxxflags: matches.opt_str("llvm-cxxflags").unwrap(),
366 nodejs: matches.opt_str("nodejs"),
370 pub fn log_config(config: &Config) {
372 logv(c, "configuration:".to_string());
375 format!("compile_lib_path: {:?}", config.compile_lib_path),
377 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
378 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
379 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
380 logv(c, format!("src_base: {:?}", config.src_base.display()));
381 logv(c, format!("build_base: {:?}", config.build_base.display()));
382 logv(c, format!("stage_id: {}", config.stage_id));
383 logv(c, format!("mode: {}", config.mode));
384 logv(c, format!("run_ignored: {}", config.run_ignored));
389 opt_str(&config.filter.as_ref().map(|re| re.to_owned()))
392 logv(c, format!("filter_exact: {}", config.filter_exact));
394 "force_pass_mode: {}",
395 opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),
397 logv(c, format!("runtool: {}", opt_str(&config.runtool)));
400 format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)),
404 format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)),
406 logv(c, format!("target: {}", config.target));
407 logv(c, format!("host: {}", config.host));
411 "android-cross-path: {:?}",
412 config.android_cross_path.display()
415 logv(c, format!("adb_path: {:?}", config.adb_path));
416 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
419 format!("adb_device_status: {}", config.adb_device_status),
421 logv(c, format!("ar: {}", config.ar));
422 logv(c, format!("linker: {:?}", config.linker));
423 logv(c, format!("verbose: {}", config.verbose));
424 logv(c, format!("quiet: {}", config.quiet));
425 logv(c, "\n".to_string());
428 pub fn opt_str(maybestr: &Option<String>) -> &str {
435 pub fn opt_str2(maybestr: Option<String>) -> String {
437 None => "(none)".to_owned(),
442 pub fn run_tests(config: &Config) {
443 if config.target.contains("android") {
444 if config.mode == DebugInfoGdb || config.mode == DebugInfoGdbLldb {
446 "{} debug-info test uses tcp 5039 port.\
451 // android debug-info test uses remote debugger so, we test 1 thread
452 // at once as they're all sharing the same TCP port to communicate
455 // we should figure out how to lift this restriction! (run them all
456 // on different ports allocated dynamically).
457 env::set_var("RUST_TEST_THREADS", "1");
462 // Note that we don't need to emit the gdb warning when
463 // DebugInfoGdbLldb, so it is ok to list that here.
464 DebugInfoGdbLldb | DebugInfoLldb => {
465 if let Some(lldb_version) = config.lldb_version.as_ref() {
466 if is_blacklisted_lldb_version(&lldb_version[..]) {
468 "WARNING: The used version of LLDB ({}) has a \
469 known issue that breaks debuginfo tests. See \
470 issue #32520 for more information. Skipping all \
478 // Some older versions of LLDB seem to have problems with multiple
479 // instances running in parallel, so only run one test thread at a
481 env::set_var("RUST_TEST_THREADS", "1");
485 if config.remote_test_client.is_some() && !config.target.contains("android") {
487 "WARNING: debuginfo tests are not available when \
494 DebugInfoCdb | _ => { /* proceed */ }
497 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
498 if let Mode::CodegenUnits = config.mode {
499 let _ = fs::remove_dir_all("tmp/partitioning-tests");
502 // If we want to collect rustfix coverage information,
503 // we first make sure that the coverage file does not exist.
504 // It will be created later on.
505 if config.rustfix_coverage {
506 let mut coverage_file_path = config.build_base.clone();
507 coverage_file_path.push("rustfix_missing_coverage.txt");
508 if coverage_file_path.exists() {
509 if let Err(e) = fs::remove_file(&coverage_file_path) {
510 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
515 let opts = test_opts(config);
516 let tests = make_tests(config);
517 // sadly osx needs some file descriptor limits raised for running tests in
518 // parallel (especially when we have lots and lots of child processes).
519 // For context, see #8904
521 raise_fd_limit::raise_fd_limit();
523 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
524 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
525 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
527 // Let tests know which target they're running as
528 env::set_var("TARGET", &config.target);
530 let res = test::run_tests_console(&opts, tests);
533 Ok(false) => panic!("Some tests failed"),
535 println!("I/O failure during tests: {:?}", e);
540 pub fn test_opts(config: &Config) -> test::TestOpts {
542 exclude_should_panic: false,
543 filter: config.filter.clone(),
544 filter_exact: config.filter_exact,
545 run_ignored: if config.run_ignored {
546 test::RunIgnored::Yes
550 format: if config.quiet {
551 test::OutputFormat::Terse
553 test::OutputFormat::Pretty
555 logfile: config.logfile.clone(),
557 bench_benchmarks: true,
558 nocapture: match env::var("RUST_TEST_NOCAPTURE") {
559 Ok(val) => &val != "0",
566 options: test::Options::new(),
570 pub fn make_tests(config: &Config) -> Vec<test::TestDescAndFn> {
571 debug!("making tests from {:?}", config.src_base.display());
572 let mut tests = Vec::new();
573 collect_tests_from_dir(
583 fn collect_tests_from_dir(
587 relative_dir_path: &Path,
588 tests: &mut Vec<test::TestDescAndFn>,
589 ) -> io::Result<()> {
590 // Ignore directories that contain a file named `compiletest-ignore-dir`.
591 if dir.join("compiletest-ignore-dir").exists() {
595 if config.mode == Mode::RunMake && dir.join("Makefile").exists() {
596 let paths = TestPaths {
597 file: dir.to_path_buf(),
598 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
600 tests.extend(make_test(config, &paths));
604 // If we find a test foo/bar.rs, we have to build the
605 // output directory `$build/foo` so we can write
606 // `$build/foo/bar` into it. We do this *now* in this
607 // sequential loop because otherwise, if we do it in the
608 // tests themselves, they race for the privilege of
609 // creating the directories and sometimes fail randomly.
610 let build_dir = output_relative_path(config, relative_dir_path);
611 fs::create_dir_all(&build_dir).unwrap();
613 // Add each `.rs` file as a test, and recurse further on any
614 // subdirectories we find, except for `aux` directories.
615 for file in fs::read_dir(dir)? {
617 let file_path = file.path();
618 let file_name = file.file_name();
619 if is_test(&file_name) {
620 debug!("found test file: {:?}", file_path.display());
621 let paths = TestPaths {
623 relative_dir: relative_dir_path.to_path_buf(),
625 tests.extend(make_test(config, &paths))
626 } else if file_path.is_dir() {
627 let relative_file_path = relative_dir_path.join(file.file_name());
628 if &file_name != "auxiliary" {
629 debug!("found directory: {:?}", file_path.display());
630 collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?;
633 debug!("found other file/directory: {:?}", file_path.display());
640 /// Returns true if `file_name` looks like a proper test file name.
641 pub fn is_test(file_name: &OsString) -> bool {
642 let file_name = file_name.to_str().unwrap();
644 if !file_name.ends_with(".rs") {
648 // `.`, `#`, and `~` are common temp-file prefixes.
649 let invalid_prefixes = &[".", "#", "~"];
650 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
653 pub fn make_test(config: &Config, testpaths: &TestPaths) -> Vec<test::TestDescAndFn> {
654 let early_props = if config.mode == Mode::RunMake {
655 // Allow `ignore` directives to be in the Makefile.
656 EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
658 EarlyProps::from_file(config, &testpaths.file)
661 // The `should-fail` annotation doesn't apply to pretty tests,
662 // since we run the pretty printer across all tests by default.
663 // If desired, we could add a `should-fail-pretty` annotation.
664 let should_panic = match config.mode {
665 Pretty => test::ShouldPanic::No,
666 _ => if early_props.should_fail {
667 test::ShouldPanic::Yes
669 test::ShouldPanic::No
673 // Incremental tests are special, they inherently cannot be run in parallel.
674 // `runtest::run` will be responsible for iterating over revisions.
675 let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental {
678 early_props.revisions.iter().map(|r| Some(r)).collect()
683 // Debugging emscripten code doesn't make sense today
684 let ignore = early_props.ignore == Ignore::Ignore
689 revision.map(|s| s.as_str()),
691 || ((config.mode == DebugInfoGdbLldb || config.mode == DebugInfoCdb ||
692 config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
693 && config.target.contains("emscripten"))
694 || (config.mode == DebugInfoGdb && !early_props.ignore.can_run_gdb())
695 || (config.mode == DebugInfoLldb && !early_props.ignore.can_run_lldb());
696 test::TestDescAndFn {
697 desc: test::TestDesc {
698 name: make_test_name(config, testpaths, revision),
703 testfn: make_test_closure(config, early_props.ignore, testpaths, revision),
709 fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
710 output_base_dir(config, testpaths, revision).join("stamp")
715 testpaths: &TestPaths,
717 revision: Option<&str>,
719 let stamp_name = stamp(config, testpaths, revision);
721 let contents = match fs::read_to_string(&stamp_name) {
723 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
724 Err(_) => return true,
726 let expected_hash = runtest::compute_stamp_hash(config);
727 if contents != expected_hash {
732 let rust_src_dir = config
733 .find_rust_src_root()
734 .expect("Could not find Rust source root");
735 let stamp = Stamp::from_path(&stamp_name);
736 let mut inputs = vec![Stamp::from_path(&testpaths.file), Stamp::from_path(&config.rustc_path)];
742 Stamp::from_path(&testpaths.file.parent().unwrap().join("auxiliary").join(aux))
745 // Relevant pretty printer files
746 let pretty_printer_files = [
747 "src/etc/debugger_pretty_printers_common.py",
748 "src/etc/gdb_load_rust_pretty_printers.py",
749 "src/etc/gdb_rust_pretty_printing.py",
750 "src/etc/lldb_batchmode.py",
751 "src/etc/lldb_rust_formatters.py",
753 inputs.extend(pretty_printer_files.iter().map(|pretty_printer_file| {
754 Stamp::from_path(&rust_src_dir.join(pretty_printer_file))
756 inputs.extend(Stamp::from_dir(&config.run_lib_path));
757 if let Some(ref rustdoc_path) = config.rustdoc_path {
758 inputs.push(Stamp::from_path(&rustdoc_path));
759 inputs.push(Stamp::from_path(&rust_src_dir.join("src/etc/htmldocck.py")));
763 inputs.extend(UI_EXTENSIONS.iter().map(|extension| {
764 let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension);
765 Stamp::from_path(path)
768 // Compiletest itself.
769 inputs.extend(Stamp::from_dir(&rust_src_dir.join("src/tools/compiletest/")));
771 inputs.iter().any(|input| input > &stamp)
774 #[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
781 fn from_path(p: &Path) -> Self {
782 let time = fs::metadata(p)
783 .and_then(|metadata| metadata.modified())
784 .unwrap_or(SystemTime::UNIX_EPOCH);
792 fn from_dir(path: &Path) -> impl Iterator<Item = Stamp> {
795 .map(|entry| entry.unwrap())
796 .filter(|entry| entry.file_type().is_file())
798 let time = (|| -> io::Result<_> { entry.metadata()?.modified() })();
801 time: time.unwrap_or(SystemTime::UNIX_EPOCH),
802 file: entry.path().into(),
810 testpaths: &TestPaths,
811 revision: Option<&String>,
812 ) -> test::TestName {
813 // Convert a complete path to something like
816 let path = PathBuf::from(config.src_base.file_name().unwrap())
817 .join(&testpaths.relative_dir)
818 .join(&testpaths.file.file_name().unwrap());
819 let mode_suffix = match config.compare_mode {
820 Some(ref mode) => format!(" ({})", mode.to_str()),
821 None => String::new(),
823 test::DynTestName(format!(
828 revision.map_or("".to_string(), |rev| format!("#{}", rev))
832 fn make_test_closure(
835 testpaths: &TestPaths,
836 revision: Option<&String>,
838 let mut config = config.clone();
839 if config.mode == DebugInfoGdbLldb {
840 // If both gdb and lldb were ignored, then the test as a whole
842 if !ignore.can_run_gdb() {
843 config.mode = DebugInfoLldb;
844 } else if !ignore.can_run_lldb() {
845 config.mode = DebugInfoGdb;
849 let testpaths = testpaths.clone();
850 let revision = revision.cloned();
851 test::DynTestFn(Box::new(move || {
852 runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str()))
856 /// Returns `true` if the given target is an Android target for the
857 /// purposes of GDB testing.
858 fn is_android_gdb_target(target: &String) -> bool {
860 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
865 /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing.
866 fn is_pc_windows_msvc_target(target: &String) -> bool {
867 target.ends_with("-pc-windows-msvc")
870 fn find_cdb(target: &String) -> Option<OsString> {
871 if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
875 let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?;
876 let cdb_arch = if cfg!(target_arch="x86") {
878 } else if cfg!(target_arch="x86_64") {
880 } else if cfg!(target_arch="aarch64") {
882 } else if cfg!(target_arch="arm") {
885 return None; // No compatible CDB.exe in the Windows 10 SDK
888 let mut path = PathBuf::new();
890 path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
892 path.push(r"cdb.exe");
898 Some(path.into_os_string())
901 /// Returns Path to CDB
902 fn analyze_cdb(cdb: Option<String>, target: &String) -> Option<OsString> {
903 cdb.map(|s| OsString::from(s)).or(find_cdb(target))
906 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
907 fn analyze_gdb(gdb: Option<String>, target: &String, android_cross_path: &PathBuf)
908 -> (Option<String>, Option<u32>, bool) {
910 const GDB_FALLBACK: &str = "gdb";
912 const GDB_FALLBACK: &str = "gdb.exe";
914 const MIN_GDB_WITH_RUST: u32 = 7011010;
916 let fallback_gdb = || {
917 if is_android_gdb_target(target) {
918 let mut gdb_path = match android_cross_path.to_str() {
919 Some(x) => x.to_owned(),
920 None => panic!("cannot find android cross path"),
922 gdb_path.push_str("/bin/gdb");
925 GDB_FALLBACK.to_owned()
929 let gdb = match gdb {
930 None => fallback_gdb(),
931 Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
932 Some(ref s) => s.to_owned(),
935 let mut version_line = None;
936 if let Ok(output) = Command::new(&gdb).arg("--version").output() {
937 if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
938 version_line = Some(first_line.to_string());
942 let version = match version_line {
943 Some(line) => extract_gdb_version(&line),
944 None => return (None, None, false),
947 let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
949 (Some(gdb), version, gdb_native_rust)
952 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
953 let full_version_line = full_version_line.trim();
955 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
956 // of the ? sections being optional
958 // We will parse up to 3 digits for minor and patch, ignoring the date
959 // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
961 // don't start parsing in the middle of a number
962 let mut prev_was_digit = false;
963 for (pos, c) in full_version_line.char_indices() {
964 if prev_was_digit || !c.is_digit(10) {
965 prev_was_digit = c.is_digit(10);
969 prev_was_digit = true;
971 let line = &full_version_line[pos..];
973 let next_split = match line.find(|c: char| !c.is_digit(10)) {
975 None => continue, // no minor version
978 if line.as_bytes()[next_split] != b'.' {
979 continue; // no minor version
982 let major = &line[..next_split];
983 let line = &line[next_split + 1..];
985 let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
986 Some(idx) => if line.as_bytes()[idx] == b'.' {
987 let patch = &line[idx + 1..];
989 let patch_len = patch
990 .find(|c: char| !c.is_digit(10))
991 .unwrap_or_else(|| patch.len());
992 let patch = &patch[..patch_len];
993 let patch = if patch_len > 3 || patch_len == 0 {
999 (&line[..idx], patch)
1001 (&line[..idx], None)
1003 None => (line, None),
1006 if major.len() != 1 || minor.is_empty() {
1010 let major: u32 = major.parse().unwrap();
1011 let minor: u32 = minor.parse().unwrap();
1012 let patch: u32 = patch.unwrap_or("0").parse().unwrap();
1014 return Some(((major * 1000) + minor) * 1000 + patch);
1020 /// Returns (LLDB version, LLDB is rust-enabled)
1021 fn extract_lldb_version(full_version_line: Option<String>) -> (Option<String>, bool) {
1022 // Extract the major LLDB version from the given version string.
1023 // LLDB version strings are different for Apple and non-Apple platforms.
1024 // The Apple variant looks like this:
1026 // LLDB-179.5 (older versions)
1027 // lldb-300.2.51 (new versions)
1029 // We are only interested in the major version number, so this function
1030 // will return `Some("179")` and `Some("300")` respectively.
1032 // Upstream versions look like:
1033 // lldb version 6.0.1
1035 // There doesn't seem to be a way to correlate the Apple version
1036 // with the upstream version, and since the tests were originally
1037 // written against Apple versions, we make a fake Apple version by
1038 // multiplying the first number by 100. This is a hack, but
1039 // normally fine because the only non-Apple version we test is
1042 if let Some(ref full_version_line) = full_version_line {
1043 if !full_version_line.trim().is_empty() {
1044 let full_version_line = full_version_line.trim();
1046 for (pos, l) in full_version_line.char_indices() {
1047 if l != 'l' && l != 'L' {
1050 if pos + 5 >= full_version_line.len() {
1053 let l = full_version_line[pos + 1..].chars().next().unwrap();
1054 if l != 'l' && l != 'L' {
1057 let d = full_version_line[pos + 2..].chars().next().unwrap();
1058 if d != 'd' && d != 'D' {
1061 let b = full_version_line[pos + 3..].chars().next().unwrap();
1062 if b != 'b' && b != 'B' {
1065 let dash = full_version_line[pos + 4..].chars().next().unwrap();
1070 let vers = full_version_line[pos + 5..]
1072 .take_while(|c| c.is_digit(10))
1073 .collect::<String>();
1074 if !vers.is_empty() {
1075 return (Some(vers), full_version_line.contains("rust-enabled"));
1079 if full_version_line.starts_with("lldb version ") {
1080 let vers = full_version_line[13..]
1082 .take_while(|c| c.is_digit(10))
1083 .collect::<String>();
1084 if !vers.is_empty() {
1085 return (Some(vers + "00"), full_version_line.contains("rust-enabled"));
1093 fn is_blacklisted_lldb_version(version: &str) -> bool {
1098 fn test_extract_gdb_version() {
1099 macro_rules! test { ($($expectation:tt: $input:tt,)*) => {{$(
1100 assert_eq!(extract_gdb_version($input), Some($expectation));
1104 7000001: "GNU gdb (GDB) CentOS (7.0.1-45.el5.centos)",
1106 7002000: "GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)",
1108 7004000: "GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04",
1109 7004001: "GNU gdb (GDB) 7.4.1-debian",
1111 7006001: "GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7",
1113 7007001: "GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1",
1114 7007001: "GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1",
1115 7007001: "GNU gdb (GDB) Fedora 7.7.1-21.fc20",
1117 7008000: "GNU gdb (GDB; openSUSE 13.2) 7.8",
1118 7009001: "GNU gdb (GDB) Fedora 7.9.1-20.fc22",
1119 7010001: "GNU gdb (GDB) Fedora 7.10.1-31.fc23",
1121 7011000: "GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11",
1122 7011001: "GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1",
1123 7011001: "GNU gdb (Debian 7.11.1-2) 7.11.1",
1124 7011001: "GNU gdb (GDB) Fedora 7.11.1-86.fc24",
1125 7011001: "GNU gdb (GDB; openSUSE Leap 42.1) 7.11.1",
1126 7011001: "GNU gdb (GDB; openSUSE Tumbleweed) 7.11.1",
1129 7011090: "GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu1) 7.11.90.20161005-git",
1132 7012000: "GNU gdb (GDB) 7.12",
1133 7012000: "GNU gdb (GDB) 7.12.20161027-git",
1134 7012050: "GNU gdb (GDB) 7.12.50.20161027-git",
1140 assert_eq!(true, is_test(&OsString::from("a_test.rs")));
1141 assert_eq!(false, is_test(&OsString::from(".a_test.rs")));
1142 assert_eq!(false, is_test(&OsString::from("a_cat.gif")));
1143 assert_eq!(false, is_test(&OsString::from("#a_dog_gif")));
1144 assert_eq!(false, is_test(&OsString::from("~a_temp_file")));