1#![crate_name = "compiletest"]
2
3#[cfg(test)]
4mod tests;
5
6pub mod common;
7mod debuggers;
8pub mod diagnostics;
9pub mod directives;
10pub mod errors;
11mod executor;
12mod json;
13mod output_capture;
14mod panic_hook;
15mod raise_fd_limit;
16mod read2;
17pub mod runtest;
18pub mod util;
19
20use core::panic;
21use std::collections::HashSet;
22use std::fmt::Write;
23use std::io::{self, ErrorKind};
24use std::process::{Command, Stdio};
25use std::sync::{Arc, OnceLock};
26use std::time::SystemTime;
27use std::{env, fs, vec};
28
29use build_helper::git::{get_git_modified_files, get_git_untracked_files};
30use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
31use getopts::Options;
32use rayon::iter::{ParallelBridge, ParallelIterator};
33use tracing::debug;
34use walkdir::WalkDir;
35
36use self::directives::{EarlyProps, make_test_description};
37use crate::common::{
38 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
39 expected_output_path, output_base_dir, output_relative_path,
40};
41use crate::directives::DirectivesCache;
42use crate::executor::{CollectedTest, ColorConfig};
43
44pub fn parse_config(args: Vec<String>) -> Config {
50 let mut opts = Options::new();
51 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
52 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
53 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
54 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
55 .optopt(
56 "",
57 "stage0-rustc-path",
58 "path to rustc to use for compiling run-make recipes",
59 "PATH",
60 )
61 .optopt(
62 "",
63 "query-rustc-path",
64 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
65 "PATH",
66 )
67 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
68 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
69 .reqopt("", "python", "path to python to use for doc tests", "PATH")
70 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
71 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
72 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
73 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
74 .reqopt("", "src-root", "directory containing sources", "PATH")
75 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
76 .reqopt("", "build-root", "path to root build directory", "PATH")
77 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
78 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
79 .reqopt("", "stage", "stage number under test", "N")
80 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
81 .reqopt(
82 "",
83 "mode",
84 "which sort of compile tests to run",
85 "pretty | debug-info | codegen | rustdoc \
86 | rustdoc-json | codegen-units | incremental | run-make | ui \
87 | rustdoc-js | mir-opt | assembly | crashes",
88 )
89 .reqopt(
90 "",
91 "suite",
92 "which suite of compile tests to run. used for nicer error reporting.",
93 "SUITE",
94 )
95 .optopt(
96 "",
97 "pass",
98 "force {check,build,run}-pass tests to this mode.",
99 "check | build | run",
100 )
101 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
102 .optflag("", "ignored", "run tests marked as ignored")
103 .optflag("", "has-enzyme", "run tests that require enzyme")
104 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
105 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
106 .optmulti(
107 "",
108 "skip",
109 "skip tests matching SUBSTRING. Can be passed multiple times",
110 "SUBSTRING",
111 )
112 .optflag("", "exact", "filters match exactly")
113 .optopt(
114 "",
115 "runner",
116 "supervisor program to run tests under \
117 (eg. emulator, valgrind)",
118 "PROGRAM",
119 )
120 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
121 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
122 .optflag(
123 "",
124 "rust-randomized-layout",
125 "set this when rustc/stdlib were compiled with randomized layouts",
126 )
127 .optflag("", "optimize-tests", "run tests with optimizations enabled")
128 .optflag("", "verbose", "run tests verbosely, showing all output")
129 .optflag(
130 "",
131 "bless",
132 "overwrite stderr/stdout files instead of complaining about a mismatch",
133 )
134 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
135 .optopt("", "color", "coloring: auto, always, never", "WHEN")
136 .optopt("", "target", "the target to build for", "TARGET")
137 .optopt("", "host", "the host to build for", "HOST")
138 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
139 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
140 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
141 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
142 .optflag("", "system-llvm", "is LLVM the system LLVM")
143 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
144 .optopt("", "adb-path", "path to the android debugger", "PATH")
145 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
146 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
147 .reqopt("", "cc", "path to a C compiler", "PATH")
148 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
149 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
150 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
151 .optopt("", "ar", "path to an archiver", "PATH")
152 .optopt("", "target-linker", "path to a linker for the target", "PATH")
153 .optopt("", "host-linker", "path to a linker for the host", "PATH")
154 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
155 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
156 .optopt("", "nodejs", "the name of nodejs", "PATH")
157 .optopt("", "npm", "the name of npm", "PATH")
158 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
159 .optopt(
160 "",
161 "compare-mode",
162 "mode describing what file the actual ui output will be compared to",
163 "COMPARE MODE",
164 )
165 .optflag(
166 "",
167 "rustfix-coverage",
168 "enable this to generate a Rustfix coverage file, which is saved in \
169 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
170 )
171 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
172 .optflag("", "only-modified", "only run tests that result been modified")
173 .optflag("", "nocapture", "")
175 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
176 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
177 .optflag("h", "help", "show this message")
178 .reqopt("", "channel", "current Rust channel", "CHANNEL")
179 .optflag(
180 "",
181 "git-hash",
182 "run tests which rely on commit version being compiled into the binaries",
183 )
184 .optopt("", "edition", "default Rust edition", "EDITION")
185 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
186 .reqopt(
187 "",
188 "git-merge-commit-email",
189 "email address used for finding merge commits",
190 "EMAIL",
191 )
192 .optopt(
193 "",
194 "compiletest-diff-tool",
195 "What custom diff tool to use for displaying compiletest tests.",
196 "COMMAND",
197 )
198 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
199 .optopt(
200 "",
201 "debugger",
202 "only test a specific debugger in debuginfo tests",
203 "gdb | lldb | cdb",
204 )
205 .optopt(
206 "",
207 "default-codegen-backend",
208 "the codegen backend currently used",
209 "CODEGEN BACKEND NAME",
210 )
211 .optopt(
212 "",
213 "override-codegen-backend",
214 "the codegen backend to use instead of the default one",
215 "CODEGEN BACKEND [NAME | PATH]",
216 );
217
218 let (argv0, args_) = args.split_first().unwrap();
219 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
220 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
221 println!("{}", opts.usage(&message));
222 println!();
223 panic!()
224 }
225
226 let matches = &match opts.parse(args_) {
227 Ok(m) => m,
228 Err(f) => panic!("{:?}", f),
229 };
230
231 if matches.opt_present("h") || matches.opt_present("help") {
232 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
233 println!("{}", opts.usage(&message));
234 println!();
235 panic!()
236 }
237
238 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
239 if path.is_relative() {
240 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
241 } else {
242 path
243 }
244 }
245
246 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
247 match m.opt_str(nm) {
248 Some(s) => Utf8PathBuf::from(&s),
249 None => panic!("no option (=path) found for {}", nm),
250 }
251 }
252
253 let target = opt_str2(matches.opt_str("target"));
254 let android_cross_path = opt_path(matches, "android-cross-path");
255 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
257 let (gdb, gdb_version) =
259 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
260 let lldb_version =
262 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
263 let color = match matches.opt_str("color").as_deref() {
264 Some("auto") | None => ColorConfig::AutoColor,
265 Some("always") => ColorConfig::AlwaysColor,
266 Some("never") => ColorConfig::NeverColor,
267 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
268 };
269 let llvm_version =
273 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
274 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
275 );
276
277 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
278 Some(backend) => match CodegenBackend::try_from(backend) {
279 Ok(backend) => backend,
280 Err(error) => {
281 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
282 }
283 },
284 None => CodegenBackend::Llvm,
286 };
287 let override_codegen_backend = matches.opt_str("override-codegen-backend");
288
289 let run_ignored = matches.opt_present("ignored");
290 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
291 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
292 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
293 let has_html_tidy = if mode == TestMode::Rustdoc {
294 Command::new("tidy")
295 .arg("--version")
296 .stdout(Stdio::null())
297 .status()
298 .map_or(false, |status| status.success())
299 } else {
300 false
302 };
303 let has_enzyme = matches.opt_present("has-enzyme");
304 let filters = if mode == TestMode::RunMake {
305 matches
306 .free
307 .iter()
308 .map(|f| {
309 let path = Utf8Path::new(f);
315 let mut iter = path.iter().skip(1);
316
317 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
318 path.parent().unwrap().to_string()
321 } else {
322 f.to_string()
323 }
324 })
325 .collect::<Vec<_>>()
326 } else {
327 matches.free.clone()
334 };
335 let compare_mode = matches.opt_str("compare-mode").map(|s| {
336 s.parse().unwrap_or_else(|_| {
337 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
338 panic!(
339 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
340 variants.join(", ")
341 );
342 })
343 });
344 if matches.opt_present("nocapture") {
345 panic!("`--nocapture` is deprecated; please use `--no-capture`");
346 }
347
348 let stage = match matches.opt_str("stage") {
349 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
350 None => panic!("`--stage` is required"),
351 };
352
353 let src_root = opt_path(matches, "src-root");
354 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
355 assert!(
356 src_test_suite_root.starts_with(&src_root),
357 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
358 src_root,
359 src_test_suite_root
360 );
361
362 let build_root = opt_path(matches, "build-root");
363 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
364 assert!(build_test_suite_root.starts_with(&build_root));
365
366 Config {
367 bless: matches.opt_present("bless"),
368 fail_fast: matches.opt_present("fail-fast")
369 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
370
371 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
372 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
373 rustc_path: opt_path(matches, "rustc-path"),
374 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
375 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
376 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
377 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
378 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
379 python: matches.opt_str("python").unwrap(),
380 jsondocck_path: matches.opt_str("jsondocck-path"),
381 jsondoclint_path: matches.opt_str("jsondoclint-path"),
382 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
383 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
384 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
385
386 src_root,
387 src_test_suite_root,
388
389 build_root,
390 build_test_suite_root,
391
392 sysroot_base: opt_path(matches, "sysroot-base"),
393
394 stage,
395 stage_id: matches.opt_str("stage-id").unwrap(),
396
397 mode,
398 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
399 debugger: matches.opt_str("debugger").map(|debugger| {
400 debugger
401 .parse::<Debugger>()
402 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
403 }),
404 run_ignored,
405 with_rustc_debug_assertions,
406 with_std_debug_assertions,
407 filters,
408 skip: matches.opt_strs("skip"),
409 filter_exact: matches.opt_present("exact"),
410 force_pass_mode: matches.opt_str("pass").map(|mode| {
411 mode.parse::<PassMode>()
412 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
413 }),
414 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
416 "auto" => None,
417 "always" => Some(true),
418 "never" => Some(false),
419 _ => panic!("unknown `--run` option `{}` given", mode),
420 }),
421 runner: matches.opt_str("runner"),
422 host_rustcflags: matches.opt_strs("host-rustcflags"),
423 target_rustcflags: matches.opt_strs("target-rustcflags"),
424 optimize_tests: matches.opt_present("optimize-tests"),
425 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
426 target,
427 host: opt_str2(matches.opt_str("host")),
428 cdb,
429 cdb_version,
430 gdb,
431 gdb_version,
432 lldb_version,
433 llvm_version,
434 system_llvm: matches.opt_present("system-llvm"),
435 android_cross_path,
436 adb_path: opt_str2(matches.opt_str("adb-path")),
437 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
438 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
439 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
440 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
441 lldb_python_dir: matches.opt_str("lldb-python-dir"),
442 verbose: matches.opt_present("verbose"),
443 only_modified: matches.opt_present("only-modified"),
444 color,
445 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
446 compare_mode,
447 rustfix_coverage: matches.opt_present("rustfix-coverage"),
448 has_html_tidy,
449 has_enzyme,
450 channel: matches.opt_str("channel").unwrap(),
451 git_hash: matches.opt_present("git-hash"),
452 edition: matches.opt_str("edition"),
453
454 cc: matches.opt_str("cc").unwrap(),
455 cxx: matches.opt_str("cxx").unwrap(),
456 cflags: matches.opt_str("cflags").unwrap(),
457 cxxflags: matches.opt_str("cxxflags").unwrap(),
458 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
459 target_linker: matches.opt_str("target-linker"),
460 host_linker: matches.opt_str("host-linker"),
461 llvm_components: matches.opt_str("llvm-components").unwrap(),
462 nodejs: matches.opt_str("nodejs"),
463 npm: matches.opt_str("npm"),
464
465 force_rerun: matches.opt_present("force-rerun"),
466
467 target_cfgs: OnceLock::new(),
468 builtin_cfg_names: OnceLock::new(),
469 supported_crate_types: OnceLock::new(),
470
471 nocapture: matches.opt_present("no-capture"),
472
473 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
474 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
475
476 profiler_runtime: matches.opt_present("profiler-runtime"),
477
478 diff_command: matches.opt_str("compiletest-diff-tool"),
479
480 minicore_path: opt_path(matches, "minicore-path"),
481
482 default_codegen_backend,
483 override_codegen_backend,
484 }
485}
486
487pub fn opt_str(maybestr: &Option<String>) -> &str {
488 match *maybestr {
489 None => "(none)",
490 Some(ref s) => s,
491 }
492}
493
494pub fn opt_str2(maybestr: Option<String>) -> String {
495 match maybestr {
496 None => "(none)".to_owned(),
497 Some(s) => s,
498 }
499}
500
501pub fn run_tests(config: Arc<Config>) {
503 debug!(?config, "run_tests");
504
505 panic_hook::install_panic_hook();
506
507 if config.rustfix_coverage {
511 let mut coverage_file_path = config.build_test_suite_root.clone();
512 coverage_file_path.push("rustfix_missing_coverage.txt");
513 if coverage_file_path.exists() {
514 if let Err(e) = fs::remove_file(&coverage_file_path) {
515 panic!("Could not delete {} due to {}", coverage_file_path, e)
516 }
517 }
518 }
519
520 unsafe {
524 raise_fd_limit::raise_fd_limit();
525 }
526 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
531
532 unsafe { env::set_var("TARGET", &config.target) };
536
537 let mut configs = Vec::new();
538 if let TestMode::DebugInfo = config.mode {
539 if !config.target.contains("emscripten") {
541 match config.debugger {
542 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
543 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
544 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
545 None => {
550 configs.extend(debuggers::configure_cdb(&config));
551 configs.extend(debuggers::configure_gdb(&config));
552 configs.extend(debuggers::configure_lldb(&config));
553 }
554 }
555 }
556 } else {
557 configs.push(config.clone());
558 };
559
560 let mut tests = Vec::new();
563 for c in configs {
564 tests.extend(collect_and_make_tests(c));
565 }
566
567 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
568
569 let ok = executor::run_tests(&config, tests);
573
574 if !ok {
576 let mut msg = String::from("Some tests failed in compiletest");
584 write!(msg, " suite={}", config.suite).unwrap();
585
586 if let Some(compare_mode) = config.compare_mode.as_ref() {
587 write!(msg, " compare_mode={}", compare_mode).unwrap();
588 }
589
590 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
591 write!(msg, " pass_mode={}", pass_mode).unwrap();
592 }
593
594 write!(msg, " mode={}", config.mode).unwrap();
595 write!(msg, " host={}", config.host).unwrap();
596 write!(msg, " target={}", config.target).unwrap();
597
598 println!("{msg}");
599
600 std::process::exit(1);
601 }
602}
603
604struct TestCollectorCx {
606 config: Arc<Config>,
607 cache: DirectivesCache,
608 common_inputs_stamp: Stamp,
609 modified_tests: Vec<Utf8PathBuf>,
610}
611
612struct TestCollector {
614 tests: Vec<CollectedTest>,
615 found_path_stems: HashSet<Utf8PathBuf>,
616 poisoned: bool,
617}
618
619impl TestCollector {
620 fn new() -> Self {
621 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
622 }
623
624 fn merge(&mut self, mut other: Self) {
625 self.tests.append(&mut other.tests);
626 self.found_path_stems.extend(other.found_path_stems);
627 self.poisoned |= other.poisoned;
628 }
629}
630
631pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
641 debug!("making tests from {}", config.src_test_suite_root);
642 let common_inputs_stamp = common_inputs_stamp(&config);
643 let modified_tests =
644 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
645 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
646 });
647 let cache = DirectivesCache::load(&config);
648
649 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
650 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
651 .unwrap_or_else(|reason| {
652 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
653 });
654
655 let TestCollector { tests, found_path_stems, poisoned } = collector;
656
657 if poisoned {
658 eprintln!();
659 panic!("there are errors in tests");
660 }
661
662 check_for_overlapping_test_paths(&found_path_stems);
663
664 tests
665}
666
667fn common_inputs_stamp(config: &Config) -> Stamp {
675 let src_root = &config.src_root;
676
677 let mut stamp = Stamp::from_path(&config.rustc_path);
678
679 let pretty_printer_files = [
681 "src/etc/rust_types.py",
682 "src/etc/gdb_load_rust_pretty_printers.py",
683 "src/etc/gdb_lookup.py",
684 "src/etc/gdb_providers.py",
685 "src/etc/lldb_batchmode.py",
686 "src/etc/lldb_lookup.py",
687 "src/etc/lldb_providers.py",
688 ];
689 for file in &pretty_printer_files {
690 let path = src_root.join(file);
691 stamp.add_path(&path);
692 }
693
694 stamp.add_dir(&src_root.join("src/etc/natvis"));
695
696 stamp.add_dir(&config.run_lib_path);
697
698 if let Some(ref rustdoc_path) = config.rustdoc_path {
699 stamp.add_path(&rustdoc_path);
700 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
701 }
702
703 if let Some(coverage_dump_path) = &config.coverage_dump_path {
706 stamp.add_path(coverage_dump_path)
707 }
708
709 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
710
711 stamp.add_dir(&src_root.join("src/tools/compiletest"));
713
714 stamp
715}
716
717fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
722 if !config.only_modified {
725 return Ok(vec![]);
726 }
727
728 let files = get_git_modified_files(
729 &config.git_config(),
730 Some(dir.as_std_path()),
731 &vec!["rs", "stderr", "fixed"],
732 )?;
733 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
735
736 let all_paths = [&files[..], &untracked_files[..]].concat();
737 let full_paths = {
738 let mut full_paths: Vec<Utf8PathBuf> = all_paths
739 .into_iter()
740 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
741 .filter_map(
742 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
743 )
744 .collect();
745 full_paths.dedup();
746 full_paths.sort_unstable();
747 full_paths
748 };
749 Ok(full_paths)
750}
751
752fn collect_tests_from_dir(
755 cx: &TestCollectorCx,
756 dir: &Utf8Path,
757 relative_dir_path: &Utf8Path,
758) -> io::Result<TestCollector> {
759 if dir.join("compiletest-ignore-dir").exists() {
761 return Ok(TestCollector::new());
762 }
763
764 let mut components = dir.components().rev();
765 if let Some(Utf8Component::Normal(last)) = components.next()
766 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
767 && let Some(Utf8Component::Normal(parent)) = components.next()
768 && parent == "tests"
769 && let Ok(backend) = CodegenBackend::try_from(backend)
770 && backend != cx.config.default_codegen_backend
771 {
772 warning!(
774 "Ignoring tests in `{dir}` because they don't match the configured codegen \
775 backend (`{}`)",
776 cx.config.default_codegen_backend.as_str(),
777 );
778 return Ok(TestCollector::new());
779 }
780
781 if cx.config.mode == TestMode::RunMake {
783 let mut collector = TestCollector::new();
784 if dir.join("rmake.rs").exists() {
785 let paths = TestPaths {
786 file: dir.to_path_buf(),
787 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
788 };
789 make_test(cx, &mut collector, &paths);
790 return Ok(collector);
792 }
793 }
794
795 let build_dir = output_relative_path(&cx.config, relative_dir_path);
802 fs::create_dir_all(&build_dir).unwrap();
803
804 fs::read_dir(dir.as_std_path())?
809 .par_bridge()
810 .map(|file| {
811 let mut collector = TestCollector::new();
812 let file = file?;
813 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
814 let file_name = file_path.file_name().unwrap();
815
816 if is_test(file_name)
817 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
818 {
819 debug!(%file_path, "found test file");
821
822 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
824 collector.found_path_stems.insert(rel_test_path);
825
826 let paths =
827 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
828 make_test(cx, &mut collector, &paths);
829 } else if file_path.is_dir() {
830 let relative_file_path = relative_dir_path.join(file_name);
832 if file_name != "auxiliary" {
833 debug!(%file_path, "found directory");
834 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
835 }
836 } else {
837 debug!(%file_path, "found other file/directory");
838 }
839 Ok(collector)
840 })
841 .reduce(
842 || Ok(TestCollector::new()),
843 |a, b| {
844 let mut a = a?;
845 a.merge(b?);
846 Ok(a)
847 },
848 )
849}
850
851pub fn is_test(file_name: &str) -> bool {
853 if !file_name.ends_with(".rs") {
854 return false;
855 }
856
857 let invalid_prefixes = &[".", "#", "~"];
859 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
860}
861
862fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
865 let test_path = if cx.config.mode == TestMode::RunMake {
869 testpaths.file.join("rmake.rs")
870 } else {
871 testpaths.file.clone()
872 };
873
874 let early_props = EarlyProps::from_file(&cx.config, &test_path);
876
877 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
884 vec![None]
885 } else {
886 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
887 };
888
889 collector.tests.extend(revisions.into_iter().map(|revision| {
892 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
894 let (test_name, filterable_path) =
895 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
896 let mut desc = make_test_description(
900 &cx.config,
901 &cx.cache,
902 test_name,
903 &test_path,
904 &filterable_path,
905 src_file,
906 revision,
907 &mut collector.poisoned,
908 );
909
910 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
913 desc.ignore = true;
914 desc.ignore_message = Some("up-to-date".into());
918 }
919
920 let config = Arc::clone(&cx.config);
921 let testpaths = testpaths.clone();
922 let revision = revision.map(str::to_owned);
923
924 CollectedTest { desc, config, testpaths, revision }
925 }));
926}
927
928fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
931 output_base_dir(config, testpaths, revision).join("stamp")
932}
933
934fn files_related_to_test(
939 config: &Config,
940 testpaths: &TestPaths,
941 props: &EarlyProps,
942 revision: Option<&str>,
943) -> Vec<Utf8PathBuf> {
944 let mut related = vec![];
945
946 if testpaths.file.is_dir() {
947 for entry in WalkDir::new(&testpaths.file) {
949 let path = entry.unwrap().into_path();
950 if path.is_file() {
951 related.push(Utf8PathBuf::try_from(path).unwrap());
952 }
953 }
954 } else {
955 related.push(testpaths.file.clone());
956 }
957
958 for aux in props.aux.all_aux_path_strings() {
959 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
961 related.push(path);
962 }
963
964 for extension in UI_EXTENSIONS {
966 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
967 related.push(path);
968 }
969
970 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
972
973 related
974}
975
976fn is_up_to_date(
982 cx: &TestCollectorCx,
983 testpaths: &TestPaths,
984 props: &EarlyProps,
985 revision: Option<&str>,
986) -> bool {
987 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
988 let contents = match fs::read_to_string(&stamp_file_path) {
990 Ok(f) => f,
991 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
992 Err(_) => return false,
994 };
995 let expected_hash = runtest::compute_stamp_hash(&cx.config);
996 if contents != expected_hash {
997 return false;
1000 }
1001
1002 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1005 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1006 inputs_stamp.add_path(&path);
1007 }
1008
1009 inputs_stamp < Stamp::from_path(&stamp_file_path)
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1016struct Stamp {
1017 time: SystemTime,
1018}
1019
1020impl Stamp {
1021 fn from_path(path: &Utf8Path) -> Self {
1023 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1024 stamp.add_path(path);
1025 stamp
1026 }
1027
1028 fn add_path(&mut self, path: &Utf8Path) {
1031 let modified = fs::metadata(path.as_std_path())
1032 .and_then(|metadata| metadata.modified())
1033 .unwrap_or(SystemTime::UNIX_EPOCH);
1034 self.time = self.time.max(modified);
1035 }
1036
1037 fn add_dir(&mut self, path: &Utf8Path) {
1041 let path = path.as_std_path();
1042 for entry in WalkDir::new(path) {
1043 let entry = entry.unwrap();
1044 if entry.file_type().is_file() {
1045 let modified = entry
1046 .metadata()
1047 .ok()
1048 .and_then(|metadata| metadata.modified().ok())
1049 .unwrap_or(SystemTime::UNIX_EPOCH);
1050 self.time = self.time.max(modified);
1051 }
1052 }
1053 }
1054}
1055
1056fn make_test_name_and_filterable_path(
1058 config: &Config,
1059 testpaths: &TestPaths,
1060 revision: Option<&str>,
1061) -> (String, Utf8PathBuf) {
1062 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1064 let debugger = match config.debugger {
1065 Some(d) => format!("-{}", d),
1066 None => String::new(),
1067 };
1068 let mode_suffix = match config.compare_mode {
1069 Some(ref mode) => format!(" ({})", mode.to_str()),
1070 None => String::new(),
1071 };
1072
1073 let name = format!(
1074 "[{}{}{}] {}{}",
1075 config.mode,
1076 debugger,
1077 mode_suffix,
1078 path,
1079 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1080 );
1081
1082 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1086 filterable_path = filterable_path.components().skip(1).collect();
1088
1089 (name, filterable_path)
1090}
1091
1092fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1110 let mut collisions = Vec::new();
1111 for path in found_path_stems {
1112 for ancestor in path.ancestors().skip(1) {
1113 if found_path_stems.contains(ancestor) {
1114 collisions.push((path, ancestor));
1115 }
1116 }
1117 }
1118 if !collisions.is_empty() {
1119 collisions.sort();
1120 let collisions: String = collisions
1121 .into_iter()
1122 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1123 .collect();
1124 panic!(
1125 "{collisions}\n\
1126 Tests cannot have overlapping names. Make sure they use unique prefixes."
1127 );
1128 }
1129}
1130
1131pub fn early_config_check(config: &Config) {
1132 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1133 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1134 }
1135
1136 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1137 let actioned = if config.bless { "blessed" } else { "checked" };
1138 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1139 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1140 }
1141
1142 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1144 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1145 }
1146}