1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 #![crate_name = "compiletest"]
16 extern crate env_logger
;
17 extern crate filetime
;
25 extern crate lazy_static
;
27 extern crate serde_derive
;
28 extern crate serde_json
;
32 use common
::CompareMode
;
33 use common
::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}
;
34 use common
::{Config, TestPaths}
;
35 use common
::{DebugInfoBoth, DebugInfoGdb, DebugInfoLldb, Mode, Pretty}
;
36 use filetime
::FileTime
;
39 use std
::ffi
::OsString
;
41 use std
::io
::{self, Read}
;
42 use std
::path
::{Path, PathBuf}
;
43 use std
::process
::Command
;
44 use test
::ColorConfig
;
47 use self::header
::{EarlyProps, Ignore}
;
61 let config
= parse_config(env
::args().collect());
63 if config
.valgrind_path
.is_none() && config
.force_valgrind
{
64 panic
!("Can't find Valgrind to run Valgrind tests");
71 pub fn parse_config(args
: Vec
<String
>) -> Config
{
72 let mut opts
= Options
::new();
76 "path to host shared libraries",
81 "path to target shared libraries",
87 "path to rustc to use for compiling",
93 "path to rustdoc to use for compiling",
99 "path to python to use for doc tests",
105 "path to python to use for doc tests",
111 "path to Valgrind executable for Valgrind tests",
117 "fail if Valgrind tests cannot be run under Valgrind",
122 "path to LLVM's FileCheck binary",
125 .reqopt("", "src-base", "directory to scan for test files", "PATH")
129 "directory to deposit test outputs",
135 "the target-stage identifier",
141 "which sort of compile tests to run",
142 "(compile-fail|run-fail|run-pass|\
143 run-pass-valgrind|pretty|debug-info|incremental|mir-opt)",
145 .optflag("", "ignored", "run tests marked as ignored")
146 .optflag("", "exact", "filters match exactly")
150 "supervisor program to run tests under \
151 (eg. emulator, valgrind)",
157 "flags to pass to rustc for host",
163 "flags to pass to rustc for target",
166 .optflag("", "verbose", "run tests verbosely, showing all output")
170 "overwrite stderr/stdout files instead of complaining about a mismatch",
175 "print one character per test instead of one line",
177 .optopt("", "color", "coloring: auto, always, never", "WHEN")
178 .optopt("", "logfile", "file to log test execution to", "FILE")
179 .optopt("", "target", "the target to build for", "TARGET")
180 .optopt("", "host", "the host to build for", "HOST")
184 "path to GDB to use for GDB debuginfo tests",
190 "the version of LLDB used",
196 "the version of LLVM used",
199 .optflag("", "system-llvm", "is LLVM the system LLVM")
202 "android-cross-path",
203 "Android NDK standalone path",
206 .optopt("", "adb-path", "path to the android debugger", "PATH")
210 "path to tests for the android debugger",
216 "directory containing LLDB's python module",
219 .reqopt("", "cc", "path to a C compiler", "PATH")
220 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
221 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
222 .optopt("", "ar", "path to an archiver", "PATH")
223 .optopt("", "linker", "path to a linker", "PATH")
227 "list of LLVM components built in",
230 .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
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",
244 .optflag("h", "help", "show this message");
246 let (argv0
, args_
) = args
.split_first().unwrap();
247 if args
.len() == 1 || args
[1] == "-h" || args
[1] == "--help" {
248 let message
= format
!("Usage: {} [OPTIONS] [TESTNAME...]", argv0
);
249 println
!("{}", opts
.usage(&message
));
254 let matches
= &match opts
.parse(args_
) {
256 Err(f
) => panic
!("{:?}", f
),
259 if matches
.opt_present("h") || matches
.opt_present("help") {
260 let message
= format
!("Usage: {} [OPTIONS] [TESTNAME...]", argv0
);
261 println
!("{}", opts
.usage(&message
));
266 fn opt_path(m
: &getopts
::Matches
, nm
: &str) -> PathBuf
{
267 match m
.opt_str(nm
) {
268 Some(s
) => PathBuf
::from(&s
),
269 None
=> panic
!("no option (=path) found for {}", nm
),
273 fn make_absolute(path
: PathBuf
) -> PathBuf
{
274 if path
.is_relative() {
275 env
::current_dir().unwrap().join(path
)
281 let target
= opt_str2(matches
.opt_str("target"));
282 let android_cross_path
= opt_path(matches
, "android-cross-path");
283 let (gdb
, gdb_version
, gdb_native_rust
) = analyze_gdb(matches
.opt_str("gdb"), &target
,
284 &android_cross_path
);
285 let (lldb_version
, lldb_native_rust
) = extract_lldb_version(matches
.opt_str("lldb-version"));
287 let color
= match matches
.opt_str("color").as_ref().map(|x
| &**x
) {
288 Some("auto") | None
=> ColorConfig
::AutoColor
,
289 Some("always") => ColorConfig
::AlwaysColor
,
290 Some("never") => ColorConfig
::NeverColor
,
292 "argument for --color must be auto, always, or never, but found `{}`",
297 let src_base
= opt_path(matches
, "src-base");
298 let run_ignored
= matches
.opt_present("ignored");
300 bless
: matches
.opt_present("bless"),
301 compile_lib_path
: make_absolute(opt_path(matches
, "compile-lib-path")),
302 run_lib_path
: make_absolute(opt_path(matches
, "run-lib-path")),
303 rustc_path
: opt_path(matches
, "rustc-path"),
304 rustdoc_path
: matches
.opt_str("rustdoc-path").map(PathBuf
::from
),
305 lldb_python
: matches
.opt_str("lldb-python").unwrap(),
306 docck_python
: matches
.opt_str("docck-python").unwrap(),
307 valgrind_path
: matches
.opt_str("valgrind-path"),
308 force_valgrind
: matches
.opt_present("force-valgrind"),
309 llvm_filecheck
: matches
.opt_str("llvm-filecheck").map(|s
| PathBuf
::from(&s
)),
311 build_base
: opt_path(matches
, "build-base"),
312 stage_id
: matches
.opt_str("stage-id").unwrap(),
317 .expect("invalid mode"),
319 filter
: matches
.free
.first().cloned(),
320 filter_exact
: matches
.opt_present("exact"),
321 logfile
: matches
.opt_str("logfile").map(|s
| PathBuf
::from(&s
)),
322 runtool
: matches
.opt_str("runtool"),
323 host_rustcflags
: matches
.opt_str("host-rustcflags"),
324 target_rustcflags
: matches
.opt_str("target-rustcflags"),
326 host
: opt_str2(matches
.opt_str("host")),
332 llvm_version
: matches
.opt_str("llvm-version"),
333 system_llvm
: matches
.opt_present("system-llvm"),
334 android_cross_path
: android_cross_path
,
335 adb_path
: opt_str2(matches
.opt_str("adb-path")),
336 adb_test_dir
: opt_str2(matches
.opt_str("adb-test-dir")),
337 adb_device_status
: opt_str2(matches
.opt_str("target")).contains("android")
338 && "(none)" != opt_str2(matches
.opt_str("adb-test-dir"))
339 && !opt_str2(matches
.opt_str("adb-test-dir")).is_empty(),
340 lldb_python_dir
: matches
.opt_str("lldb-python-dir"),
341 verbose
: matches
.opt_present("verbose"),
342 quiet
: matches
.opt_present("quiet"),
344 remote_test_client
: matches
.opt_str("remote-test-client").map(PathBuf
::from
),
345 compare_mode
: matches
.opt_str("compare-mode").map(CompareMode
::parse
),
347 cc
: matches
.opt_str("cc").unwrap(),
348 cxx
: matches
.opt_str("cxx").unwrap(),
349 cflags
: matches
.opt_str("cflags").unwrap(),
350 ar
: matches
.opt_str("ar").unwrap_or("ar".into()),
351 linker
: matches
.opt_str("linker"),
352 llvm_components
: matches
.opt_str("llvm-components").unwrap(),
353 llvm_cxxflags
: matches
.opt_str("llvm-cxxflags").unwrap(),
354 nodejs
: matches
.opt_str("nodejs"),
358 pub fn log_config(config
: &Config
) {
360 logv(c
, "configuration:".to_string());
363 format
!("compile_lib_path: {:?}", config
.compile_lib_path
),
365 logv(c
, format
!("run_lib_path: {:?}", config
.run_lib_path
));
366 logv(c
, format
!("rustc_path: {:?}", config
.rustc_path
.display()));
367 logv(c
, format
!("rustdoc_path: {:?}", config
.rustdoc_path
));
368 logv(c
, format
!("src_base: {:?}", config
.src_base
.display()));
369 logv(c
, format
!("build_base: {:?}", config
.build_base
.display()));
370 logv(c
, format
!("stage_id: {}", config
.stage_id
));
371 logv(c
, format
!("mode: {}", config
.mode
));
372 logv(c
, format
!("run_ignored: {}", config
.run_ignored
));
377 opt_str(&config
.filter
.as_ref().map(|re
| re
.to_owned()))
380 logv(c
, format
!("filter_exact: {}", config
.filter_exact
));
381 logv(c
, format
!("runtool: {}", opt_str(&config
.runtool
)));
384 format
!("host-rustcflags: {}", opt_str(&config
.host_rustcflags
)),
388 format
!("target-rustcflags: {}", opt_str(&config
.target_rustcflags
)),
390 logv(c
, format
!("target: {}", config
.target
));
391 logv(c
, format
!("host: {}", config
.host
));
395 "android-cross-path: {:?}",
396 config
.android_cross_path
.display()
399 logv(c
, format
!("adb_path: {:?}", config
.adb_path
));
400 logv(c
, format
!("adb_test_dir: {:?}", config
.adb_test_dir
));
403 format
!("adb_device_status: {}", config
.adb_device_status
),
405 logv(c
, format
!("ar: {}", config
.ar
));
406 logv(c
, format
!("linker: {:?}", config
.linker
));
407 logv(c
, format
!("verbose: {}", config
.verbose
));
408 logv(c
, format
!("quiet: {}", config
.quiet
));
409 logv(c
, "\n".to_string());
412 pub fn opt_str(maybestr
: &Option
<String
>) -> &str {
419 pub fn opt_str2(maybestr
: Option
<String
>) -> String
{
421 None
=> "(none)".to_owned(),
426 pub fn run_tests(config
: &Config
) {
427 if config
.target
.contains("android") {
428 if config
.mode
== DebugInfoGdb
|| config
.mode
== DebugInfoBoth
{
430 "{} debug-info test uses tcp 5039 port.\
435 // android debug-info test uses remote debugger so, we test 1 thread
436 // at once as they're all sharing the same TCP port to communicate
439 // we should figure out how to lift this restriction! (run them all
440 // on different ports allocated dynamically).
441 env
::set_var("RUST_TEST_THREADS", "1");
446 // Note that we don't need to emit the gdb warning when
447 // DebugInfoBoth, so it is ok to list that here.
448 DebugInfoBoth
| DebugInfoLldb
=> {
449 if let Some(lldb_version
) = config
.lldb_version
.as_ref() {
450 if is_blacklisted_lldb_version(&lldb_version
[..]) {
452 "WARNING: The used version of LLDB ({}) has a \
453 known issue that breaks debuginfo tests. See \
454 issue #32520 for more information. Skipping all \
462 // Some older versions of LLDB seem to have problems with multiple
463 // instances running in parallel, so only run one test thread at a
465 env
::set_var("RUST_TEST_THREADS", "1");
469 if config
.remote_test_client
.is_some() && !config
.target
.contains("android") {
471 "WARNING: debuginfo tests are not available when \
477 _
=> { /* proceed */ }
480 // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
481 if let Mode
::CodegenUnits
= config
.mode
{
482 let _
= fs
::remove_dir_all("tmp/partitioning-tests");
485 let opts
= test_opts(config
);
486 let tests
= make_tests(config
);
487 // sadly osx needs some file descriptor limits raised for running tests in
488 // parallel (especially when we have lots and lots of child processes).
489 // For context, see #8904
491 raise_fd_limit
::raise_fd_limit();
493 // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
494 // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
495 env
::set_var("__COMPAT_LAYER", "RunAsInvoker");
497 // Let tests know which target they're running as
498 env
::set_var("TARGET", &config
.target
);
500 let res
= test
::run_tests_console(&opts
, tests
.into_iter().collect());
503 Ok(false) => panic
!("Some tests failed"),
505 println
!("I/O failure during tests: {:?}", e
);
510 pub fn test_opts(config
: &Config
) -> test
::TestOpts
{
512 filter
: config
.filter
.clone(),
513 filter_exact
: config
.filter_exact
,
514 run_ignored
: config
.run_ignored
,
515 format
: if config
.quiet
{
516 test
::OutputFormat
::Terse
518 test
::OutputFormat
::Pretty
520 logfile
: config
.logfile
.clone(),
522 bench_benchmarks
: true,
523 nocapture
: match env
::var("RUST_TEST_NOCAPTURE") {
524 Ok(val
) => &val
!= "0",
531 options
: test
::Options
::new(),
535 pub fn make_tests(config
: &Config
) -> Vec
<test
::TestDescAndFn
> {
536 debug
!("making tests from {:?}", config
.src_base
.display());
537 let mut tests
= Vec
::new();
538 collect_tests_from_dir(
548 fn collect_tests_from_dir(
552 relative_dir_path
: &Path
,
553 tests
: &mut Vec
<test
::TestDescAndFn
>,
554 ) -> io
::Result
<()> {
555 // Ignore directories that contain a file
556 // `compiletest-ignore-dir`.
557 for file
in fs
::read_dir(dir
)?
{
559 let name
= file
.file_name();
560 if name
== *"compiletest-ignore-dir" {
563 if name
== *"Makefile" && config
.mode
== Mode
::RunMake
{
564 let paths
= TestPaths
{
565 file
: dir
.to_path_buf(),
566 relative_dir
: relative_dir_path
.parent().unwrap().to_path_buf(),
568 tests
.extend(make_test(config
, &paths
));
573 // If we find a test foo/bar.rs, we have to build the
574 // output directory `$build/foo` so we can write
575 // `$build/foo/bar` into it. We do this *now* in this
576 // sequential loop because otherwise, if we do it in the
577 // tests themselves, they race for the privilege of
578 // creating the directories and sometimes fail randomly.
579 let build_dir
= output_relative_path(config
, relative_dir_path
);
580 fs
::create_dir_all(&build_dir
).unwrap();
582 // Add each `.rs` file as a test, and recurse further on any
583 // subdirectories we find, except for `aux` directories.
584 let dirs
= fs
::read_dir(dir
)?
;
587 let file_path
= file
.path();
588 let file_name
= file
.file_name();
589 if is_test(&file_name
) {
590 debug
!("found test file: {:?}", file_path
.display());
591 let paths
= TestPaths
{
593 relative_dir
: relative_dir_path
.to_path_buf(),
595 tests
.extend(make_test(config
, &paths
))
596 } else if file_path
.is_dir() {
597 let relative_file_path
= relative_dir_path
.join(file
.file_name());
598 if &file_name
!= "auxiliary" {
599 debug
!("found directory: {:?}", file_path
.display());
600 collect_tests_from_dir(config
, base
, &file_path
, &relative_file_path
, tests
)?
;
603 debug
!("found other file/directory: {:?}", file_path
.display());
609 pub fn is_test(file_name
: &OsString
) -> bool
{
610 let file_name
= file_name
.to_str().unwrap();
612 if !file_name
.ends_with(".rs") {
616 // `.`, `#`, and `~` are common temp-file prefixes.
617 let invalid_prefixes
= &[".", "#", "~"];
618 !invalid_prefixes
.iter().any(|p
| file_name
.starts_with(p
))
621 pub fn make_test(config
: &Config
, testpaths
: &TestPaths
) -> Vec
<test
::TestDescAndFn
> {
622 let early_props
= if config
.mode
== Mode
::RunMake
{
623 // Allow `ignore` directives to be in the Makefile.
624 EarlyProps
::from_file(config
, &testpaths
.file
.join("Makefile"))
626 EarlyProps
::from_file(config
, &testpaths
.file
)
629 // The `should-fail` annotation doesn't apply to pretty tests,
630 // since we run the pretty printer across all tests by default.
631 // If desired, we could add a `should-fail-pretty` annotation.
632 let should_panic
= match config
.mode
{
633 Pretty
=> test
::ShouldPanic
::No
,
634 _
=> if early_props
.should_fail
{
635 test
::ShouldPanic
::Yes
637 test
::ShouldPanic
::No
641 // Incremental tests are special, they inherently cannot be run in parallel.
642 // `runtest::run` will be responsible for iterating over revisions.
643 let revisions
= if early_props
.revisions
.is_empty() || config
.mode
== Mode
::Incremental
{
646 early_props
.revisions
.iter().map(|r
| Some(r
)).collect()
651 // Debugging emscripten code doesn't make sense today
652 let ignore
= early_props
.ignore
== Ignore
::Ignore
657 revision
.map(|s
| s
.as_str()),
659 || ((config
.mode
== DebugInfoBoth
||
660 config
.mode
== DebugInfoGdb
|| config
.mode
== DebugInfoLldb
)
661 && config
.target
.contains("emscripten"))
662 || (config
.mode
== DebugInfoGdb
&& !early_props
.ignore
.can_run_gdb())
663 || (config
.mode
== DebugInfoLldb
&& !early_props
.ignore
.can_run_lldb());
664 test
::TestDescAndFn
{
665 desc
: test
::TestDesc
{
666 name
: make_test_name(config
, testpaths
, revision
),
671 testfn
: make_test_closure(config
, early_props
.ignore
, testpaths
, revision
),
677 fn stamp(config
: &Config
, testpaths
: &TestPaths
, revision
: Option
<&str>) -> PathBuf
{
678 output_base_dir(config
, testpaths
, revision
).join("stamp")
683 testpaths
: &TestPaths
,
685 revision
: Option
<&str>,
687 let stamp_name
= stamp(config
, testpaths
, revision
);
689 let mut f
= match fs
::File
::open(&stamp_name
) {
691 Err(_
) => return true,
693 let mut contents
= String
::new();
694 f
.read_to_string(&mut contents
)
695 .expect("Can't read stamp contents");
696 let expected_hash
= runtest
::compute_stamp_hash(config
);
697 if contents
!= expected_hash
{
702 let rust_src_dir
= config
703 .find_rust_src_root()
704 .expect("Could not find Rust source root");
705 let stamp
= mtime(&stamp_name
);
706 let mut inputs
= vec
![mtime(&testpaths
.file
), mtime(&config
.rustc_path
)];
707 for aux
in props
.aux
.iter() {
708 inputs
.push(mtime(&testpaths
715 // Relevant pretty printer files
716 let pretty_printer_files
= [
717 "src/etc/debugger_pretty_printers_common.py",
718 "src/etc/gdb_load_rust_pretty_printers.py",
719 "src/etc/gdb_rust_pretty_printing.py",
720 "src/etc/lldb_batchmode.py",
721 "src/etc/lldb_rust_formatters.py",
723 for pretty_printer_file
in &pretty_printer_files
{
724 inputs
.push(mtime(&rust_src_dir
.join(pretty_printer_file
)));
726 let mut entries
= config
.run_lib_path
.read_dir().unwrap().collect
::<Vec
<_
>>();
727 while let Some(entry
) = entries
.pop() {
728 let entry
= entry
.unwrap();
729 let path
= entry
.path();
730 if entry
.metadata().unwrap().is_file() {
731 inputs
.push(mtime(&path
));
733 entries
.extend(path
.read_dir().unwrap());
736 if let Some(ref rustdoc_path
) = config
.rustdoc_path
{
737 inputs
.push(mtime(&rustdoc_path
));
738 inputs
.push(mtime(&rust_src_dir
.join("src/etc/htmldocck.py")));
742 for extension
in UI_EXTENSIONS
{
743 let path
= &expected_output_path(testpaths
, revision
, &config
.compare_mode
, extension
);
744 inputs
.push(mtime(path
));
747 inputs
.iter().any(|input
| *input
> stamp
)
750 fn mtime(path
: &Path
) -> FileTime
{
752 .map(|f
| FileTime
::from_last_modification_time(&f
))
753 .unwrap_or_else(|_
| FileTime
::zero())
758 testpaths
: &TestPaths
,
759 revision
: Option
<&String
>,
760 ) -> test
::TestName
{
761 // Convert a complete path to something like
763 // run-pass/foo/bar/baz.rs
764 let path
= PathBuf
::from(config
.src_base
.file_name().unwrap())
765 .join(&testpaths
.relative_dir
)
766 .join(&testpaths
.file
.file_name().unwrap());
767 let mode_suffix
= match config
.compare_mode
{
768 Some(ref mode
) => format
!(" ({})", mode
.to_str()),
771 test
::DynTestName(format
!(
776 revision
.map_or("".to_string(), |rev
| format
!("#{}", rev
))
780 fn make_test_closure(
783 testpaths
: &TestPaths
,
784 revision
: Option
<&String
>,
786 let mut config
= config
.clone();
787 if config
.mode
== DebugInfoBoth
{
788 // If both gdb and lldb were ignored, then the test as a whole
790 if !ignore
.can_run_gdb() {
791 config
.mode
= DebugInfoLldb
;
792 } else if !ignore
.can_run_lldb() {
793 config
.mode
= DebugInfoGdb
;
797 let testpaths
= testpaths
.clone();
798 let revision
= revision
.cloned();
799 test
::DynTestFn(Box
::new(move || {
800 runtest
::run(config
, &testpaths
, revision
.as_ref().map(|s
| s
.as_str()))
804 /// Returns true if the given target is an Android target for the
805 /// purposes of GDB testing.
806 fn is_android_gdb_target(target
: &String
) -> bool
{
808 "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true,
813 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
814 fn analyze_gdb(gdb
: Option
<String
>, target
: &String
, android_cross_path
: &PathBuf
)
815 -> (Option
<String
>, Option
<u32>, bool
) {
817 const GDB_FALLBACK
: &str = "gdb";
819 const GDB_FALLBACK
: &str = "gdb.exe";
821 const MIN_GDB_WITH_RUST
: u32 = 7011010;
823 let fallback_gdb
= || {
824 if is_android_gdb_target(target
) {
825 let mut gdb_path
= match android_cross_path
.to_str() {
826 Some(x
) => x
.to_owned(),
827 None
=> panic
!("cannot find android cross path"),
829 gdb_path
.push_str("/bin/gdb");
832 GDB_FALLBACK
.to_owned()
836 let gdb
= match gdb
{
837 None
=> fallback_gdb(),
838 Some(ref s
) if s
.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
839 Some(ref s
) => s
.to_owned(),
842 let mut version_line
= None
;
843 if let Ok(output
) = Command
::new(&gdb
).arg("--version").output() {
844 if let Some(first_line
) = String
::from_utf8_lossy(&output
.stdout
).lines().next() {
845 version_line
= Some(first_line
.to_string());
849 let version
= match version_line
{
850 Some(line
) => extract_gdb_version(&line
),
851 None
=> return (None
, None
, false),
854 let gdb_native_rust
= version
.map_or(false, |v
| v
>= MIN_GDB_WITH_RUST
);
856 (Some(gdb
), version
, gdb_native_rust
)
859 fn extract_gdb_version(full_version_line
: &str) -> Option
<u32> {
860 let full_version_line
= full_version_line
.trim();
862 // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
863 // of the ? sections being optional
865 // We will parse up to 3 digits for minor and patch, ignoring the date
866 // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
868 // don't start parsing in the middle of a number
869 let mut prev_was_digit
= false;
870 for (pos
, c
) in full_version_line
.char_indices() {
871 if prev_was_digit
|| !c
.is_digit(10) {
872 prev_was_digit
= c
.is_digit(10);
876 prev_was_digit
= true;
878 let line
= &full_version_line
[pos
..];
880 let next_split
= match line
.find(|c
: char| !c
.is_digit(10)) {
882 None
=> continue, // no minor version
885 if line
.as_bytes()[next_split
] != b'
.'
{
886 continue; // no minor version
889 let major
= &line
[..next_split
];
890 let line
= &line
[next_split
+ 1..];
892 let (minor
, patch
) = match line
.find(|c
: char| !c
.is_digit(10)) {
893 Some(idx
) => if line
.as_bytes()[idx
] == b'
.'
{
894 let patch
= &line
[idx
+ 1..];
896 let patch_len
= patch
897 .find(|c
: char| !c
.is_digit(10))
898 .unwrap_or_else(|| patch
.len());
899 let patch
= &patch
[..patch_len
];
900 let patch
= if patch_len
> 3 || patch_len
== 0 {
906 (&line
[..idx
], patch
)
910 None
=> (line
, None
),
913 if major
.len() != 1 || minor
.is_empty() {
917 let major
: u32 = major
.parse().unwrap();
918 let minor
: u32 = minor
.parse().unwrap();
919 let patch
: u32 = patch
.unwrap_or("0").parse().unwrap();
921 return Some(((major
* 1000) + minor
) * 1000 + patch
);
927 /// Returns (LLDB version, LLDB is rust-enabled)
928 fn extract_lldb_version(full_version_line
: Option
<String
>) -> (Option
<String
>, bool
) {
929 // Extract the major LLDB version from the given version string.
930 // LLDB version strings are different for Apple and non-Apple platforms.
931 // The Apple variant looks like this:
933 // LLDB-179.5 (older versions)
934 // lldb-300.2.51 (new versions)
936 // We are only interested in the major version number, so this function
937 // will return `Some("179")` and `Some("300")` respectively.
939 // Upstream versions look like:
940 // lldb version 6.0.1
942 // There doesn't seem to be a way to correlate the Apple version
943 // with the upstream version, and since the tests were originally
944 // written against Apple versions, we make a fake Apple version by
945 // multiplying the first number by 100. This is a hack, but
946 // normally fine because the only non-Apple version we test is
949 if let Some(ref full_version_line
) = full_version_line
{
950 if !full_version_line
.trim().is_empty() {
951 let full_version_line
= full_version_line
.trim();
953 for (pos
, l
) in full_version_line
.char_indices() {
954 if l
!= 'l'
&& l
!= 'L'
{
957 if pos
+ 5 >= full_version_line
.len() {
960 let l
= full_version_line
[pos
+ 1..].chars().next().unwrap();
961 if l
!= 'l'
&& l
!= 'L'
{
964 let d
= full_version_line
[pos
+ 2..].chars().next().unwrap();
965 if d
!= 'd'
&& d
!= 'D'
{
968 let b
= full_version_line
[pos
+ 3..].chars().next().unwrap();
969 if b
!= 'b'
&& b
!= 'B'
{
972 let dash
= full_version_line
[pos
+ 4..].chars().next().unwrap();
977 let vers
= full_version_line
[pos
+ 5..]
979 .take_while(|c
| c
.is_digit(10))
980 .collect
::<String
>();
981 if !vers
.is_empty() {
982 return (Some(vers
), full_version_line
.contains("rust-enabled"));
986 if full_version_line
.starts_with("lldb version ") {
987 let vers
= full_version_line
[13..]
989 .take_while(|c
| c
.is_digit(10))
990 .collect
::<String
>();
991 if !vers
.is_empty() {
992 return (Some(vers
+ "00"), full_version_line
.contains("rust-enabled"));
1000 fn is_blacklisted_lldb_version(version
: &str) -> bool
{
1005 fn test_extract_gdb_version() {
1006 macro_rules
! test
{ ($
($expectation
:tt
: $input
:tt
,)*) => {{$
(
1007 assert_eq
!(extract_gdb_version($input
), Some($expectation
));
1011 7000001: "GNU gdb (GDB) CentOS (7.0.1-45.el5.centos)",
1013 7002000: "GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)",
1015 7004000: "GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04",
1016 7004001: "GNU gdb (GDB) 7.4.1-debian",
1018 7006001: "GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7",
1020 7007001: "GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1",
1021 7007001: "GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1",
1022 7007001: "GNU gdb (GDB) Fedora 7.7.1-21.fc20",
1024 7008000: "GNU gdb (GDB; openSUSE 13.2) 7.8",
1025 7009001: "GNU gdb (GDB) Fedora 7.9.1-20.fc22",
1026 7010001: "GNU gdb (GDB) Fedora 7.10.1-31.fc23",
1028 7011000: "GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11",
1029 7011001: "GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1",
1030 7011001: "GNU gdb (Debian 7.11.1-2) 7.11.1",
1031 7011001: "GNU gdb (GDB) Fedora 7.11.1-86.fc24",
1032 7011001: "GNU gdb (GDB; openSUSE Leap 42.1) 7.11.1",
1033 7011001: "GNU gdb (GDB; openSUSE Tumbleweed) 7.11.1",
1036 7011090: "GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu1) 7.11.90.20161005-git",
1039 7012000: "GNU gdb (GDB) 7.12",
1040 7012000: "GNU gdb (GDB) 7.12.20161027-git",
1041 7012050: "GNU gdb (GDB) 7.12.50.20161027-git",