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