]>
Commit | Line | Data |
---|---|---|
a7813a04 | 1 | #![crate_name = "compiletest"] |
dfeec247 XL |
2 | // The `test` crate is the only unstable feature |
3 | // allowed here, just to share similar code. | |
4 | #![feature(test)] | |
1a4d82fc | 5 | |
ff7c6d11 | 6 | extern crate test; |
a7813a04 | 7 | |
17df50a5 XL |
8 | use crate::common::{ |
9 | expected_output_path, output_base_dir, output_relative_path, PanicStrategy, UI_EXTENSIONS, | |
10 | }; | |
136023e0 | 11 | use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths}; |
dfeec247 | 12 | use crate::util::logv; |
94b46f34 | 13 | use getopts::Options; |
85aaf69f | 14 | use std::env; |
a7813a04 | 15 | use std::ffi::OsString; |
c34b1796 | 16 | use std::fs; |
0731742a | 17 | use std::io::{self, ErrorKind}; |
c34b1796 | 18 | use std::path::{Path, PathBuf}; |
fc512014 | 19 | use std::process::{Command, Stdio}; |
48663c56 | 20 | use std::time::SystemTime; |
2c00a5a8 | 21 | use test::ColorConfig; |
3dfed10e | 22 | use tracing::*; |
0731742a | 23 | use walkdir::WalkDir; |
970d7e83 | 24 | |
136023e0 | 25 | use self::header::{make_test_description, EarlyProps}; |
a7813a04 | 26 | |
416331ca XL |
27 | #[cfg(test)] |
28 | mod tests; | |
29 | ||
970d7e83 | 30 | pub mod common; |
c295e0f8 | 31 | pub mod compute_diff; |
970d7e83 | 32 | pub mod errors; |
94b46f34 XL |
33 | pub mod header; |
34 | mod json; | |
9346a6ac | 35 | mod raise_fd_limit; |
abe05a73 | 36 | mod read2; |
94b46f34 XL |
37 | pub mod runtest; |
38 | pub mod util; | |
a7813a04 XL |
39 | |
40 | fn main() { | |
29967ef6 | 41 | tracing_subscriber::fmt::init(); |
970d7e83 | 42 | |
85aaf69f | 43 | let config = parse_config(env::args().collect()); |
1a4d82fc JJ |
44 | |
45 | if config.valgrind_path.is_none() && config.force_valgrind { | |
46 | panic!("Can't find Valgrind to run Valgrind tests"); | |
47 | } | |
48 | ||
fc512014 | 49 | if !config.has_tidy && config.mode == Mode::Rustdoc { |
6a06907d | 50 | eprintln!("warning: `tidy` is not installed; diffs will not be generated"); |
fc512014 XL |
51 | } |
52 | ||
970d7e83 | 53 | log_config(&config); |
dfeec247 | 54 | run_tests(config); |
970d7e83 LB |
55 | } |
56 | ||
ff7c6d11 | 57 | pub fn parse_config(args: Vec<String>) -> Config { |
041b39d2 | 58 | let mut opts = Options::new(); |
dfeec247 XL |
59 | opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH") |
60 | .reqopt("", "run-lib-path", "path to target shared libraries", "PATH") | |
61 | .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH") | |
62 | .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH") | |
3dfed10e | 63 | .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH") |
04454e1e | 64 | .reqopt("", "python", "path to python to use for doc tests", "PATH") |
5869c6ff | 65 | .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") |
dfeec247 XL |
66 | .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM") |
67 | .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind") | |
68 | .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH") | |
69 | .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR") | |
041b39d2 | 70 | .reqopt("", "src-base", "directory to scan for test files", "PATH") |
dfeec247 XL |
71 | .reqopt("", "build-base", "directory to deposit test outputs", "PATH") |
72 | .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET") | |
ff7c6d11 XL |
73 | .reqopt( |
74 | "", | |
75 | "mode", | |
76 | "which sort of compile tests to run", | |
5869c6ff | 77 | "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \ |
fc512014 | 78 | | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly", |
ff7c6d11 | 79 | ) |
29967ef6 XL |
80 | .reqopt( |
81 | "", | |
82 | "suite", | |
83 | "which suite of compile tests to run. used for nicer error reporting.", | |
84 | "SUITE", | |
85 | ) | |
dc9dc135 XL |
86 | .optopt( |
87 | "", | |
88 | "pass", | |
89 | "force {check,build,run}-pass tests to this mode.", | |
dfeec247 | 90 | "check | build | run", |
dc9dc135 | 91 | ) |
17df50a5 | 92 | .optopt("", "run", "whether to execute run-* tests", "auto | always | never") |
041b39d2 | 93 | .optflag("", "ignored", "run tests marked as ignored") |
04454e1e | 94 | .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING") |
041b39d2 | 95 | .optflag("", "exact", "filters match exactly") |
ff7c6d11 XL |
96 | .optopt( |
97 | "", | |
98 | "runtool", | |
99 | "supervisor program to run tests under \ | |
100 | (eg. emulator, valgrind)", | |
101 | "PROGRAM", | |
102 | ) | |
17df50a5 XL |
103 | .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS") |
104 | .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS") | |
064997fb | 105 | .optflag("", "optimize-tests", "run tests with optimizations enabled") |
17df50a5 | 106 | .optopt("", "target-panic", "what panic strategy the target supports", "unwind | abort") |
041b39d2 | 107 | .optflag("", "verbose", "run tests verbosely, showing all output") |
94b46f34 XL |
108 | .optflag( |
109 | "", | |
110 | "bless", | |
111 | "overwrite stderr/stdout files instead of complaining about a mismatch", | |
112 | ) | |
dfeec247 | 113 | .optflag("", "quiet", "print one character per test instead of one line") |
041b39d2 XL |
114 | .optopt("", "color", "coloring: auto, always, never", "WHEN") |
115 | .optopt("", "logfile", "file to log test execution to", "FILE") | |
116 | .optopt("", "target", "the target to build for", "TARGET") | |
117 | .optopt("", "host", "the host to build for", "HOST") | |
dfeec247 XL |
118 | .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH") |
119 | .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH") | |
120 | .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING") | |
121 | .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING") | |
041b39d2 | 122 | .optflag("", "system-llvm", "is LLVM the system LLVM") |
dfeec247 | 123 | .optopt("", "android-cross-path", "Android NDK standalone path", "PATH") |
041b39d2 | 124 | .optopt("", "adb-path", "path to the android debugger", "PATH") |
dfeec247 XL |
125 | .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH") |
126 | .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH") | |
041b39d2 XL |
127 | .reqopt("", "cc", "path to a C compiler", "PATH") |
128 | .reqopt("", "cxx", "path to a C++ compiler", "PATH") | |
129 | .reqopt("", "cflags", "flags for the C compiler", "FLAGS") | |
5099ac24 | 130 | .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS") |
abe05a73 XL |
131 | .optopt("", "ar", "path to an archiver", "PATH") |
132 | .optopt("", "linker", "path to a linker", "PATH") | |
dfeec247 | 133 | .reqopt("", "llvm-components", "list of LLVM components built in", "LIST") |
48663c56 | 134 | .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH") |
041b39d2 | 135 | .optopt("", "nodejs", "the name of nodejs", "PATH") |
6a06907d | 136 | .optopt("", "npm", "the name of npm", "PATH") |
dfeec247 | 137 | .optopt("", "remote-test-client", "path to the remote test client", "PATH") |
83c7162d XL |
138 | .optopt( |
139 | "", | |
140 | "compare-mode", | |
141 | "mode describing what file the actual ui output will be compared to", | |
94b46f34 | 142 | "COMPARE MODE", |
83c7162d | 143 | ) |
532ac7d7 XL |
144 | .optflag( |
145 | "", | |
146 | "rustfix-coverage", | |
147 | "enable this to generate a Rustfix coverage file, which is saved in \ | |
148 | `./<build_base>/rustfix_missing_coverage.txt`", | |
149 | ) | |
94222f64 | 150 | .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged") |
17df50a5 | 151 | .optflag("h", "help", "show this message") |
3c0e092e XL |
152 | .reqopt("", "channel", "current Rust channel", "CHANNEL") |
153 | .optopt("", "edition", "default Rust edition", "EDITION"); | |
970d7e83 | 154 | |
c1a9b12d | 155 | let (argv0, args_) = args.split_first().unwrap(); |
b039eaaf | 156 | if args.len() == 1 || args[1] == "-h" || args[1] == "--help" { |
1a4d82fc | 157 | let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); |
041b39d2 | 158 | println!("{}", opts.usage(&message)); |
e1599b0c | 159 | println!(); |
1a4d82fc JJ |
160 | panic!() |
161 | } | |
162 | ||
ff7c6d11 XL |
163 | let matches = &match opts.parse(args_) { |
164 | Ok(m) => m, | |
165 | Err(f) => panic!("{:?}", f), | |
166 | }; | |
970d7e83 | 167 | |
1a4d82fc JJ |
168 | if matches.opt_present("h") || matches.opt_present("help") { |
169 | let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); | |
041b39d2 | 170 | println!("{}", opts.usage(&message)); |
e1599b0c | 171 | println!(); |
1a4d82fc JJ |
172 | panic!() |
173 | } | |
174 | ||
c34b1796 | 175 | fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf { |
85aaf69f | 176 | match m.opt_str(nm) { |
c34b1796 | 177 | Some(s) => PathBuf::from(&s), |
85aaf69f SL |
178 | None => panic!("no option (=path) found for {}", nm), |
179 | } | |
970d7e83 LB |
180 | } |
181 | ||
54a0048b | 182 | fn make_absolute(path: PathBuf) -> PathBuf { |
dfeec247 | 183 | if path.is_relative() { env::current_dir().unwrap().join(path) } else { path } |
54a0048b | 184 | } |
1a4d82fc | 185 | |
0bf4aa26 XL |
186 | let target = opt_str2(matches.opt_str("target")); |
187 | let android_cross_path = opt_path(matches, "android-cross-path"); | |
1b1a35ee | 188 | let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target); |
dfeec247 XL |
189 | let (gdb, gdb_version, gdb_native_rust) = |
190 | analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path); | |
3dfed10e XL |
191 | let (lldb_version, lldb_native_rust) = matches |
192 | .opt_str("lldb-version") | |
193 | .as_deref() | |
194 | .and_then(extract_lldb_version) | |
195 | .map(|(v, b)| (Some(v), b)) | |
196 | .unwrap_or((None, false)); | |
197 | let color = match matches.opt_str("color").as_deref() { | |
7cac9316 XL |
198 | Some("auto") | None => ColorConfig::AutoColor, |
199 | Some("always") => ColorConfig::AlwaysColor, | |
200 | Some("never") => ColorConfig::NeverColor, | |
dfeec247 | 201 | Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x), |
7cac9316 | 202 | }; |
3dfed10e XL |
203 | let llvm_version = |
204 | matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version); | |
7cac9316 | 205 | |
83c7162d XL |
206 | let src_base = opt_path(matches, "src-base"); |
207 | let run_ignored = matches.opt_present("ignored"); | |
6a06907d XL |
208 | let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode"); |
209 | let has_tidy = if mode == Mode::Rustdoc { | |
210 | Command::new("tidy") | |
211 | .arg("--version") | |
212 | .stdout(Stdio::null()) | |
213 | .status() | |
214 | .map_or(false, |status| status.success()) | |
215 | } else { | |
216 | // Avoid spawning an external command when we know tidy won't be used. | |
217 | false | |
218 | }; | |
1a4d82fc | 219 | Config { |
94b46f34 | 220 | bless: matches.opt_present("bless"), |
54a0048b SL |
221 | compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")), |
222 | run_lib_path: make_absolute(opt_path(matches, "run-lib-path")), | |
970d7e83 | 223 | rustc_path: opt_path(matches, "rustc-path"), |
3b2f2976 | 224 | rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), |
3dfed10e | 225 | rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from), |
04454e1e | 226 | python: matches.opt_str("python").unwrap(), |
5869c6ff | 227 | jsondocck_path: matches.opt_str("jsondocck-path"), |
1a4d82fc JJ |
228 | valgrind_path: matches.opt_str("valgrind-path"), |
229 | force_valgrind: matches.opt_present("force-valgrind"), | |
9fa01778 | 230 | run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), |
48663c56 XL |
231 | llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), |
232 | llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), | |
83c7162d | 233 | src_base, |
970d7e83 | 234 | build_base: opt_path(matches, "build-base"), |
1a4d82fc | 235 | stage_id: matches.opt_str("stage-id").unwrap(), |
6a06907d | 236 | mode, |
29967ef6 | 237 | suite: matches.opt_str("suite").unwrap(), |
dfeec247 | 238 | debugger: None, |
83c7162d | 239 | run_ignored, |
6a06907d | 240 | filters: matches.free.clone(), |
04454e1e | 241 | skip: matches.opt_strs("skip"), |
476ff2be | 242 | filter_exact: matches.opt_present("exact"), |
dfeec247 | 243 | force_pass_mode: matches.opt_str("pass").map(|mode| { |
dc9dc135 XL |
244 | mode.parse::<PassMode>() |
245 | .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode)) | |
dfeec247 | 246 | }), |
17df50a5 XL |
247 | run: matches.opt_str("run").and_then(|mode| match mode.as_str() { |
248 | "auto" => None, | |
249 | "always" => Some(true), | |
250 | "never" => Some(false), | |
251 | _ => panic!("unknown `--run` option `{}` given", mode), | |
252 | }), | |
c34b1796 | 253 | logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)), |
1a4d82fc | 254 | runtool: matches.opt_str("runtool"), |
17df50a5 XL |
255 | host_rustcflags: Some(matches.opt_strs("host-rustcflags").join(" ")), |
256 | target_rustcflags: Some(matches.opt_strs("target-rustcflags").join(" ")), | |
064997fb | 257 | optimize_tests: matches.opt_present("optimize-tests"), |
17df50a5 XL |
258 | target_panic: match matches.opt_str("target-panic").as_deref() { |
259 | Some("unwind") | None => PanicStrategy::Unwind, | |
260 | Some("abort") => PanicStrategy::Abort, | |
261 | _ => panic!("unknown `--target-panic` option `{}` given", mode), | |
262 | }, | |
e1599b0c | 263 | target, |
1a4d82fc | 264 | host: opt_str2(matches.opt_str("host")), |
dc9dc135 | 265 | cdb, |
1b1a35ee | 266 | cdb_version, |
3b2f2976 XL |
267 | gdb, |
268 | gdb_version, | |
269 | gdb_native_rust, | |
0bf4aa26 XL |
270 | lldb_version, |
271 | lldb_native_rust, | |
3dfed10e | 272 | llvm_version, |
041b39d2 | 273 | system_llvm: matches.opt_present("system-llvm"), |
e1599b0c | 274 | android_cross_path, |
1a4d82fc | 275 | adb_path: opt_str2(matches.opt_str("adb-path")), |
7cac9316 | 276 | adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")), |
ff7c6d11 XL |
277 | adb_device_status: opt_str2(matches.opt_str("target")).contains("android") |
278 | && "(none)" != opt_str2(matches.opt_str("adb-test-dir")) | |
279 | && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(), | |
1a4d82fc | 280 | lldb_python_dir: matches.opt_str("lldb-python-dir"), |
1a4d82fc | 281 | verbose: matches.opt_present("verbose"), |
54a0048b | 282 | quiet: matches.opt_present("quiet"), |
3b2f2976 | 283 | color, |
7cac9316 | 284 | remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), |
83c7162d | 285 | compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse), |
532ac7d7 | 286 | rustfix_coverage: matches.opt_present("rustfix-coverage"), |
fc512014 | 287 | has_tidy, |
17df50a5 | 288 | channel: matches.opt_str("channel").unwrap(), |
3c0e092e | 289 | edition: matches.opt_str("edition"), |
a7813a04 XL |
290 | |
291 | cc: matches.opt_str("cc").unwrap(), | |
292 | cxx: matches.opt_str("cxx").unwrap(), | |
293 | cflags: matches.opt_str("cflags").unwrap(), | |
5099ac24 | 294 | cxxflags: matches.opt_str("cxxflags").unwrap(), |
3dfed10e | 295 | ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")), |
abe05a73 | 296 | linker: matches.opt_str("linker"), |
a7813a04 | 297 | llvm_components: matches.opt_str("llvm-components").unwrap(), |
c30ab7b3 | 298 | nodejs: matches.opt_str("nodejs"), |
6a06907d | 299 | npm: matches.opt_str("npm"), |
94222f64 XL |
300 | |
301 | force_rerun: matches.opt_present("force-rerun"), | |
970d7e83 LB |
302 | } |
303 | } | |
304 | ||
1a4d82fc | 305 | pub fn log_config(config: &Config) { |
970d7e83 | 306 | let c = config; |
041b39d2 | 307 | logv(c, "configuration:".to_string()); |
dfeec247 | 308 | logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path)); |
1a4d82fc JJ |
309 | logv(c, format!("run_lib_path: {:?}", config.run_lib_path)); |
310 | logv(c, format!("rustc_path: {:?}", config.rustc_path.display())); | |
3b2f2976 | 311 | logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path)); |
3dfed10e | 312 | logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path)); |
1a4d82fc JJ |
313 | logv(c, format!("src_base: {:?}", config.src_base.display())); |
314 | logv(c, format!("build_base: {:?}", config.build_base.display())); | |
315 | logv(c, format!("stage_id: {}", config.stage_id)); | |
316 | logv(c, format!("mode: {}", config.mode)); | |
317 | logv(c, format!("run_ignored: {}", config.run_ignored)); | |
6a06907d | 318 | logv(c, format!("filters: {:?}", config.filters)); |
04454e1e | 319 | logv(c, format!("skip: {:?}", config.skip)); |
476ff2be | 320 | logv(c, format!("filter_exact: {}", config.filter_exact)); |
ff7c6d11 XL |
321 | logv( |
322 | c, | |
dfeec247 | 323 | format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),), |
ff7c6d11 | 324 | ); |
dfeec247 XL |
325 | logv(c, format!("runtool: {}", opt_str(&config.runtool))); |
326 | logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags))); | |
327 | logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags))); | |
1a4d82fc JJ |
328 | logv(c, format!("target: {}", config.target)); |
329 | logv(c, format!("host: {}", config.host)); | |
dfeec247 | 330 | logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display())); |
1a4d82fc JJ |
331 | logv(c, format!("adb_path: {:?}", config.adb_path)); |
332 | logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir)); | |
dfeec247 | 333 | logv(c, format!("adb_device_status: {}", config.adb_device_status)); |
abe05a73 XL |
334 | logv(c, format!("ar: {}", config.ar)); |
335 | logv(c, format!("linker: {:?}", config.linker)); | |
1a4d82fc | 336 | logv(c, format!("verbose: {}", config.verbose)); |
54a0048b | 337 | logv(c, format!("quiet: {}", config.quiet)); |
041b39d2 | 338 | logv(c, "\n".to_string()); |
970d7e83 LB |
339 | } |
340 | ||
041b39d2 | 341 | pub fn opt_str(maybestr: &Option<String>) -> &str { |
970d7e83 | 342 | match *maybestr { |
1a4d82fc | 343 | None => "(none)", |
85aaf69f | 344 | Some(ref s) => s, |
970d7e83 LB |
345 | } |
346 | } | |
347 | ||
1a4d82fc JJ |
348 | pub fn opt_str2(maybestr: Option<String>) -> String { |
349 | match maybestr { | |
e9174d1e | 350 | None => "(none)".to_owned(), |
1a4d82fc JJ |
351 | Some(s) => s, |
352 | } | |
970d7e83 LB |
353 | } |
354 | ||
dfeec247 | 355 | pub fn run_tests(config: Config) { |
532ac7d7 XL |
356 | // If we want to collect rustfix coverage information, |
357 | // we first make sure that the coverage file does not exist. | |
358 | // It will be created later on. | |
359 | if config.rustfix_coverage { | |
360 | let mut coverage_file_path = config.build_base.clone(); | |
361 | coverage_file_path.push("rustfix_missing_coverage.txt"); | |
362 | if coverage_file_path.exists() { | |
363 | if let Err(e) = fs::remove_file(&coverage_file_path) { | |
364 | panic!("Could not delete {} due to {}", coverage_file_path.display(), e) | |
365 | } | |
366 | } | |
367 | } | |
368 | ||
1a4d82fc JJ |
369 | // sadly osx needs some file descriptor limits raised for running tests in |
370 | // parallel (especially when we have lots and lots of child processes). | |
371 | // For context, see #8904 | |
ff7c6d11 XL |
372 | unsafe { |
373 | raise_fd_limit::raise_fd_limit(); | |
374 | } | |
85aaf69f SL |
375 | // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows |
376 | // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary | |
377 | env::set_var("__COMPAT_LAYER", "RunAsInvoker"); | |
32a655c1 SL |
378 | |
379 | // Let tests know which target they're running as | |
380 | env::set_var("TARGET", &config.target); | |
381 | ||
dfeec247 XL |
382 | let opts = test_opts(&config); |
383 | ||
384 | let mut configs = Vec::new(); | |
385 | if let Mode::DebugInfo = config.mode { | |
386 | // Debugging emscripten code doesn't make sense today | |
387 | if !config.target.contains("emscripten") { | |
388 | configs.extend(configure_cdb(&config)); | |
389 | configs.extend(configure_gdb(&config)); | |
390 | configs.extend(configure_lldb(&config)); | |
391 | } | |
392 | } else { | |
29967ef6 | 393 | configs.push(config.clone()); |
dfeec247 XL |
394 | }; |
395 | ||
396 | let mut tests = Vec::new(); | |
397 | for c in &configs { | |
398 | make_tests(c, &mut tests); | |
399 | } | |
400 | ||
0731742a | 401 | let res = test::run_tests_console(&opts, tests); |
1a4d82fc JJ |
402 | match res { |
403 | Ok(true) => {} | |
29967ef6 XL |
404 | Ok(false) => { |
405 | // We want to report that the tests failed, but we also want to give | |
406 | // some indication of just what tests we were running. Especially on | |
407 | // CI, where there can be cross-compiled tests for a lot of | |
408 | // architectures, without this critical information it can be quite | |
409 | // easy to miss which tests failed, and as such fail to reproduce | |
410 | // the failure locally. | |
411 | ||
412 | eprintln!( | |
413 | "Some tests failed in compiletest suite={}{} mode={} host={} target={}", | |
414 | config.suite, | |
415 | config.compare_mode.map(|c| format!(" compare_mode={:?}", c)).unwrap_or_default(), | |
416 | config.mode, | |
417 | config.host, | |
418 | config.target | |
419 | ); | |
420 | ||
421 | std::process::exit(1); | |
422 | } | |
1a4d82fc | 423 | Err(e) => { |
ba9703b0 | 424 | // We don't know if tests passed or not, but if there was an error |
fc512014 | 425 | // during testing we don't want to just succeed (we may not have |
ba9703b0 | 426 | // tested something), so fail. |
29967ef6 XL |
427 | // |
428 | // This should realistically "never" happen, so don't try to make | |
429 | // this a pretty error message. | |
ba9703b0 | 430 | panic!("I/O failure during tests: {:?}", e); |
1a4d82fc JJ |
431 | } |
432 | } | |
970d7e83 LB |
433 | } |
434 | ||
dfeec247 | 435 | fn configure_cdb(config: &Config) -> Option<Config> { |
3dfed10e | 436 | config.cdb.as_ref()?; |
dfeec247 XL |
437 | |
438 | Some(Config { debugger: Some(Debugger::Cdb), ..config.clone() }) | |
439 | } | |
440 | ||
441 | fn configure_gdb(config: &Config) -> Option<Config> { | |
3dfed10e | 442 | config.gdb_version?; |
dfeec247 XL |
443 | |
444 | if util::matches_env(&config.target, "msvc") { | |
445 | return None; | |
446 | } | |
447 | ||
448 | if config.remote_test_client.is_some() && !config.target.contains("android") { | |
449 | println!( | |
450 | "WARNING: debuginfo tests are not available when \ | |
451 | testing with remote" | |
452 | ); | |
453 | return None; | |
454 | } | |
455 | ||
456 | if config.target.contains("android") { | |
457 | println!( | |
458 | "{} debug-info test uses tcp 5039 port.\ | |
459 | please reserve it", | |
460 | config.target | |
461 | ); | |
462 | ||
463 | // android debug-info test uses remote debugger so, we test 1 thread | |
464 | // at once as they're all sharing the same TCP port to communicate | |
465 | // over. | |
466 | // | |
467 | // we should figure out how to lift this restriction! (run them all | |
468 | // on different ports allocated dynamically). | |
469 | env::set_var("RUST_TEST_THREADS", "1"); | |
470 | } | |
471 | ||
472 | Some(Config { debugger: Some(Debugger::Gdb), ..config.clone() }) | |
473 | } | |
474 | ||
475 | fn configure_lldb(config: &Config) -> Option<Config> { | |
3dfed10e | 476 | config.lldb_python_dir.as_ref()?; |
dfeec247 | 477 | |
3dfed10e XL |
478 | if let Some(350) = config.lldb_version { |
479 | println!( | |
480 | "WARNING: The used version of LLDB (350) has a \ | |
481 | known issue that breaks debuginfo tests. See \ | |
482 | issue #32520 for more information. Skipping all \ | |
483 | LLDB-based tests!", | |
484 | ); | |
485 | return None; | |
dfeec247 XL |
486 | } |
487 | ||
dfeec247 XL |
488 | Some(Config { debugger: Some(Debugger::Lldb), ..config.clone() }) |
489 | } | |
490 | ||
1a4d82fc | 491 | pub fn test_opts(config: &Config) -> test::TestOpts { |
970d7e83 | 492 | test::TestOpts { |
48663c56 | 493 | exclude_should_panic: false, |
6a06907d | 494 | filters: config.filters.clone(), |
476ff2be | 495 | filter_exact: config.filter_exact, |
dfeec247 XL |
496 | run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No }, |
497 | format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty }, | |
1a4d82fc | 498 | logfile: config.logfile.clone(), |
970d7e83 | 499 | run_tests: true, |
d9579d0f | 500 | bench_benchmarks: true, |
54a0048b SL |
501 | nocapture: match env::var("RUST_TEST_NOCAPTURE") { |
502 | Ok(val) => &val != "0", | |
ff7c6d11 | 503 | Err(_) => false, |
54a0048b | 504 | }, |
7cac9316 | 505 | color: config.color, |
c295e0f8 XL |
506 | shuffle: false, |
507 | shuffle_seed: None, | |
5bcae85e | 508 | test_threads: None, |
04454e1e | 509 | skip: config.skip.clone(), |
476ff2be | 510 | list: false, |
7cac9316 | 511 | options: test::Options::new(), |
60c5eb7d | 512 | time_options: None, |
dfeec247 | 513 | force_run_in_process: false, |
970d7e83 LB |
514 | } |
515 | } | |
516 | ||
dfeec247 | 517 | pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) { |
ff7c6d11 | 518 | debug!("making tests from {:?}", config.src_base.display()); |
60c5eb7d | 519 | let inputs = common_inputs_stamp(config); |
ba9703b0 | 520 | collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests) |
3dfed10e | 521 | .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); |
7453a54e SL |
522 | } |
523 | ||
60c5eb7d XL |
524 | /// Returns a stamp constructed from input files common to all test cases. |
525 | fn common_inputs_stamp(config: &Config) -> Stamp { | |
dfeec247 | 526 | let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root"); |
60c5eb7d XL |
527 | |
528 | let mut stamp = Stamp::from_path(&config.rustc_path); | |
529 | ||
530 | // Relevant pretty printer files | |
531 | let pretty_printer_files = [ | |
f035d41b | 532 | "src/etc/rust_types.py", |
60c5eb7d | 533 | "src/etc/gdb_load_rust_pretty_printers.py", |
f035d41b XL |
534 | "src/etc/gdb_lookup.py", |
535 | "src/etc/gdb_providers.py", | |
60c5eb7d | 536 | "src/etc/lldb_batchmode.py", |
f035d41b XL |
537 | "src/etc/lldb_lookup.py", |
538 | "src/etc/lldb_providers.py", | |
60c5eb7d XL |
539 | ]; |
540 | for file in &pretty_printer_files { | |
541 | let path = rust_src_dir.join(file); | |
542 | stamp.add_path(&path); | |
543 | } | |
544 | ||
545 | stamp.add_dir(&config.run_lib_path); | |
546 | ||
547 | if let Some(ref rustdoc_path) = config.rustdoc_path { | |
548 | stamp.add_path(&rustdoc_path); | |
549 | stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py")); | |
550 | } | |
551 | ||
552 | // Compiletest itself. | |
553 | stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/")); | |
554 | ||
555 | stamp | |
556 | } | |
557 | ||
ff7c6d11 XL |
558 | fn collect_tests_from_dir( |
559 | config: &Config, | |
ff7c6d11 XL |
560 | dir: &Path, |
561 | relative_dir_path: &Path, | |
60c5eb7d | 562 | inputs: &Stamp, |
ff7c6d11 XL |
563 | tests: &mut Vec<test::TestDescAndFn>, |
564 | ) -> io::Result<()> { | |
0731742a XL |
565 | // Ignore directories that contain a file named `compiletest-ignore-dir`. |
566 | if dir.join("compiletest-ignore-dir").exists() { | |
567 | return Ok(()); | |
568 | } | |
569 | ||
570 | if config.mode == Mode::RunMake && dir.join("Makefile").exists() { | |
571 | let paths = TestPaths { | |
572 | file: dir.to_path_buf(), | |
573 | relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), | |
574 | }; | |
60c5eb7d | 575 | tests.extend(make_test(config, &paths, inputs)); |
0731742a | 576 | return Ok(()); |
7453a54e SL |
577 | } |
578 | ||
a7813a04 XL |
579 | // If we find a test foo/bar.rs, we have to build the |
580 | // output directory `$build/foo` so we can write | |
581 | // `$build/foo/bar` into it. We do this *now* in this | |
582 | // sequential loop because otherwise, if we do it in the | |
583 | // tests themselves, they race for the privilege of | |
584 | // creating the directories and sometimes fail randomly. | |
94b46f34 | 585 | let build_dir = output_relative_path(config, relative_dir_path); |
a7813a04 XL |
586 | fs::create_dir_all(&build_dir).unwrap(); |
587 | ||
588 | // Add each `.rs` file as a test, and recurse further on any | |
589 | // subdirectories we find, except for `aux` directories. | |
0731742a | 590 | for file in fs::read_dir(dir)? { |
54a0048b | 591 | let file = file?; |
7453a54e | 592 | let file_path = file.path(); |
a7813a04 XL |
593 | let file_name = file.file_name(); |
594 | if is_test(&file_name) { | |
595 | debug!("found test file: {:?}", file_path.display()); | |
dfeec247 XL |
596 | let paths = |
597 | TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; | |
04454e1e | 598 | |
60c5eb7d | 599 | tests.extend(make_test(config, &paths, inputs)) |
7453a54e SL |
600 | } else if file_path.is_dir() { |
601 | let relative_file_path = relative_dir_path.join(file.file_name()); | |
94b46f34 | 602 | if &file_name != "auxiliary" { |
a7813a04 | 603 | debug!("found directory: {:?}", file_path.display()); |
ba9703b0 | 604 | collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?; |
a7813a04 XL |
605 | } |
606 | } else { | |
607 | debug!("found other file/directory: {:?}", file_path.display()); | |
970d7e83 LB |
608 | } |
609 | } | |
7453a54e | 610 | Ok(()) |
970d7e83 LB |
611 | } |
612 | ||
532ac7d7 | 613 | /// Returns true if `file_name` looks like a proper test file name. |
a7813a04 XL |
614 | pub fn is_test(file_name: &OsString) -> bool { |
615 | let file_name = file_name.to_str().unwrap(); | |
970d7e83 | 616 | |
a7813a04 XL |
617 | if !file_name.ends_with(".rs") { |
618 | return false; | |
970d7e83 LB |
619 | } |
620 | ||
a7813a04 XL |
621 | // `.`, `#`, and `~` are common temp-file prefixes. |
622 | let invalid_prefixes = &[".", "#", "~"]; | |
623 | !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) | |
970d7e83 LB |
624 | } |
625 | ||
60c5eb7d | 626 | fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> { |
136023e0 XL |
627 | let test_path = if config.mode == Mode::RunMake { |
628 | // Parse directives in the Makefile | |
629 | testpaths.file.join("Makefile") | |
83c7162d | 630 | } else { |
136023e0 | 631 | PathBuf::from(&testpaths.file) |
54a0048b | 632 | }; |
136023e0 | 633 | let early_props = EarlyProps::from_file(config, &test_path); |
54a0048b | 634 | |
94b46f34 XL |
635 | // Incremental tests are special, they inherently cannot be run in parallel. |
636 | // `runtest::run` will be responsible for iterating over revisions. | |
637 | let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental { | |
638 | vec![None] | |
639 | } else { | |
3dfed10e | 640 | early_props.revisions.iter().map(Some).collect() |
94b46f34 XL |
641 | }; |
642 | revisions | |
643 | .into_iter() | |
644 | .map(|revision| { | |
136023e0 XL |
645 | let src_file = |
646 | std::fs::File::open(&test_path).expect("open test file to parse ignores"); | |
647 | let cfg = revision.map(|v| &**v); | |
648 | let test_name = crate::make_test_name(config, testpaths, revision); | |
649 | let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg); | |
650 | // Ignore tests that already run and are up to date with respect to inputs. | |
94222f64 XL |
651 | if !config.force_rerun { |
652 | desc.ignore |= is_up_to_date( | |
653 | config, | |
654 | testpaths, | |
655 | &early_props, | |
656 | revision.map(|s| s.as_str()), | |
657 | inputs, | |
658 | ); | |
659 | } | |
136023e0 | 660 | test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) } |
94b46f34 XL |
661 | }) |
662 | .collect() | |
970d7e83 LB |
663 | } |
664 | ||
94b46f34 XL |
665 | fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { |
666 | output_base_dir(config, testpaths, revision).join("stamp") | |
8bb4bdeb XL |
667 | } |
668 | ||
04454e1e FG |
669 | fn files_related_to_test( |
670 | config: &Config, | |
671 | testpaths: &TestPaths, | |
672 | props: &EarlyProps, | |
673 | revision: Option<&str>, | |
674 | ) -> Vec<PathBuf> { | |
675 | let mut related = vec![]; | |
676 | ||
677 | if testpaths.file.is_dir() { | |
678 | // run-make tests use their individual directory | |
679 | for entry in WalkDir::new(&testpaths.file) { | |
680 | let path = entry.unwrap().into_path(); | |
681 | if path.is_file() { | |
682 | related.push(path); | |
683 | } | |
684 | } | |
685 | } else { | |
686 | related.push(testpaths.file.clone()); | |
687 | } | |
688 | ||
689 | for aux in &props.aux { | |
690 | let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux); | |
691 | related.push(path); | |
692 | } | |
693 | ||
694 | // UI test files. | |
695 | for extension in UI_EXTENSIONS { | |
696 | let path = expected_output_path(testpaths, revision, &config.compare_mode, extension); | |
697 | related.push(path); | |
698 | } | |
699 | ||
700 | related | |
701 | } | |
702 | ||
60c5eb7d | 703 | fn is_up_to_date( |
94b46f34 XL |
704 | config: &Config, |
705 | testpaths: &TestPaths, | |
706 | props: &EarlyProps, | |
707 | revision: Option<&str>, | |
60c5eb7d | 708 | inputs: &Stamp, |
94b46f34 XL |
709 | ) -> bool { |
710 | let stamp_name = stamp(config, testpaths, revision); | |
711 | // Check hash. | |
0731742a | 712 | let contents = match fs::read_to_string(&stamp_name) { |
94b46f34 | 713 | Ok(f) => f, |
0731742a | 714 | Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"), |
60c5eb7d | 715 | Err(_) => return false, |
94b46f34 | 716 | }; |
94b46f34 XL |
717 | let expected_hash = runtest::compute_stamp_hash(config); |
718 | if contents != expected_hash { | |
60c5eb7d | 719 | return false; |
94b46f34 XL |
720 | } |
721 | ||
722 | // Check timestamps. | |
60c5eb7d | 723 | let mut inputs = inputs.clone(); |
04454e1e | 724 | for path in files_related_to_test(config, testpaths, props, revision) { |
60c5eb7d | 725 | inputs.add_path(&path); |
abe05a73 | 726 | } |
ff7c6d11 | 727 | |
60c5eb7d | 728 | inputs < Stamp::from_path(&stamp_name) |
9fa01778 XL |
729 | } |
730 | ||
60c5eb7d | 731 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] |
9fa01778 | 732 | struct Stamp { |
48663c56 | 733 | time: SystemTime, |
9fa01778 XL |
734 | } |
735 | ||
736 | impl Stamp { | |
60c5eb7d XL |
737 | fn from_path(path: &Path) -> Self { |
738 | let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH }; | |
739 | stamp.add_path(path); | |
740 | stamp | |
741 | } | |
742 | ||
743 | fn add_path(&mut self, path: &Path) { | |
744 | let modified = fs::metadata(path) | |
48663c56 XL |
745 | .and_then(|metadata| metadata.modified()) |
746 | .unwrap_or(SystemTime::UNIX_EPOCH); | |
60c5eb7d | 747 | self.time = self.time.max(modified); |
9fa01778 XL |
748 | } |
749 | ||
60c5eb7d XL |
750 | fn add_dir(&mut self, path: &Path) { |
751 | for entry in WalkDir::new(path) { | |
752 | let entry = entry.unwrap(); | |
753 | if entry.file_type().is_file() { | |
dfeec247 XL |
754 | let modified = entry |
755 | .metadata() | |
756 | .ok() | |
60c5eb7d XL |
757 | .and_then(|metadata| metadata.modified().ok()) |
758 | .unwrap_or(SystemTime::UNIX_EPOCH); | |
759 | self.time = self.time.max(modified); | |
760 | } | |
761 | } | |
48663c56 | 762 | } |
8bb4bdeb XL |
763 | } |
764 | ||
94b46f34 XL |
765 | fn make_test_name( |
766 | config: &Config, | |
767 | testpaths: &TestPaths, | |
768 | revision: Option<&String>, | |
769 | ) -> test::TestName { | |
04454e1e FG |
770 | // Print the name of the file, relative to the repository root. |
771 | // `src_base` looks like `/path/to/rust/src/test/ui` | |
772 | let root_directory = config.src_base.parent().unwrap().parent().unwrap().parent().unwrap(); | |
773 | let path = testpaths.file.strip_prefix(root_directory).unwrap(); | |
dfeec247 XL |
774 | let debugger = match config.debugger { |
775 | Some(d) => format!("-{}", d), | |
776 | None => String::new(), | |
777 | }; | |
83c7162d XL |
778 | let mode_suffix = match config.compare_mode { |
779 | Some(ref mode) => format!(" ({})", mode.to_str()), | |
a1dfa0c6 | 780 | None => String::new(), |
83c7162d | 781 | }; |
dfeec247 | 782 | |
94b46f34 | 783 | test::DynTestName(format!( |
dfeec247 | 784 | "[{}{}{}] {}{}", |
94b46f34 | 785 | config.mode, |
dfeec247 | 786 | debugger, |
94b46f34 XL |
787 | mode_suffix, |
788 | path.display(), | |
789 | revision.map_or("".to_string(), |rev| format!("#{}", rev)) | |
790 | )) | |
970d7e83 LB |
791 | } |
792 | ||
94b46f34 XL |
793 | fn make_test_closure( |
794 | config: &Config, | |
795 | testpaths: &TestPaths, | |
796 | revision: Option<&String>, | |
797 | ) -> test::TestFn { | |
dfeec247 | 798 | let config = config.clone(); |
7453a54e | 799 | let testpaths = testpaths.clone(); |
94b46f34 | 800 | let revision = revision.cloned(); |
3dfed10e | 801 | test::DynTestFn(Box::new(move || runtest::run(config, &testpaths, revision.as_deref()))) |
1a4d82fc JJ |
802 | } |
803 | ||
9fa01778 | 804 | /// Returns `true` if the given target is an Android target for the |
0bf4aa26 | 805 | /// purposes of GDB testing. |
3dfed10e XL |
806 | fn is_android_gdb_target(target: &str) -> bool { |
807 | matches!( | |
808 | &target[..], | |
809 | "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" | |
810 | ) | |
0bf4aa26 XL |
811 | } |
812 | ||
dc9dc135 | 813 | /// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing. |
3dfed10e | 814 | fn is_pc_windows_msvc_target(target: &str) -> bool { |
dc9dc135 XL |
815 | target.ends_with("-pc-windows-msvc") |
816 | } | |
817 | ||
3dfed10e | 818 | fn find_cdb(target: &str) -> Option<OsString> { |
dc9dc135 XL |
819 | if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { |
820 | return None; | |
821 | } | |
822 | ||
3dfed10e | 823 | let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?; |
dfeec247 | 824 | let cdb_arch = if cfg!(target_arch = "x86") { |
dc9dc135 | 825 | "x86" |
dfeec247 | 826 | } else if cfg!(target_arch = "x86_64") { |
dc9dc135 | 827 | "x64" |
dfeec247 | 828 | } else if cfg!(target_arch = "aarch64") { |
dc9dc135 | 829 | "arm64" |
dfeec247 | 830 | } else if cfg!(target_arch = "arm") { |
dc9dc135 XL |
831 | "arm" |
832 | } else { | |
833 | return None; // No compatible CDB.exe in the Windows 10 SDK | |
834 | }; | |
835 | ||
836 | let mut path = PathBuf::new(); | |
837 | path.push(pf86); | |
838 | path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? | |
839 | path.push(cdb_arch); | |
840 | path.push(r"cdb.exe"); | |
841 | ||
842 | if !path.exists() { | |
843 | return None; | |
844 | } | |
845 | ||
846 | Some(path.into_os_string()) | |
847 | } | |
848 | ||
849 | /// Returns Path to CDB | |
1b1a35ee XL |
850 | fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) { |
851 | let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target)); | |
852 | ||
853 | let mut version = None; | |
854 | if let Some(cdb) = cdb.as_ref() { | |
855 | if let Ok(output) = Command::new(cdb).arg("/version").output() { | |
856 | if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { | |
857 | version = extract_cdb_version(&first_line); | |
858 | } | |
859 | } | |
860 | } | |
861 | ||
862 | (cdb, version) | |
863 | } | |
864 | ||
865 | fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> { | |
866 | // Example full_version_line: "cdb version 10.0.18362.1" | |
867 | let version = full_version_line.rsplit(' ').next()?; | |
868 | let mut components = version.split('.'); | |
869 | let major: u16 = components.next().unwrap().parse().unwrap(); | |
870 | let minor: u16 = components.next().unwrap().parse().unwrap(); | |
871 | let patch: u16 = components.next().unwrap_or("0").parse().unwrap(); | |
872 | let build: u16 = components.next().unwrap_or("0").parse().unwrap(); | |
873 | Some([major, minor, patch, build]) | |
dc9dc135 XL |
874 | } |
875 | ||
c30ab7b3 | 876 | /// Returns (Path to GDB, GDB Version, GDB has Rust Support) |
dfeec247 XL |
877 | fn analyze_gdb( |
878 | gdb: Option<String>, | |
3dfed10e | 879 | target: &str, |
dfeec247 XL |
880 | android_cross_path: &PathBuf, |
881 | ) -> (Option<String>, Option<u32>, bool) { | |
c30ab7b3 SL |
882 | #[cfg(not(windows))] |
883 | const GDB_FALLBACK: &str = "gdb"; | |
884 | #[cfg(windows)] | |
885 | const GDB_FALLBACK: &str = "gdb.exe"; | |
85aaf69f | 886 | |
c30ab7b3 SL |
887 | const MIN_GDB_WITH_RUST: u32 = 7011010; |
888 | ||
0bf4aa26 XL |
889 | let fallback_gdb = || { |
890 | if is_android_gdb_target(target) { | |
891 | let mut gdb_path = match android_cross_path.to_str() { | |
892 | Some(x) => x.to_owned(), | |
893 | None => panic!("cannot find android cross path"), | |
894 | }; | |
895 | gdb_path.push_str("/bin/gdb"); | |
896 | gdb_path | |
897 | } else { | |
898 | GDB_FALLBACK.to_owned() | |
899 | } | |
900 | }; | |
901 | ||
c30ab7b3 | 902 | let gdb = match gdb { |
0bf4aa26 XL |
903 | None => fallback_gdb(), |
904 | Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb | |
905 | Some(ref s) => s.to_owned(), | |
c30ab7b3 SL |
906 | }; |
907 | ||
0531ce1d | 908 | let mut version_line = None; |
0bf4aa26 | 909 | if let Ok(output) = Command::new(&gdb).arg("--version").output() { |
0531ce1d XL |
910 | if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { |
911 | version_line = Some(first_line.to_string()); | |
912 | } | |
913 | } | |
c30ab7b3 SL |
914 | |
915 | let version = match version_line { | |
916 | Some(line) => extract_gdb_version(&line), | |
917 | None => return (None, None, false), | |
918 | }; | |
919 | ||
920 | let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST); | |
921 | ||
0bf4aa26 | 922 | (Some(gdb), version, gdb_native_rust) |
c30ab7b3 SL |
923 | } |
924 | ||
925 | fn extract_gdb_version(full_version_line: &str) -> Option<u32> { | |
926 | let full_version_line = full_version_line.trim(); | |
927 | ||
928 | // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both | |
929 | // of the ? sections being optional | |
930 | ||
f9f354fc XL |
931 | // We will parse up to 3 digits for each component, ignoring the date |
932 | ||
933 | // We skip text in parentheses. This avoids accidentally parsing | |
934 | // the openSUSE version, which looks like: | |
935 | // GNU gdb (GDB; openSUSE Leap 15.0) 8.1 | |
936 | // This particular form is documented in the GNU coding standards: | |
937 | // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion | |
c30ab7b3 | 938 | |
cdc7bbd5 XL |
939 | let unbracketed_part = full_version_line.split('[').next().unwrap(); |
940 | let mut splits = unbracketed_part.trim_end().rsplit(' '); | |
3dfed10e XL |
941 | let version_string = splits.next().unwrap(); |
942 | ||
943 | let mut splits = version_string.split('.'); | |
944 | let major = splits.next().unwrap(); | |
945 | let minor = splits.next().unwrap(); | |
946 | let patch = splits.next(); | |
947 | ||
948 | let major: u32 = major.parse().unwrap(); | |
949 | let (minor, patch): (u32, u32) = match minor.find(not_a_digit) { | |
950 | None => { | |
951 | let minor = minor.parse().unwrap(); | |
952 | let patch: u32 = match patch { | |
953 | Some(patch) => match patch.find(not_a_digit) { | |
954 | None => patch.parse().unwrap(), | |
955 | Some(idx) if idx > 3 => 0, | |
956 | Some(idx) => patch[..idx].parse().unwrap(), | |
957 | }, | |
958 | None => 0, | |
959 | }; | |
960 | (minor, patch) | |
c30ab7b3 | 961 | } |
3dfed10e XL |
962 | // There is no patch version after minor-date (e.g. "4-2012"). |
963 | Some(idx) => { | |
964 | let minor = minor[..idx].parse().unwrap(); | |
965 | (minor, 0) | |
c30ab7b3 | 966 | } |
3dfed10e | 967 | }; |
c30ab7b3 | 968 | |
3dfed10e | 969 | Some(((major * 1000) + minor) * 1000 + patch) |
1a4d82fc JJ |
970 | } |
971 | ||
0bf4aa26 | 972 | /// Returns (LLDB version, LLDB is rust-enabled) |
3dfed10e | 973 | fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> { |
1a4d82fc JJ |
974 | // Extract the major LLDB version from the given version string. |
975 | // LLDB version strings are different for Apple and non-Apple platforms. | |
0bf4aa26 | 976 | // The Apple variant looks like this: |
1a4d82fc JJ |
977 | // |
978 | // LLDB-179.5 (older versions) | |
979 | // lldb-300.2.51 (new versions) | |
980 | // | |
981 | // We are only interested in the major version number, so this function | |
3dfed10e | 982 | // will return `Some(179)` and `Some(300)` respectively. |
0bf4aa26 XL |
983 | // |
984 | // Upstream versions look like: | |
985 | // lldb version 6.0.1 | |
986 | // | |
987 | // There doesn't seem to be a way to correlate the Apple version | |
988 | // with the upstream version, and since the tests were originally | |
989 | // written against Apple versions, we make a fake Apple version by | |
990 | // multiplying the first number by 100. This is a hack, but | |
991 | // normally fine because the only non-Apple version we test is | |
992 | // rust-enabled. | |
1a4d82fc | 993 | |
3dfed10e | 994 | let full_version_line = full_version_line.trim(); |
0bf4aa26 | 995 | |
3dfed10e XL |
996 | if let Some(apple_ver) = |
997 | full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-")) | |
998 | { | |
999 | if let Some(idx) = apple_ver.find(not_a_digit) { | |
1000 | let version: u32 = apple_ver[..idx].parse().unwrap(); | |
1001 | return Some((version, full_version_line.contains("rust-enabled"))); | |
1002 | } | |
1003 | } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") { | |
1004 | if let Some(idx) = lldb_ver.find(not_a_digit) { | |
cdc7bbd5 | 1005 | let version: u32 = lldb_ver[..idx].parse().ok()?; |
3dfed10e | 1006 | return Some((version * 100, full_version_line.contains("rust-enabled"))); |
e9174d1e | 1007 | } |
1a4d82fc | 1008 | } |
3dfed10e XL |
1009 | None |
1010 | } | |
1011 | ||
1012 | fn not_a_digit(c: char) -> bool { | |
1013 | !c.is_digit(10) | |
970d7e83 | 1014 | } |