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