• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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