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