1#![crate_name = "compiletest"]
2
3#[cfg(test)]
4mod tests;
5
6pub mod cli;
7mod common;
8mod debuggers;
9mod diagnostics;
10mod directives;
11mod edition;
12mod errors;
13mod executor;
14mod json;
15mod output_capture;
16mod panic_hook;
17mod raise_fd_limit;
18mod read2;
19mod runtest;
20pub mod rustdoc_gui_test;
21mod util;
22
23use core::panic;
24use std::collections::HashSet;
25use std::fmt::Write;
26use std::io::{self, ErrorKind};
27use std::sync::{Arc, OnceLock};
28use std::time::SystemTime;
29use std::{env, fs, vec};
30
31use build_helper::git::{get_git_modified_files, get_git_untracked_files};
32use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
33use getopts::Options;
34use rayon::iter::{ParallelBridge, ParallelIterator};
35use tracing::debug;
36use walkdir::WalkDir;
37
38use self::directives::{EarlyProps, make_test_description};
39use crate::common::{
40 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
41 expected_output_path, output_base_dir, output_relative_path,
42};
43use crate::directives::{AuxProps, DirectivesCache, FileDirectives};
44use crate::edition::parse_edition;
45use crate::executor::CollectedTest;
46
47fn parse_config(args: Vec<String>) -> Config {
53 let mut opts = Options::new();
54 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
55 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
56 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
57 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
58 .optopt(
59 "",
60 "stage0-rustc-path",
61 "path to rustc to use for compiling run-make recipes",
62 "PATH",
63 )
64 .optopt(
65 "",
66 "query-rustc-path",
67 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
68 "PATH",
69 )
70 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
71 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
72 .reqopt("", "python", "path to python to use for doc tests", "PATH")
73 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
74 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
75 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
76 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
77 .reqopt("", "src-root", "directory containing sources", "PATH")
78 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
79 .reqopt("", "build-root", "path to root build directory", "PATH")
80 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
81 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
82 .reqopt("", "stage", "stage number under test", "N")
83 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
84 .reqopt(
85 "",
86 "mode",
87 "which sort of compile tests to run",
88 "pretty | debug-info | codegen | rustdoc-html \
89 | rustdoc-json | codegen-units | incremental | run-make | ui \
90 | rustdoc-js | mir-opt | assembly | crashes",
91 )
92 .reqopt(
93 "",
94 "suite",
95 "which suite of compile tests to run. used for nicer error reporting.",
96 "SUITE",
97 )
98 .optopt(
99 "",
100 "pass",
101 "force {check,build,run}-pass tests to this mode.",
102 "check | build | run",
103 )
104 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
105 .optflag("", "ignored", "run tests marked as ignored")
106 .optflag("", "has-enzyme", "run tests that require enzyme")
107 .optflag("", "has-offload", "run tests that require offload")
108 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
109 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
110 .optflag("", "with-std-remap-debuginfo", "whether std was built with remapping")
111 .optmulti(
112 "",
113 "skip",
114 "skip tests matching SUBSTRING. Can be passed multiple times",
115 "SUBSTRING",
116 )
117 .optflag("", "exact", "filters match exactly")
118 .optopt(
119 "",
120 "runner",
121 "supervisor program to run tests under \
122 (eg. emulator, valgrind)",
123 "PROGRAM",
124 )
125 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
126 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
127 .optflag(
128 "",
129 "rust-randomized-layout",
130 "set this when rustc/stdlib were compiled with randomized layouts",
131 )
132 .optflag("", "optimize-tests", "run tests with optimizations enabled")
133 .optflag("", "verbose", "run tests verbosely, showing all output")
134 .optflag(
135 "",
136 "bless",
137 "overwrite stderr/stdout files instead of complaining about a mismatch",
138 )
139 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
140 .optopt("", "target", "the target to build for", "TARGET")
141 .optopt("", "host", "the host to build for", "HOST")
142 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
143 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
144 .optopt("", "lldb", "path to LLDB to use for LLDB debuginfo tests", "PATH")
145 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
146 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
147 .optflag("", "system-llvm", "is LLVM the system LLVM")
148 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
149 .optopt("", "adb-path", "path to the android debugger", "PATH")
150 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
151 .reqopt("", "cc", "path to a C compiler", "PATH")
152 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
153 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
154 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
155 .optopt("", "ar", "path to an archiver", "PATH")
156 .optopt("", "target-linker", "path to a linker for the target", "PATH")
157 .optopt("", "host-linker", "path to a linker for the host", "PATH")
158 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
159 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
160 .optopt("", "nodejs", "the name of nodejs", "PATH")
161 .optopt("", "npm", "the name of npm", "PATH")
162 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
163 .optopt(
164 "",
165 "compare-mode",
166 "mode describing what file the actual ui output will be compared to",
167 "COMPARE MODE",
168 )
169 .optflag(
170 "",
171 "rustfix-coverage",
172 "enable this to generate a Rustfix coverage file, which is saved in \
173 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
174 )
175 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
176 .optflag("", "only-modified", "only run tests that result been modified")
177 .optflag("", "nocapture", "")
179 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
180 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
181 .optflag("h", "help", "show this message")
182 .reqopt("", "channel", "current Rust channel", "CHANNEL")
183 .optflag(
184 "",
185 "git-hash",
186 "run tests which rely on commit version being compiled into the binaries",
187 )
188 .optopt("", "edition", "default Rust edition", "EDITION")
189 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
190 .reqopt(
191 "",
192 "git-merge-commit-email",
193 "email address used for finding merge commits",
194 "EMAIL",
195 )
196 .optopt(
197 "",
198 "compiletest-diff-tool",
199 "What custom diff tool to use for displaying compiletest tests.",
200 "COMMAND",
201 )
202 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
203 .optopt(
204 "",
205 "debugger",
206 "only test a specific debugger in debuginfo tests",
207 "gdb | lldb | cdb",
208 )
209 .optopt(
210 "",
211 "default-codegen-backend",
212 "the codegen backend currently used",
213 "CODEGEN BACKEND NAME",
214 )
215 .optopt(
216 "",
217 "override-codegen-backend",
218 "the codegen backend to use instead of the default one",
219 "CODEGEN BACKEND [NAME | PATH]",
220 )
221 .optflag("", "bypass-ignore-backends", "ignore `//@ ignore-backends` directives")
222 .reqopt("", "jobs", "number of parallel jobs bootstrap was configured with", "JOBS")
223 .optopt(
224 "",
225 "parallel-frontend-threads",
226 "number of parallel threads to use for the frontend when building test artifacts",
227 "THREADS_COUNT",
228 )
229 .optopt("", "iteration-count", "number of times to execute each test", "COUNT");
230
231 let (argv0, args_) = args.split_first().unwrap();
232 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
233 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
234 println!("{}", opts.usage(&message));
235 println!();
236 panic!()
237 }
238
239 let matches = &match opts.parse(args_) {
240 Ok(m) => m,
241 Err(f) => panic!("{:?}", f),
242 };
243
244 if matches.opt_present("h") || matches.opt_present("help") {
245 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
246 println!("{}", opts.usage(&message));
247 println!();
248 panic!()
249 }
250
251 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
252 if path.is_relative() {
253 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
254 } else {
255 path
256 }
257 }
258
259 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
260 match m.opt_str(nm) {
261 Some(s) => Utf8PathBuf::from(&s),
262 None => panic!("no option (=path) found for {}", nm),
263 }
264 }
265
266 let host = matches.opt_str("host").expect("`--host` must be unconditionally specified");
267 let target = matches.opt_str("target").expect("`--target` must be unconditionally specified");
268
269 let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from);
270
271 let adb_path = matches.opt_str("adb-path").map(Utf8PathBuf::from);
272 let adb_test_dir = matches.opt_str("adb-test-dir").map(Utf8PathBuf::from);
273 let adb_device_status = target.contains("android") && adb_test_dir.is_some();
274
275 let cdb = matches.opt_str("cdb").map(Utf8PathBuf::from);
277 let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
278 let gdb = matches.opt_str("gdb").map(Utf8PathBuf::from);
280 let gdb_version = gdb.as_deref().and_then(debuggers::query_gdb_version);
281 let lldb = matches.opt_str("lldb").map(Utf8PathBuf::from);
283 let lldb_version =
284 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
285 let llvm_version =
289 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
290 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
291 );
292
293 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
294 Some(backend) => match CodegenBackend::try_from(backend) {
295 Ok(backend) => backend,
296 Err(error) => {
297 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
298 }
299 },
300 None => CodegenBackend::Llvm,
302 };
303 let override_codegen_backend = matches.opt_str("override-codegen-backend");
304
305 let run_ignored = matches.opt_present("ignored");
306 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
307 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
308 let with_std_remap_debuginfo = matches.opt_present("with-std-remap-debuginfo");
309 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
310 let has_enzyme = matches.opt_present("has-enzyme");
311 let has_offload = matches.opt_present("has-offload");
312 let filters = if mode == TestMode::RunMake {
313 matches
314 .free
315 .iter()
316 .map(|f| {
317 let path = Utf8Path::new(f);
323 let mut iter = path.iter().skip(1);
324
325 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
326 path.parent().unwrap().to_string()
329 } else {
330 f.to_string()
331 }
332 })
333 .collect::<Vec<_>>()
334 } else {
335 matches.free.clone()
342 };
343 let compare_mode = matches.opt_str("compare-mode").map(|s| {
344 s.parse().unwrap_or_else(|_| {
345 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
346 panic!(
347 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
348 variants.join(", ")
349 );
350 })
351 });
352 if matches.opt_present("nocapture") {
353 panic!("`--nocapture` is deprecated; please use `--no-capture`");
354 }
355
356 let stage = match matches.opt_str("stage") {
357 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
358 None => panic!("`--stage` is required"),
359 };
360
361 let src_root = opt_path(matches, "src-root");
362 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
363 assert!(
364 src_test_suite_root.starts_with(&src_root),
365 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
366 src_root,
367 src_test_suite_root
368 );
369
370 let build_root = opt_path(matches, "build-root");
371 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
372 assert!(build_test_suite_root.starts_with(&build_root));
373
374 let jobs = match matches.opt_str("jobs") {
375 Some(jobs) => jobs.parse::<u32>().expect("expected `--jobs` to be an `u32`"),
376 None => panic!("`--jobs` is required"),
377 };
378
379 let parallel_frontend_threads = match matches.opt_str("parallel-frontend-threads") {
380 Some(threads) => {
381 threads.parse::<u32>().expect("expected `--parallel-frontend-threads` to be an `u32`")
382 }
383 None => Config::DEFAULT_PARALLEL_FRONTEND_THREADS,
384 };
385 let iteration_count = match matches.opt_str("iteration-count") {
386 Some(count) => {
387 count.parse::<u32>().expect("expected `--iteration-count` to be a positive integer")
388 }
389 None => Config::DEFAULT_ITERATION_COUNT,
390 };
391 assert!(iteration_count > 0, "`--iteration-count` must be a positive integer");
392
393 Config {
394 bless: matches.opt_present("bless"),
395 fail_fast: matches.opt_present("fail-fast")
396 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
397
398 host_compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
399 target_run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
400 rustc_path: opt_path(matches, "rustc-path"),
401 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
402 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
403 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
404 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
405 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
406 python: matches.opt_str("python").unwrap(),
407 jsondocck_path: matches.opt_str("jsondocck-path").map(Utf8PathBuf::from),
408 jsondoclint_path: matches.opt_str("jsondoclint-path").map(Utf8PathBuf::from),
409 run_clang_based_tests_with: matches
410 .opt_str("run-clang-based-tests-with")
411 .map(Utf8PathBuf::from),
412 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
413 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
414
415 src_root,
416 src_test_suite_root,
417
418 build_root,
419 build_test_suite_root,
420
421 sysroot_base: opt_path(matches, "sysroot-base"),
422
423 stage,
424 stage_id: matches.opt_str("stage-id").unwrap(),
425
426 mode,
427 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
428 debugger: matches.opt_str("debugger").map(|debugger| {
429 debugger
430 .parse::<Debugger>()
431 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
432 }),
433 run_ignored,
434 with_rustc_debug_assertions,
435 with_std_debug_assertions,
436 with_std_remap_debuginfo,
437 filters,
438 skip: matches.opt_strs("skip"),
439 filter_exact: matches.opt_present("exact"),
440 force_pass_mode: matches.opt_str("pass").map(|mode| {
441 mode.parse::<PassMode>()
442 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
443 }),
444 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
446 "auto" => None,
447 "always" => Some(true),
448 "never" => Some(false),
449 _ => panic!("unknown `--run` option `{}` given", mode),
450 }),
451 runner: matches.opt_str("runner"),
452 host_rustcflags: matches.opt_strs("host-rustcflags"),
453 target_rustcflags: matches.opt_strs("target-rustcflags"),
454 optimize_tests: matches.opt_present("optimize-tests"),
455 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
456 target,
457 host,
458 cdb,
459 cdb_version,
460 gdb,
461 gdb_version,
462 lldb,
463 lldb_version,
464 llvm_version,
465 system_llvm: matches.opt_present("system-llvm"),
466 android_cross_path,
467 adb_path,
468 adb_test_dir,
469 adb_device_status,
470 verbose: matches.opt_present("verbose"),
471 only_modified: matches.opt_present("only-modified"),
472 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
473 compare_mode,
474 rustfix_coverage: matches.opt_present("rustfix-coverage"),
475 has_enzyme,
476 has_offload,
477 channel: matches.opt_str("channel").unwrap(),
478 git_hash: matches.opt_present("git-hash"),
479 edition: matches.opt_str("edition").as_deref().map(parse_edition),
480
481 cc: matches.opt_str("cc").unwrap(),
482 cxx: matches.opt_str("cxx").unwrap(),
483 cflags: matches.opt_str("cflags").unwrap(),
484 cxxflags: matches.opt_str("cxxflags").unwrap(),
485 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
486 target_linker: matches.opt_str("target-linker"),
487 host_linker: matches.opt_str("host-linker"),
488 llvm_components: matches.opt_str("llvm-components").unwrap(),
489 nodejs: matches.opt_str("nodejs").map(Utf8PathBuf::from),
490
491 force_rerun: matches.opt_present("force-rerun"),
492
493 target_cfgs: OnceLock::new(),
494 builtin_cfg_names: OnceLock::new(),
495 supported_crate_types: OnceLock::new(),
496
497 capture: !matches.opt_present("no-capture"),
498
499 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
500 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
501
502 profiler_runtime: matches.opt_present("profiler-runtime"),
503
504 diff_command: matches.opt_str("compiletest-diff-tool"),
505
506 minicore_path: opt_path(matches, "minicore-path"),
507
508 default_codegen_backend,
509 override_codegen_backend,
510 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
511
512 jobs,
513
514 parallel_frontend_threads,
515 iteration_count,
516 }
517}
518
519fn run_tests(config: Arc<Config>) {
521 debug!(?config, "run_tests");
522
523 panic_hook::install_panic_hook();
524
525 if config.rustfix_coverage {
529 let mut coverage_file_path = config.build_test_suite_root.clone();
530 coverage_file_path.push("rustfix_missing_coverage.txt");
531 if coverage_file_path.exists() {
532 if let Err(e) = fs::remove_file(&coverage_file_path) {
533 panic!("Could not delete {} due to {}", coverage_file_path, e)
534 }
535 }
536 }
537
538 unsafe {
542 raise_fd_limit::raise_fd_limit();
543 }
544 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
549
550 let mut configs = Vec::new();
551 if let TestMode::DebugInfo = config.mode {
552 if !config.target.contains("emscripten") {
554 match config.debugger {
555 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
556 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
557 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
558 None => {
563 configs.extend(debuggers::configure_cdb(&config));
564 configs.extend(debuggers::configure_gdb(&config));
565 configs.extend(debuggers::configure_lldb(&config));
566 }
567 }
568 }
569 } else {
570 configs.push(config.clone());
571 };
572
573 let mut tests = Vec::new();
576 for c in configs {
577 tests.extend(collect_and_make_tests(c));
578 }
579
580 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
581
582 let ok = executor::run_tests(&config, tests);
586
587 if !ok {
589 let mut msg = String::from("Some tests failed in compiletest");
597 write!(msg, " suite={}", config.suite).unwrap();
598
599 if let Some(compare_mode) = config.compare_mode.as_ref() {
600 write!(msg, " compare_mode={}", compare_mode).unwrap();
601 }
602
603 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
604 write!(msg, " pass_mode={}", pass_mode).unwrap();
605 }
606
607 write!(msg, " mode={}", config.mode).unwrap();
608 write!(msg, " host={}", config.host).unwrap();
609 write!(msg, " target={}", config.target).unwrap();
610
611 println!("{msg}");
612
613 std::process::exit(1);
614 }
615}
616
617struct TestCollectorCx {
619 config: Arc<Config>,
620 cache: DirectivesCache,
621 common_inputs_stamp: Stamp,
622 modified_tests: Vec<Utf8PathBuf>,
623}
624
625struct TestCollector {
627 tests: Vec<CollectedTest>,
628 found_path_stems: HashSet<Utf8PathBuf>,
629 poisoned: bool,
630}
631
632impl TestCollector {
633 fn new() -> Self {
634 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
635 }
636
637 fn merge(&mut self, mut other: Self) {
638 self.tests.append(&mut other.tests);
639 self.found_path_stems.extend(other.found_path_stems);
640 self.poisoned |= other.poisoned;
641 }
642}
643
644fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
654 debug!("making tests from {}", config.src_test_suite_root);
655 let common_inputs_stamp = common_inputs_stamp(&config);
656 let modified_tests =
657 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
658 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
659 });
660 let cache = DirectivesCache::load(&config);
661
662 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
663 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
664 .unwrap_or_else(|reason| {
665 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
666 });
667
668 let TestCollector { tests, found_path_stems, poisoned } = collector;
669
670 if poisoned {
671 eprintln!();
672 panic!("there are errors in tests");
673 }
674
675 check_for_overlapping_test_paths(&found_path_stems);
676
677 tests
678}
679
680fn common_inputs_stamp(config: &Config) -> Stamp {
688 let src_root = &config.src_root;
689
690 let mut stamp = Stamp::from_path(&config.rustc_path);
691
692 let pretty_printer_files = [
694 "src/etc/rust_types.py",
695 "src/etc/gdb_load_rust_pretty_printers.py",
696 "src/etc/gdb_lookup.py",
697 "src/etc/gdb_providers.py",
698 "src/etc/lldb_batchmode.py",
699 "src/etc/lldb_lookup.py",
700 "src/etc/lldb_providers.py",
701 ];
702 for file in &pretty_printer_files {
703 let path = src_root.join(file);
704 stamp.add_path(&path);
705 }
706
707 stamp.add_dir(&src_root.join("src/etc/natvis"));
708
709 stamp.add_dir(&config.target_run_lib_path);
710
711 if let Some(ref rustdoc_path) = config.rustdoc_path {
712 stamp.add_path(&rustdoc_path);
713 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
714 }
715
716 if let Some(coverage_dump_path) = &config.coverage_dump_path {
719 stamp.add_path(coverage_dump_path)
720 }
721
722 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
723
724 stamp.add_dir(&src_root.join("src/tools/compiletest"));
726
727 stamp
728}
729
730fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
735 if !config.only_modified {
738 return Ok(vec![]);
739 }
740
741 let files = get_git_modified_files(
742 &config.git_config(),
743 Some(dir.as_std_path()),
744 &vec!["rs", "stderr", "fixed"],
745 )?;
746 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
748
749 let all_paths = [&files[..], &untracked_files[..]].concat();
750 let full_paths = {
751 let mut full_paths: Vec<Utf8PathBuf> = all_paths
752 .into_iter()
753 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
754 .filter_map(
755 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
756 )
757 .collect();
758 full_paths.dedup();
759 full_paths.sort_unstable();
760 full_paths
761 };
762 Ok(full_paths)
763}
764
765fn collect_tests_from_dir(
768 cx: &TestCollectorCx,
769 dir: &Utf8Path,
770 relative_dir_path: &Utf8Path,
771) -> io::Result<TestCollector> {
772 if dir.join("compiletest-ignore-dir").exists() {
774 return Ok(TestCollector::new());
775 }
776
777 let mut components = dir.components().rev();
778 if let Some(Utf8Component::Normal(last)) = components.next()
779 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
780 && let Some(Utf8Component::Normal(parent)) = components.next()
781 && parent == "tests"
782 && let Ok(backend) = CodegenBackend::try_from(backend)
783 && backend != cx.config.default_codegen_backend
784 {
785 warning!(
787 "Ignoring tests in `{dir}` because they don't match the configured codegen \
788 backend (`{}`)",
789 cx.config.default_codegen_backend.as_str(),
790 );
791 return Ok(TestCollector::new());
792 }
793
794 if cx.config.mode == TestMode::RunMake {
796 let mut collector = TestCollector::new();
797 if dir.join("rmake.rs").exists() {
798 let paths = TestPaths {
799 file: dir.to_path_buf(),
800 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
801 };
802 make_test(cx, &mut collector, &paths);
803 return Ok(collector);
805 }
806 }
807
808 let build_dir = output_relative_path(&cx.config, relative_dir_path);
815 fs::create_dir_all(&build_dir).unwrap();
816
817 fs::read_dir(dir.as_std_path())?
822 .par_bridge()
823 .map(|file| {
824 let mut collector = TestCollector::new();
825 let file = file?;
826 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
827 let file_name = file_path.file_name().unwrap();
828
829 if is_test(file_name)
830 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
831 {
832 debug!(%file_path, "found test file");
834
835 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
837 collector.found_path_stems.insert(rel_test_path);
838
839 let paths =
840 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
841 make_test(cx, &mut collector, &paths);
842 } else if file_path.is_dir() {
843 let relative_file_path = relative_dir_path.join(file_name);
845 if file_name != "auxiliary" {
846 debug!(%file_path, "found directory");
847 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
848 }
849 } else {
850 debug!(%file_path, "found other file/directory");
851 }
852 Ok(collector)
853 })
854 .reduce(
855 || Ok(TestCollector::new()),
856 |a, b| {
857 let mut a = a?;
858 a.merge(b?);
859 Ok(a)
860 },
861 )
862}
863
864fn is_test(file_name: &str) -> bool {
866 if !file_name.ends_with(".rs") {
867 return false;
868 }
869
870 let invalid_prefixes = &[".", "#", "~"];
872 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
873}
874
875fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
878 let test_path = if cx.config.mode == TestMode::RunMake {
882 testpaths.file.join("rmake.rs")
883 } else {
884 testpaths.file.clone()
885 };
886
887 let file_contents =
889 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
890 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
891
892 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
893 panic!("directives check failed:\n{message}");
896 }
897 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
898
899 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
906 vec![None]
907 } else {
908 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
909 };
910
911 collector.tests.extend(revisions.into_iter().map(|revision| {
914 let (test_name, filterable_path) =
916 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
917
918 let mut aux_props = AuxProps::default();
921
922 let mut desc = make_test_description(
926 &cx.config,
927 &cx.cache,
928 test_name,
929 &test_path,
930 &filterable_path,
931 &file_directives,
932 revision,
933 &mut collector.poisoned,
934 &mut aux_props,
935 );
936
937 if !desc.ignore
940 && !cx.config.force_rerun
941 && is_up_to_date(cx, testpaths, &aux_props, revision)
942 {
943 desc.ignore = true;
944 desc.ignore_message = Some("up-to-date".into());
948 }
949
950 let config = Arc::clone(&cx.config);
951 let testpaths = testpaths.clone();
952 let revision = revision.map(str::to_owned);
953
954 CollectedTest { desc, config, testpaths, revision }
955 }));
956}
957
958fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
961 output_base_dir(config, testpaths, revision).join("stamp")
962}
963
964fn files_related_to_test(
969 config: &Config,
970 testpaths: &TestPaths,
971 aux_props: &AuxProps,
972 revision: Option<&str>,
973) -> Vec<Utf8PathBuf> {
974 let mut related = vec![];
975
976 if testpaths.file.is_dir() {
977 for entry in WalkDir::new(&testpaths.file) {
979 let path = entry.unwrap().into_path();
980 if path.is_file() {
981 related.push(Utf8PathBuf::try_from(path).unwrap());
982 }
983 }
984 } else {
985 related.push(testpaths.file.clone());
986 }
987
988 for aux in aux_props.all_aux_path_strings() {
989 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
994 related.push(path);
995 }
996
997 for extension in UI_EXTENSIONS {
999 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1000 related.push(path);
1001 }
1002
1003 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1005
1006 related
1007}
1008
1009fn is_up_to_date(
1015 cx: &TestCollectorCx,
1016 testpaths: &TestPaths,
1017 aux_props: &AuxProps,
1018 revision: Option<&str>,
1019) -> bool {
1020 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1021 let contents = match fs::read_to_string(&stamp_file_path) {
1023 Ok(f) => f,
1024 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1025 Err(_) => return false,
1027 };
1028 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1029 if contents != expected_hash {
1030 return false;
1033 }
1034
1035 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1038 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1039 inputs_stamp.add_path(&path);
1040 }
1041
1042 inputs_stamp < Stamp::from_path(&stamp_file_path)
1045}
1046
1047#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1049struct Stamp {
1050 time: SystemTime,
1051}
1052
1053impl Stamp {
1054 fn from_path(path: &Utf8Path) -> Self {
1056 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1057 stamp.add_path(path);
1058 stamp
1059 }
1060
1061 fn add_path(&mut self, path: &Utf8Path) {
1064 let modified = fs::metadata(path.as_std_path())
1065 .and_then(|metadata| metadata.modified())
1066 .unwrap_or(SystemTime::UNIX_EPOCH);
1067 self.time = self.time.max(modified);
1068 }
1069
1070 fn add_dir(&mut self, path: &Utf8Path) {
1074 let path = path.as_std_path();
1075 for entry in WalkDir::new(path) {
1076 let entry = entry.unwrap();
1077 if entry.file_type().is_file() {
1078 let modified = entry
1079 .metadata()
1080 .ok()
1081 .and_then(|metadata| metadata.modified().ok())
1082 .unwrap_or(SystemTime::UNIX_EPOCH);
1083 self.time = self.time.max(modified);
1084 }
1085 }
1086 }
1087}
1088
1089fn make_test_name_and_filterable_path(
1091 config: &Config,
1092 testpaths: &TestPaths,
1093 revision: Option<&str>,
1094) -> (String, Utf8PathBuf) {
1095 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1097 let debugger = match config.debugger {
1098 Some(d) => format!("-{}", d),
1099 None => String::new(),
1100 };
1101 let mode_suffix = match config.compare_mode {
1102 Some(ref mode) => format!(" ({})", mode.to_str()),
1103 None => String::new(),
1104 };
1105
1106 let name = format!(
1107 "[{}{}{}] {}{}",
1108 config.mode,
1109 debugger,
1110 mode_suffix,
1111 path,
1112 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1113 );
1114
1115 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1119 filterable_path = filterable_path.components().skip(1).collect();
1121
1122 (name, filterable_path)
1123}
1124
1125fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1143 let mut collisions = Vec::new();
1144 for path in found_path_stems {
1145 for ancestor in path.ancestors().skip(1) {
1146 if found_path_stems.contains(ancestor) {
1147 collisions.push((path, ancestor));
1148 }
1149 }
1150 }
1151 if !collisions.is_empty() {
1152 collisions.sort();
1153 let collisions: String = collisions
1154 .into_iter()
1155 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1156 .collect();
1157 panic!(
1158 "{collisions}\n\
1159 Tests cannot have overlapping names. Make sure they use unique prefixes."
1160 );
1161 }
1162}
1163
1164fn early_config_check(config: &Config) {
1165 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1166 let actioned = if config.bless { "blessed" } else { "checked" };
1167 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1168 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1169 }
1170
1171 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1173 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1174 }
1175}