]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/main.rs
rustc: Load the `rustc_trans` crate at runtime
[rust.git] / src / tools / compiletest / src / main.rs
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.
4 //
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.
10
11 #![crate_name = "compiletest"]
12 #![feature(test)]
13 #![feature(slice_rotate)]
14 #![deny(warnings)]
15
16 extern crate diff;
17 extern crate env_logger;
18 extern crate filetime;
19 extern crate getopts;
20 #[cfg(unix)]
21 extern crate libc;
22 #[macro_use]
23 extern crate log;
24 extern crate rustc_serialize;
25 extern crate regex;
26 extern crate test;
27
28 use std::env;
29 use std::ffi::OsString;
30 use std::fs;
31 use std::io;
32 use std::path::{Path, PathBuf};
33 use std::process::Command;
34 use filetime::FileTime;
35 use getopts::Options;
36 use common::{Config, TestPaths};
37 use common::{DebugInfoGdb, DebugInfoLldb, Mode, Pretty};
38 use common::{expected_output_path, UI_EXTENSIONS};
39 use test::ColorConfig;
40 use util::logv;
41
42 use self::header::EarlyProps;
43
44 pub mod util;
45 mod json;
46 pub mod header;
47 pub mod runtest;
48 pub mod common;
49 pub mod errors;
50 mod raise_fd_limit;
51 mod read2;
52
53 fn main() {
54     env_logger::init().unwrap();
55
56     let config = parse_config(env::args().collect());
57
58     if config.valgrind_path.is_none() && config.force_valgrind {
59         panic!("Can't find Valgrind to run Valgrind tests");
60     }
61
62     log_config(&config);
63     run_tests(&config);
64 }
65
66 pub fn parse_config(args: Vec<String>) -> Config {
67     let mut opts = Options::new();
68     opts.reqopt(
69         "",
70         "compile-lib-path",
71         "path to host shared libraries",
72         "PATH",
73     ).reqopt(
74             "",
75             "run-lib-path",
76             "path to target shared libraries",
77             "PATH",
78         )
79         .reqopt(
80             "",
81             "rustc-path",
82             "path to rustc to use for compiling",
83             "PATH",
84         )
85         .optopt(
86             "",
87             "rustdoc-path",
88             "path to rustdoc to use for compiling",
89             "PATH",
90         )
91         .reqopt(
92             "",
93             "lldb-python",
94             "path to python to use for doc tests",
95             "PATH",
96         )
97         .reqopt(
98             "",
99             "docck-python",
100             "path to python to use for doc tests",
101             "PATH",
102         )
103         .optopt(
104             "",
105             "valgrind-path",
106             "path to Valgrind executable for Valgrind tests",
107             "PROGRAM",
108         )
109         .optflag(
110             "",
111             "force-valgrind",
112             "fail if Valgrind tests cannot be run under Valgrind",
113         )
114         .optopt(
115             "",
116             "llvm-filecheck",
117             "path to LLVM's FileCheck binary",
118             "DIR",
119         )
120         .reqopt("", "src-base", "directory to scan for test files", "PATH")
121         .reqopt(
122             "",
123             "build-base",
124             "directory to deposit test outputs",
125             "PATH",
126         )
127         .reqopt(
128             "",
129             "stage-id",
130             "the target-stage identifier",
131             "stageN-TARGET",
132         )
133         .reqopt(
134             "",
135             "mode",
136             "which sort of compile tests to run",
137             "(compile-fail|parse-fail|run-fail|run-pass|\
138              run-pass-valgrind|pretty|debug-info|incremental|mir-opt)",
139         )
140         .optflag("", "ignored", "run tests marked as ignored")
141         .optflag("", "exact", "filters match exactly")
142         .optopt(
143             "",
144             "runtool",
145             "supervisor program to run tests under \
146              (eg. emulator, valgrind)",
147             "PROGRAM",
148         )
149         .optopt(
150             "",
151             "host-rustcflags",
152             "flags to pass to rustc for host",
153             "FLAGS",
154         )
155         .optopt(
156             "",
157             "target-rustcflags",
158             "flags to pass to rustc for target",
159             "FLAGS",
160         )
161         .optflag("", "verbose", "run tests verbosely, showing all output")
162         .optflag(
163             "",
164             "quiet",
165             "print one character per test instead of one line",
166         )
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")
171         .optopt(
172             "",
173             "gdb",
174             "path to GDB to use for GDB debuginfo tests",
175             "PATH",
176         )
177         .optopt(
178             "",
179             "lldb-version",
180             "the version of LLDB used",
181             "VERSION STRING",
182         )
183         .optopt(
184             "",
185             "llvm-version",
186             "the version of LLVM used",
187             "VERSION STRING",
188         )
189         .optflag("", "system-llvm", "is LLVM the system LLVM")
190         .optopt(
191             "",
192             "android-cross-path",
193             "Android NDK standalone path",
194             "PATH",
195         )
196         .optopt("", "adb-path", "path to the android debugger", "PATH")
197         .optopt(
198             "",
199             "adb-test-dir",
200             "path to tests for the android debugger",
201             "PATH",
202         )
203         .optopt(
204             "",
205             "lldb-python-dir",
206             "directory containing LLDB's python module",
207             "PATH",
208         )
209         .reqopt("", "cc", "path to a C compiler", "PATH")
210         .reqopt("", "cxx", "path to a C++ compiler", "PATH")
211         .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
212         .optopt("", "ar", "path to an archiver", "PATH")
213         .optopt("", "linker", "path to a linker", "PATH")
214         .reqopt(
215             "",
216             "llvm-components",
217             "list of LLVM components built in",
218             "LIST",
219         )
220         .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
221         .optopt("", "nodejs", "the name of nodejs", "PATH")
222         .optopt(
223             "",
224             "remote-test-client",
225             "path to the remote test client",
226             "PATH",
227         )
228         .optflag("h", "help", "show this message");
229
230     let (argv0, args_) = args.split_first().unwrap();
231     if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
232         let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
233         println!("{}", opts.usage(&message));
234         println!("");
235         panic!()
236     }
237
238     let matches = &match opts.parse(args_) {
239         Ok(m) => m,
240         Err(f) => panic!("{:?}", f),
241     };
242
243     if matches.opt_present("h") || matches.opt_present("help") {
244         let message = format!("Usage: {} [OPTIONS]  [TESTNAME...]", argv0);
245         println!("{}", opts.usage(&message));
246         println!("");
247         panic!()
248     }
249
250     fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
251         match m.opt_str(nm) {
252             Some(s) => PathBuf::from(&s),
253             None => panic!("no option (=path) found for {}", nm),
254         }
255     }
256
257     fn make_absolute(path: PathBuf) -> PathBuf {
258         if path.is_relative() {
259             env::current_dir().unwrap().join(path)
260         } else {
261             path
262         }
263     }
264
265     let (gdb, gdb_version, gdb_native_rust) = analyze_gdb(matches.opt_str("gdb"));
266
267     let color = match matches.opt_str("color").as_ref().map(|x| &**x) {
268         Some("auto") | None => ColorConfig::AutoColor,
269         Some("always") => ColorConfig::AlwaysColor,
270         Some("never") => ColorConfig::NeverColor,
271         Some(x) => panic!(
272             "argument for --color must be auto, always, or never, but found `{}`",
273             x
274         ),
275     };
276
277     Config {
278         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
279         run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
280         rustc_path: opt_path(matches, "rustc-path"),
281         rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
282         lldb_python: matches.opt_str("lldb-python").unwrap(),
283         docck_python: matches.opt_str("docck-python").unwrap(),
284         valgrind_path: matches.opt_str("valgrind-path"),
285         force_valgrind: matches.opt_present("force-valgrind"),
286         llvm_filecheck: matches.opt_str("llvm-filecheck").map(|s| PathBuf::from(&s)),
287         src_base: opt_path(matches, "src-base"),
288         build_base: opt_path(matches, "build-base"),
289         stage_id: matches.opt_str("stage-id").unwrap(),
290         mode: matches
291             .opt_str("mode")
292             .unwrap()
293             .parse()
294             .expect("invalid mode"),
295         run_ignored: matches.opt_present("ignored"),
296         filter: matches.free.first().cloned(),
297         filter_exact: matches.opt_present("exact"),
298         logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
299         runtool: matches.opt_str("runtool"),
300         host_rustcflags: matches.opt_str("host-rustcflags"),
301         target_rustcflags: matches.opt_str("target-rustcflags"),
302         target: opt_str2(matches.opt_str("target")),
303         host: opt_str2(matches.opt_str("host")),
304         gdb,
305         gdb_version,
306         gdb_native_rust,
307         lldb_version: extract_lldb_version(matches.opt_str("lldb-version")),
308         llvm_version: matches.opt_str("llvm-version"),
309         system_llvm: matches.opt_present("system-llvm"),
310         android_cross_path: opt_path(matches, "android-cross-path"),
311         adb_path: opt_str2(matches.opt_str("adb-path")),
312         adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
313         adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
314             && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
315             && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
316         lldb_python_dir: matches.opt_str("lldb-python-dir"),
317         verbose: matches.opt_present("verbose"),
318         quiet: matches.opt_present("quiet"),
319         color,
320         remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
321
322         cc: matches.opt_str("cc").unwrap(),
323         cxx: matches.opt_str("cxx").unwrap(),
324         cflags: matches.opt_str("cflags").unwrap(),
325         ar: matches.opt_str("ar").unwrap_or("ar".into()),
326         linker: matches.opt_str("linker"),
327         llvm_components: matches.opt_str("llvm-components").unwrap(),
328         llvm_cxxflags: matches.opt_str("llvm-cxxflags").unwrap(),
329         nodejs: matches.opt_str("nodejs"),
330     }
331 }
332
333 pub fn log_config(config: &Config) {
334     let c = config;
335     logv(c, "configuration:".to_string());
336     logv(
337         c,
338         format!("compile_lib_path: {:?}", config.compile_lib_path),
339     );
340     logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
341     logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
342     logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
343     logv(c, format!("src_base: {:?}", config.src_base.display()));
344     logv(c, format!("build_base: {:?}", config.build_base.display()));
345     logv(c, format!("stage_id: {}", config.stage_id));
346     logv(c, format!("mode: {}", config.mode));
347     logv(c, format!("run_ignored: {}", config.run_ignored));
348     logv(
349         c,
350         format!(
351             "filter: {}",
352             opt_str(&config.filter.as_ref().map(|re| re.to_owned()))
353         ),
354     );
355     logv(c, format!("filter_exact: {}", config.filter_exact));
356     logv(c, format!("runtool: {}", opt_str(&config.runtool)));
357     logv(
358         c,
359         format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)),
360     );
361     logv(
362         c,
363         format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)),
364     );
365     logv(c, format!("target: {}", config.target));
366     logv(c, format!("host: {}", config.host));
367     logv(
368         c,
369         format!(
370             "android-cross-path: {:?}",
371             config.android_cross_path.display()
372         ),
373     );
374     logv(c, format!("adb_path: {:?}", config.adb_path));
375     logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
376     logv(
377         c,
378         format!("adb_device_status: {}", config.adb_device_status),
379     );
380     logv(c, format!("ar: {}", config.ar));
381     logv(c, format!("linker: {:?}", config.linker));
382     logv(c, format!("verbose: {}", config.verbose));
383     logv(c, format!("quiet: {}", config.quiet));
384     logv(c, "\n".to_string());
385 }
386
387 pub fn opt_str(maybestr: &Option<String>) -> &str {
388     match *maybestr {
389         None => "(none)",
390         Some(ref s) => s,
391     }
392 }
393
394 pub fn opt_str2(maybestr: Option<String>) -> String {
395     match maybestr {
396         None => "(none)".to_owned(),
397         Some(s) => s,
398     }
399 }
400
401 pub fn run_tests(config: &Config) {
402     if config.target.contains("android") {
403         if let DebugInfoGdb = config.mode {
404             println!(
405                 "{} debug-info test uses tcp 5039 port.\
406                  please reserve it",
407                 config.target
408             );
409
410             // android debug-info test uses remote debugger so, we test 1 thread
411             // at once as they're all sharing the same TCP port to communicate
412             // over.
413             //
414             // we should figure out how to lift this restriction! (run them all
415             // on different ports allocated dynamically).
416             env::set_var("RUST_TEST_THREADS", "1");
417         }
418     }
419
420     match config.mode {
421         DebugInfoLldb => {
422             if let Some(lldb_version) = config.lldb_version.as_ref() {
423                 if is_blacklisted_lldb_version(&lldb_version[..]) {
424                     println!(
425                         "WARNING: The used version of LLDB ({}) has a \
426                          known issue that breaks debuginfo tests. See \
427                          issue #32520 for more information. Skipping all \
428                          LLDB-based tests!",
429                         lldb_version
430                     );
431                     return;
432                 }
433             }
434
435             // Some older versions of LLDB seem to have problems with multiple
436             // instances running in parallel, so only run one test thread at a
437             // time.
438             env::set_var("RUST_TEST_THREADS", "1");
439         }
440
441         DebugInfoGdb => {
442             if config.remote_test_client.is_some() && !config.target.contains("android") {
443                 println!(
444                     "WARNING: debuginfo tests are not available when \
445                      testing with remote"
446                 );
447                 return;
448             }
449         }
450         _ => { /* proceed */ }
451     }
452
453     // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests.
454     if let Mode::CodegenUnits = config.mode {
455         let _ = fs::remove_dir_all("tmp/partitioning-tests");
456     }
457
458     let opts = test_opts(config);
459     let tests = make_tests(config);
460     // sadly osx needs some file descriptor limits raised for running tests in
461     // parallel (especially when we have lots and lots of child processes).
462     // For context, see #8904
463     unsafe {
464         raise_fd_limit::raise_fd_limit();
465     }
466     // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
467     // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
468     env::set_var("__COMPAT_LAYER", "RunAsInvoker");
469
470     // Let tests know which target they're running as
471     env::set_var("TARGET", &config.target);
472
473     let res = test::run_tests_console(&opts, tests.into_iter().collect());
474     match res {
475         Ok(true) => {}
476         Ok(false) => panic!("Some tests failed"),
477         Err(e) => {
478             println!("I/O failure during tests: {:?}", e);
479         }
480     }
481 }
482
483 pub fn test_opts(config: &Config) -> test::TestOpts {
484     test::TestOpts {
485         filter: config.filter.clone(),
486         filter_exact: config.filter_exact,
487         run_ignored: config.run_ignored,
488         quiet: config.quiet,
489         logfile: config.logfile.clone(),
490         run_tests: true,
491         bench_benchmarks: true,
492         nocapture: match env::var("RUST_TEST_NOCAPTURE") {
493             Ok(val) => &val != "0",
494             Err(_) => false,
495         },
496         color: config.color,
497         test_threads: None,
498         skip: vec![],
499         list: false,
500         options: test::Options::new(),
501     }
502 }
503
504 pub fn make_tests(config: &Config) -> Vec<test::TestDescAndFn> {
505     debug!("making tests from {:?}", config.src_base.display());
506     let mut tests = Vec::new();
507     collect_tests_from_dir(
508         config,
509         &config.src_base,
510         &config.src_base,
511         &PathBuf::new(),
512         &mut tests,
513     ).unwrap();
514     tests
515 }
516
517 fn collect_tests_from_dir(
518     config: &Config,
519     base: &Path,
520     dir: &Path,
521     relative_dir_path: &Path,
522     tests: &mut Vec<test::TestDescAndFn>,
523 ) -> io::Result<()> {
524     // Ignore directories that contain a file
525     // `compiletest-ignore-dir`.
526     for file in fs::read_dir(dir)? {
527         let file = file?;
528         let name = file.file_name();
529         if name == *"compiletest-ignore-dir" {
530             return Ok(());
531         }
532         if name == *"Makefile" && config.mode == Mode::RunMake {
533             let paths = TestPaths {
534                 file: dir.to_path_buf(),
535                 base: base.to_path_buf(),
536                 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
537             };
538             tests.push(make_test(config, &paths));
539             return Ok(());
540         }
541     }
542
543     // If we find a test foo/bar.rs, we have to build the
544     // output directory `$build/foo` so we can write
545     // `$build/foo/bar` into it. We do this *now* in this
546     // sequential loop because otherwise, if we do it in the
547     // tests themselves, they race for the privilege of
548     // creating the directories and sometimes fail randomly.
549     let build_dir = config.build_base.join(&relative_dir_path);
550     fs::create_dir_all(&build_dir).unwrap();
551
552     // Add each `.rs` file as a test, and recurse further on any
553     // subdirectories we find, except for `aux` directories.
554     let dirs = fs::read_dir(dir)?;
555     for file in dirs {
556         let file = file?;
557         let file_path = file.path();
558         let file_name = file.file_name();
559         if is_test(&file_name) {
560             debug!("found test file: {:?}", file_path.display());
561             let paths = TestPaths {
562                 file: file_path,
563                 base: base.to_path_buf(),
564                 relative_dir: relative_dir_path.to_path_buf(),
565             };
566             tests.push(make_test(config, &paths))
567         } else if file_path.is_dir() {
568             let relative_file_path = relative_dir_path.join(file.file_name());
569             if &file_name == "auxiliary" {
570                 // `aux` directories contain other crates used for
571                 // cross-crate tests. Don't search them for tests, but
572                 // do create a directory in the build dir for them,
573                 // since we will dump intermediate output in there
574                 // sometimes.
575                 let build_dir = config.build_base.join(&relative_file_path);
576                 fs::create_dir_all(&build_dir).unwrap();
577             } else {
578                 debug!("found directory: {:?}", file_path.display());
579                 collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?;
580             }
581         } else {
582             debug!("found other file/directory: {:?}", file_path.display());
583         }
584     }
585     Ok(())
586 }
587
588 pub fn is_test(file_name: &OsString) -> bool {
589     let file_name = file_name.to_str().unwrap();
590
591     if !file_name.ends_with(".rs") {
592         return false;
593     }
594
595     // `.`, `#`, and `~` are common temp-file prefixes.
596     let invalid_prefixes = &[".", "#", "~"];
597     !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
598 }
599
600 pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn {
601     let early_props = EarlyProps::from_file(config, &testpaths.file);
602
603     // The `should-fail` annotation doesn't apply to pretty tests,
604     // since we run the pretty printer across all tests by default.
605     // If desired, we could add a `should-fail-pretty` annotation.
606     let should_panic = match config.mode {
607         Pretty => test::ShouldPanic::No,
608         _ => if early_props.should_fail {
609             test::ShouldPanic::Yes
610         } else {
611             test::ShouldPanic::No
612         },
613     };
614
615     // Debugging emscripten code doesn't make sense today
616     let ignore = early_props.ignore || !up_to_date(config, testpaths, &early_props)
617         || (config.mode == DebugInfoGdb || config.mode == DebugInfoLldb)
618             && config.target.contains("emscripten");
619
620     test::TestDescAndFn {
621         desc: test::TestDesc {
622             name: make_test_name(config, testpaths),
623             ignore,
624             should_panic,
625             allow_fail: false,
626         },
627         testfn: make_test_closure(config, testpaths),
628     }
629 }
630
631 fn stamp(config: &Config, testpaths: &TestPaths) -> PathBuf {
632     let stamp_name = format!(
633         "{}-{}.stamp",
634         testpaths.file.file_name().unwrap().to_str().unwrap(),
635         config.stage_id
636     );
637     config
638         .build_base
639         .canonicalize()
640         .unwrap_or_else(|_| config.build_base.clone())
641         .join(&testpaths.relative_dir)
642         .join(stamp_name)
643 }
644
645 fn up_to_date(config: &Config, testpaths: &TestPaths, props: &EarlyProps) -> bool {
646     let rust_src_dir = config
647         .find_rust_src_root()
648         .expect("Could not find Rust source root");
649     let stamp = mtime(&stamp(config, testpaths));
650     let mut inputs = vec![mtime(&testpaths.file), mtime(&config.rustc_path)];
651     for aux in props.aux.iter() {
652         inputs.push(mtime(&testpaths
653             .file
654             .parent()
655             .unwrap()
656             .join("auxiliary")
657             .join(aux)));
658     }
659     // Relevant pretty printer files
660     let pretty_printer_files = [
661         "src/etc/debugger_pretty_printers_common.py",
662         "src/etc/gdb_load_rust_pretty_printers.py",
663         "src/etc/gdb_rust_pretty_printing.py",
664         "src/etc/lldb_batchmode.py",
665         "src/etc/lldb_rust_formatters.py",
666     ];
667     for pretty_printer_file in &pretty_printer_files {
668         inputs.push(mtime(&rust_src_dir.join(pretty_printer_file)));
669     }
670     let mut entries = config.run_lib_path.read_dir().unwrap()
671         .collect::<Vec<_>>();
672     while let Some(entry) = entries.pop() {
673         let entry = entry.unwrap();
674         let path = entry.path();
675         if entry.metadata().unwrap().is_file() {
676             inputs.push(mtime(&path));
677         } else {
678             entries.extend(path.read_dir().unwrap());
679         }
680     }
681     if let Some(ref rustdoc_path) = config.rustdoc_path {
682         inputs.push(mtime(&rustdoc_path));
683         inputs.push(mtime(&rust_src_dir.join("src/etc/htmldocck.py")));
684     }
685
686     // UI test files.
687     for extension in UI_EXTENSIONS {
688         for revision in &props.revisions {
689             let path = &expected_output_path(testpaths, Some(revision), extension);
690             inputs.push(mtime(path));
691         }
692
693         if props.revisions.is_empty() {
694             let path = &expected_output_path(testpaths, None, extension);
695             inputs.push(mtime(path));
696         }
697     }
698
699     inputs.iter().any(|input| *input > stamp)
700 }
701
702 fn mtime(path: &Path) -> FileTime {
703     fs::metadata(path)
704         .map(|f| FileTime::from_last_modification_time(&f))
705         .unwrap_or_else(|_| FileTime::zero())
706 }
707
708 pub fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName {
709     // Convert a complete path to something like
710     //
711     //    run-pass/foo/bar/baz.rs
712     let path = PathBuf::from(config.src_base.file_name().unwrap())
713         .join(&testpaths.relative_dir)
714         .join(&testpaths.file.file_name().unwrap());
715     test::DynTestName(format!("[{}] {}", config.mode, path.display()))
716 }
717
718 pub fn make_test_closure(config: &Config, testpaths: &TestPaths) -> test::TestFn {
719     let config = config.clone();
720     let testpaths = testpaths.clone();
721     test::DynTestFn(Box::new(move || runtest::run(config, &testpaths)))
722 }
723
724 /// Returns (Path to GDB, GDB Version, GDB has Rust Support)
725 fn analyze_gdb(gdb: Option<String>) -> (Option<String>, Option<u32>, bool) {
726     #[cfg(not(windows))]
727     const GDB_FALLBACK: &str = "gdb";
728     #[cfg(windows)]
729     const GDB_FALLBACK: &str = "gdb.exe";
730
731     const MIN_GDB_WITH_RUST: u32 = 7011010;
732
733     let gdb = match gdb {
734         None => GDB_FALLBACK,
735         Some(ref s) if s.is_empty() => GDB_FALLBACK, // may be empty if configure found no gdb
736         Some(ref s) => s,
737     };
738
739     let version_line = Command::new(gdb)
740         .arg("--version")
741         .output()
742         .map(|output| {
743             String::from_utf8_lossy(&output.stdout)
744                 .lines()
745                 .next()
746                 .unwrap()
747                 .to_string()
748         })
749         .ok();
750
751     let version = match version_line {
752         Some(line) => extract_gdb_version(&line),
753         None => return (None, None, false),
754     };
755
756     let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST);
757
758     (Some(gdb.to_owned()), version, gdb_native_rust)
759 }
760
761 fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
762     let full_version_line = full_version_line.trim();
763
764     // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
765     // of the ? sections being optional
766
767     // We will parse up to 3 digits for minor and patch, ignoring the date
768     // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
769
770     // don't start parsing in the middle of a number
771     let mut prev_was_digit = false;
772     for (pos, c) in full_version_line.char_indices() {
773         if prev_was_digit || !c.is_digit(10) {
774             prev_was_digit = c.is_digit(10);
775             continue;
776         }
777
778         prev_was_digit = true;
779
780         let line = &full_version_line[pos..];
781
782         let next_split = match line.find(|c: char| !c.is_digit(10)) {
783             Some(idx) => idx,
784             None => continue, // no minor version
785         };
786
787         if line.as_bytes()[next_split] != b'.' {
788             continue; // no minor version
789         }
790
791         let major = &line[..next_split];
792         let line = &line[next_split + 1..];
793
794         let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
795             Some(idx) => if line.as_bytes()[idx] == b'.' {
796                 let patch = &line[idx + 1..];
797
798                 let patch_len = patch
799                     .find(|c: char| !c.is_digit(10))
800                     .unwrap_or_else(|| patch.len());
801                 let patch = &patch[..patch_len];
802                 let patch = if patch_len > 3 || patch_len == 0 {
803                     None
804                 } else {
805                     Some(patch)
806                 };
807
808                 (&line[..idx], patch)
809             } else {
810                 (&line[..idx], None)
811             },
812             None => (line, None),
813         };
814
815         if major.len() != 1 || minor.is_empty() {
816             continue;
817         }
818
819         let major: u32 = major.parse().unwrap();
820         let minor: u32 = minor.parse().unwrap();
821         let patch: u32 = patch.unwrap_or("0").parse().unwrap();
822
823         return Some(((major * 1000) + minor) * 1000 + patch);
824     }
825
826     None
827 }
828
829 fn extract_lldb_version(full_version_line: Option<String>) -> Option<String> {
830     // Extract the major LLDB version from the given version string.
831     // LLDB version strings are different for Apple and non-Apple platforms.
832     // At the moment, this function only supports the Apple variant, which looks
833     // like this:
834     //
835     // LLDB-179.5 (older versions)
836     // lldb-300.2.51 (new versions)
837     //
838     // We are only interested in the major version number, so this function
839     // will return `Some("179")` and `Some("300")` respectively.
840
841     if let Some(ref full_version_line) = full_version_line {
842         if !full_version_line.trim().is_empty() {
843             let full_version_line = full_version_line.trim();
844
845             for (pos, l) in full_version_line.char_indices() {
846                 if l != 'l' && l != 'L' {
847                     continue;
848                 }
849                 if pos + 5 >= full_version_line.len() {
850                     continue;
851                 }
852                 let l = full_version_line[pos + 1..].chars().next().unwrap();
853                 if l != 'l' && l != 'L' {
854                     continue;
855                 }
856                 let d = full_version_line[pos + 2..].chars().next().unwrap();
857                 if d != 'd' && d != 'D' {
858                     continue;
859                 }
860                 let b = full_version_line[pos + 3..].chars().next().unwrap();
861                 if b != 'b' && b != 'B' {
862                     continue;
863                 }
864                 let dash = full_version_line[pos + 4..].chars().next().unwrap();
865                 if dash != '-' {
866                     continue;
867                 }
868
869                 let vers = full_version_line[pos + 5..]
870                     .chars()
871                     .take_while(|c| c.is_digit(10))
872                     .collect::<String>();
873                 if !vers.is_empty() {
874                     return Some(vers);
875                 }
876             }
877         }
878     }
879     None
880 }
881
882 fn is_blacklisted_lldb_version(version: &str) -> bool {
883     version == "350"
884 }
885
886 #[test]
887 fn test_extract_gdb_version() {
888     macro_rules! test { ($($expectation:tt: $input:tt,)*) => {{$(
889         assert_eq!(extract_gdb_version($input), Some($expectation));
890     )*}}}
891
892     test! {
893         7000001: "GNU gdb (GDB) CentOS (7.0.1-45.el5.centos)",
894
895         7002000: "GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)",
896
897         7004000: "GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04",
898         7004001: "GNU gdb (GDB) 7.4.1-debian",
899
900         7006001: "GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7",
901
902         7007001: "GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1",
903         7007001: "GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1",
904         7007001: "GNU gdb (GDB) Fedora 7.7.1-21.fc20",
905
906         7008000: "GNU gdb (GDB; openSUSE 13.2) 7.8",
907         7009001: "GNU gdb (GDB) Fedora 7.9.1-20.fc22",
908         7010001: "GNU gdb (GDB) Fedora 7.10.1-31.fc23",
909
910         7011000: "GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11",
911         7011001: "GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1",
912         7011001: "GNU gdb (Debian 7.11.1-2) 7.11.1",
913         7011001: "GNU gdb (GDB) Fedora 7.11.1-86.fc24",
914         7011001: "GNU gdb (GDB; openSUSE Leap 42.1) 7.11.1",
915         7011001: "GNU gdb (GDB; openSUSE Tumbleweed) 7.11.1",
916
917         7011090: "7.11.90",
918         7011090: "GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu1) 7.11.90.20161005-git",
919
920         7012000: "7.12",
921         7012000: "GNU gdb (GDB) 7.12",
922         7012000: "GNU gdb (GDB) 7.12.20161027-git",
923         7012050: "GNU gdb (GDB) 7.12.50.20161027-git",
924     }
925 }