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
224 let (argv0, args_) = args.split_first().unwrap();
225 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
226 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
227 println!("{}", opts.usage(&message));
228 println!();
229 panic!()
230 }
231
232 let matches = &match opts.parse(args_) {
233 Ok(m) => m,
234 Err(f) => panic!("{:?}", f),
235 };
236
237 if matches.opt_present("h") || matches.opt_present("help") {
238 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
239 println!("{}", opts.usage(&message));
240 println!();
241 panic!()
242 }
243
244 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
245 if path.is_relative() {
246 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
247 } else {
248 path
249 }
250 }
251
252 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
253 match m.opt_str(nm) {
254 Some(s) => Utf8PathBuf::from(&s),
255 None => panic!("no option (=path) found for {}", nm),
256 }
257 }
258
259 let host = matches.opt_str("host").expect("`--host` must be unconditionally specified");
260 let target = matches.opt_str("target").expect("`--target` must be unconditionally specified");
261
262 let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from);
263
264 let adb_path = matches.opt_str("adb-path").map(Utf8PathBuf::from);
265 let adb_test_dir = matches.opt_str("adb-test-dir").map(Utf8PathBuf::from);
266 let adb_device_status = target.contains("android") && adb_test_dir.is_some();
267
268 let cdb = matches.opt_str("cdb").map(Utf8PathBuf::from);
270 let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
271 let gdb = matches.opt_str("gdb").map(Utf8PathBuf::from);
273 let gdb_version = gdb.as_deref().and_then(debuggers::query_gdb_version);
274 let lldb = matches.opt_str("lldb").map(Utf8PathBuf::from);
276 let lldb_version =
277 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
278 let llvm_version =
282 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
283 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
284 );
285
286 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
287 Some(backend) => match CodegenBackend::try_from(backend) {
288 Ok(backend) => backend,
289 Err(error) => {
290 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
291 }
292 },
293 None => CodegenBackend::Llvm,
295 };
296 let override_codegen_backend = matches.opt_str("override-codegen-backend");
297
298 let run_ignored = matches.opt_present("ignored");
299 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
300 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
301 let with_std_remap_debuginfo = matches.opt_present("with-std-remap-debuginfo");
302 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
303 let has_enzyme = matches.opt_present("has-enzyme");
304 let has_offload = matches.opt_present("has-offload");
305 let filters = if mode == TestMode::RunMake {
306 matches
307 .free
308 .iter()
309 .map(|f| {
310 let path = Utf8Path::new(f);
316 let mut iter = path.iter().skip(1);
317
318 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
319 path.parent().unwrap().to_string()
322 } else {
323 f.to_string()
324 }
325 })
326 .collect::<Vec<_>>()
327 } else {
328 matches.free.clone()
335 };
336 let compare_mode = matches.opt_str("compare-mode").map(|s| {
337 s.parse().unwrap_or_else(|_| {
338 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
339 panic!(
340 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
341 variants.join(", ")
342 );
343 })
344 });
345 if matches.opt_present("nocapture") {
346 panic!("`--nocapture` is deprecated; please use `--no-capture`");
347 }
348
349 let stage = match matches.opt_str("stage") {
350 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
351 None => panic!("`--stage` is required"),
352 };
353
354 let src_root = opt_path(matches, "src-root");
355 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
356 assert!(
357 src_test_suite_root.starts_with(&src_root),
358 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
359 src_root,
360 src_test_suite_root
361 );
362
363 let build_root = opt_path(matches, "build-root");
364 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
365 assert!(build_test_suite_root.starts_with(&build_root));
366
367 let jobs = match matches.opt_str("jobs") {
368 Some(jobs) => jobs.parse::<u32>().expect("expected `--jobs` to be an `u32`"),
369 None => panic!("`--jobs` is required"),
370 };
371
372 Config {
373 bless: matches.opt_present("bless"),
374 fail_fast: matches.opt_present("fail-fast")
375 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
376
377 host_compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
378 target_run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
379 rustc_path: opt_path(matches, "rustc-path"),
380 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
381 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
382 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
383 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
384 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
385 python: matches.opt_str("python").unwrap(),
386 jsondocck_path: matches.opt_str("jsondocck-path").map(Utf8PathBuf::from),
387 jsondoclint_path: matches.opt_str("jsondoclint-path").map(Utf8PathBuf::from),
388 run_clang_based_tests_with: matches
389 .opt_str("run-clang-based-tests-with")
390 .map(Utf8PathBuf::from),
391 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
392 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
393
394 src_root,
395 src_test_suite_root,
396
397 build_root,
398 build_test_suite_root,
399
400 sysroot_base: opt_path(matches, "sysroot-base"),
401
402 stage,
403 stage_id: matches.opt_str("stage-id").unwrap(),
404
405 mode,
406 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
407 debugger: matches.opt_str("debugger").map(|debugger| {
408 debugger
409 .parse::<Debugger>()
410 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
411 }),
412 run_ignored,
413 with_rustc_debug_assertions,
414 with_std_debug_assertions,
415 with_std_remap_debuginfo,
416 filters,
417 skip: matches.opt_strs("skip"),
418 filter_exact: matches.opt_present("exact"),
419 force_pass_mode: matches.opt_str("pass").map(|mode| {
420 mode.parse::<PassMode>()
421 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
422 }),
423 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
425 "auto" => None,
426 "always" => Some(true),
427 "never" => Some(false),
428 _ => panic!("unknown `--run` option `{}` given", mode),
429 }),
430 runner: matches.opt_str("runner"),
431 host_rustcflags: matches.opt_strs("host-rustcflags"),
432 target_rustcflags: matches.opt_strs("target-rustcflags"),
433 optimize_tests: matches.opt_present("optimize-tests"),
434 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
435 target,
436 host,
437 cdb,
438 cdb_version,
439 gdb,
440 gdb_version,
441 lldb,
442 lldb_version,
443 llvm_version,
444 system_llvm: matches.opt_present("system-llvm"),
445 android_cross_path,
446 adb_path,
447 adb_test_dir,
448 adb_device_status,
449 verbose: matches.opt_present("verbose"),
450 only_modified: matches.opt_present("only-modified"),
451 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
452 compare_mode,
453 rustfix_coverage: matches.opt_present("rustfix-coverage"),
454 has_enzyme,
455 has_offload,
456 channel: matches.opt_str("channel").unwrap(),
457 git_hash: matches.opt_present("git-hash"),
458 edition: matches.opt_str("edition").as_deref().map(parse_edition),
459
460 cc: matches.opt_str("cc").unwrap(),
461 cxx: matches.opt_str("cxx").unwrap(),
462 cflags: matches.opt_str("cflags").unwrap(),
463 cxxflags: matches.opt_str("cxxflags").unwrap(),
464 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
465 target_linker: matches.opt_str("target-linker"),
466 host_linker: matches.opt_str("host-linker"),
467 llvm_components: matches.opt_str("llvm-components").unwrap(),
468 nodejs: matches.opt_str("nodejs").map(Utf8PathBuf::from),
469
470 force_rerun: matches.opt_present("force-rerun"),
471
472 target_cfgs: OnceLock::new(),
473 builtin_cfg_names: OnceLock::new(),
474 supported_crate_types: OnceLock::new(),
475
476 capture: !matches.opt_present("no-capture"),
477
478 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
479 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
480
481 profiler_runtime: matches.opt_present("profiler-runtime"),
482
483 diff_command: matches.opt_str("compiletest-diff-tool"),
484
485 minicore_path: opt_path(matches, "minicore-path"),
486
487 default_codegen_backend,
488 override_codegen_backend,
489 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
490
491 jobs,
492 }
493}
494
495fn run_tests(config: Arc<Config>) {
497 debug!(?config, "run_tests");
498
499 panic_hook::install_panic_hook();
500
501 if config.rustfix_coverage {
505 let mut coverage_file_path = config.build_test_suite_root.clone();
506 coverage_file_path.push("rustfix_missing_coverage.txt");
507 if coverage_file_path.exists() {
508 if let Err(e) = fs::remove_file(&coverage_file_path) {
509 panic!("Could not delete {} due to {}", coverage_file_path, e)
510 }
511 }
512 }
513
514 unsafe {
518 raise_fd_limit::raise_fd_limit();
519 }
520 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
525
526 let mut configs = Vec::new();
527 if let TestMode::DebugInfo = config.mode {
528 if !config.target.contains("emscripten") {
530 match config.debugger {
531 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
532 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
533 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
534 None => {
539 configs.extend(debuggers::configure_cdb(&config));
540 configs.extend(debuggers::configure_gdb(&config));
541 configs.extend(debuggers::configure_lldb(&config));
542 }
543 }
544 }
545 } else {
546 configs.push(config.clone());
547 };
548
549 let mut tests = Vec::new();
552 for c in configs {
553 tests.extend(collect_and_make_tests(c));
554 }
555
556 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
557
558 let ok = executor::run_tests(&config, tests);
562
563 if !ok {
565 let mut msg = String::from("Some tests failed in compiletest");
573 write!(msg, " suite={}", config.suite).unwrap();
574
575 if let Some(compare_mode) = config.compare_mode.as_ref() {
576 write!(msg, " compare_mode={}", compare_mode).unwrap();
577 }
578
579 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
580 write!(msg, " pass_mode={}", pass_mode).unwrap();
581 }
582
583 write!(msg, " mode={}", config.mode).unwrap();
584 write!(msg, " host={}", config.host).unwrap();
585 write!(msg, " target={}", config.target).unwrap();
586
587 println!("{msg}");
588
589 std::process::exit(1);
590 }
591}
592
593struct TestCollectorCx {
595 config: Arc<Config>,
596 cache: DirectivesCache,
597 common_inputs_stamp: Stamp,
598 modified_tests: Vec<Utf8PathBuf>,
599}
600
601struct TestCollector {
603 tests: Vec<CollectedTest>,
604 found_path_stems: HashSet<Utf8PathBuf>,
605 poisoned: bool,
606}
607
608impl TestCollector {
609 fn new() -> Self {
610 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
611 }
612
613 fn merge(&mut self, mut other: Self) {
614 self.tests.append(&mut other.tests);
615 self.found_path_stems.extend(other.found_path_stems);
616 self.poisoned |= other.poisoned;
617 }
618}
619
620fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
630 debug!("making tests from {}", config.src_test_suite_root);
631 let common_inputs_stamp = common_inputs_stamp(&config);
632 let modified_tests =
633 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
634 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
635 });
636 let cache = DirectivesCache::load(&config);
637
638 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
639 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
640 .unwrap_or_else(|reason| {
641 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
642 });
643
644 let TestCollector { tests, found_path_stems, poisoned } = collector;
645
646 if poisoned {
647 eprintln!();
648 panic!("there are errors in tests");
649 }
650
651 check_for_overlapping_test_paths(&found_path_stems);
652
653 tests
654}
655
656fn common_inputs_stamp(config: &Config) -> Stamp {
664 let src_root = &config.src_root;
665
666 let mut stamp = Stamp::from_path(&config.rustc_path);
667
668 let pretty_printer_files = [
670 "src/etc/rust_types.py",
671 "src/etc/gdb_load_rust_pretty_printers.py",
672 "src/etc/gdb_lookup.py",
673 "src/etc/gdb_providers.py",
674 "src/etc/lldb_batchmode.py",
675 "src/etc/lldb_lookup.py",
676 "src/etc/lldb_providers.py",
677 ];
678 for file in &pretty_printer_files {
679 let path = src_root.join(file);
680 stamp.add_path(&path);
681 }
682
683 stamp.add_dir(&src_root.join("src/etc/natvis"));
684
685 stamp.add_dir(&config.target_run_lib_path);
686
687 if let Some(ref rustdoc_path) = config.rustdoc_path {
688 stamp.add_path(&rustdoc_path);
689 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
690 }
691
692 if let Some(coverage_dump_path) = &config.coverage_dump_path {
695 stamp.add_path(coverage_dump_path)
696 }
697
698 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
699
700 stamp.add_dir(&src_root.join("src/tools/compiletest"));
702
703 stamp
704}
705
706fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
711 if !config.only_modified {
714 return Ok(vec![]);
715 }
716
717 let files = get_git_modified_files(
718 &config.git_config(),
719 Some(dir.as_std_path()),
720 &vec!["rs", "stderr", "fixed"],
721 )?;
722 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
724
725 let all_paths = [&files[..], &untracked_files[..]].concat();
726 let full_paths = {
727 let mut full_paths: Vec<Utf8PathBuf> = all_paths
728 .into_iter()
729 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
730 .filter_map(
731 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
732 )
733 .collect();
734 full_paths.dedup();
735 full_paths.sort_unstable();
736 full_paths
737 };
738 Ok(full_paths)
739}
740
741fn collect_tests_from_dir(
744 cx: &TestCollectorCx,
745 dir: &Utf8Path,
746 relative_dir_path: &Utf8Path,
747) -> io::Result<TestCollector> {
748 if dir.join("compiletest-ignore-dir").exists() {
750 return Ok(TestCollector::new());
751 }
752
753 let mut components = dir.components().rev();
754 if let Some(Utf8Component::Normal(last)) = components.next()
755 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
756 && let Some(Utf8Component::Normal(parent)) = components.next()
757 && parent == "tests"
758 && let Ok(backend) = CodegenBackend::try_from(backend)
759 && backend != cx.config.default_codegen_backend
760 {
761 warning!(
763 "Ignoring tests in `{dir}` because they don't match the configured codegen \
764 backend (`{}`)",
765 cx.config.default_codegen_backend.as_str(),
766 );
767 return Ok(TestCollector::new());
768 }
769
770 if cx.config.mode == TestMode::RunMake {
772 let mut collector = TestCollector::new();
773 if dir.join("rmake.rs").exists() {
774 let paths = TestPaths {
775 file: dir.to_path_buf(),
776 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
777 };
778 make_test(cx, &mut collector, &paths);
779 return Ok(collector);
781 }
782 }
783
784 let build_dir = output_relative_path(&cx.config, relative_dir_path);
791 fs::create_dir_all(&build_dir).unwrap();
792
793 fs::read_dir(dir.as_std_path())?
798 .par_bridge()
799 .map(|file| {
800 let mut collector = TestCollector::new();
801 let file = file?;
802 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
803 let file_name = file_path.file_name().unwrap();
804
805 if is_test(file_name)
806 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
807 {
808 debug!(%file_path, "found test file");
810
811 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
813 collector.found_path_stems.insert(rel_test_path);
814
815 let paths =
816 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
817 make_test(cx, &mut collector, &paths);
818 } else if file_path.is_dir() {
819 let relative_file_path = relative_dir_path.join(file_name);
821 if file_name != "auxiliary" {
822 debug!(%file_path, "found directory");
823 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
824 }
825 } else {
826 debug!(%file_path, "found other file/directory");
827 }
828 Ok(collector)
829 })
830 .reduce(
831 || Ok(TestCollector::new()),
832 |a, b| {
833 let mut a = a?;
834 a.merge(b?);
835 Ok(a)
836 },
837 )
838}
839
840fn is_test(file_name: &str) -> bool {
842 if !file_name.ends_with(".rs") {
843 return false;
844 }
845
846 let invalid_prefixes = &[".", "#", "~"];
848 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
849}
850
851fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
854 let test_path = if cx.config.mode == TestMode::RunMake {
858 testpaths.file.join("rmake.rs")
859 } else {
860 testpaths.file.clone()
861 };
862
863 let file_contents =
865 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
866 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
867
868 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
869 panic!("directives check failed:\n{message}");
872 }
873 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
874
875 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
882 vec![None]
883 } else {
884 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
885 };
886
887 collector.tests.extend(revisions.into_iter().map(|revision| {
890 let (test_name, filterable_path) =
892 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
893
894 let mut aux_props = AuxProps::default();
897
898 let mut desc = make_test_description(
902 &cx.config,
903 &cx.cache,
904 test_name,
905 &test_path,
906 &filterable_path,
907 &file_directives,
908 revision,
909 &mut collector.poisoned,
910 &mut aux_props,
911 );
912
913 if !desc.ignore
916 && !cx.config.force_rerun
917 && is_up_to_date(cx, testpaths, &aux_props, revision)
918 {
919 desc.ignore = true;
920 desc.ignore_message = Some("up-to-date".into());
924 }
925
926 let config = Arc::clone(&cx.config);
927 let testpaths = testpaths.clone();
928 let revision = revision.map(str::to_owned);
929
930 CollectedTest { desc, config, testpaths, revision }
931 }));
932}
933
934fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
937 output_base_dir(config, testpaths, revision).join("stamp")
938}
939
940fn files_related_to_test(
945 config: &Config,
946 testpaths: &TestPaths,
947 aux_props: &AuxProps,
948 revision: Option<&str>,
949) -> Vec<Utf8PathBuf> {
950 let mut related = vec![];
951
952 if testpaths.file.is_dir() {
953 for entry in WalkDir::new(&testpaths.file) {
955 let path = entry.unwrap().into_path();
956 if path.is_file() {
957 related.push(Utf8PathBuf::try_from(path).unwrap());
958 }
959 }
960 } else {
961 related.push(testpaths.file.clone());
962 }
963
964 for aux in aux_props.all_aux_path_strings() {
965 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
970 related.push(path);
971 }
972
973 for extension in UI_EXTENSIONS {
975 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
976 related.push(path);
977 }
978
979 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
981
982 related
983}
984
985fn is_up_to_date(
991 cx: &TestCollectorCx,
992 testpaths: &TestPaths,
993 aux_props: &AuxProps,
994 revision: Option<&str>,
995) -> bool {
996 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
997 let contents = match fs::read_to_string(&stamp_file_path) {
999 Ok(f) => f,
1000 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1001 Err(_) => return false,
1003 };
1004 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1005 if contents != expected_hash {
1006 return false;
1009 }
1010
1011 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1014 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1015 inputs_stamp.add_path(&path);
1016 }
1017
1018 inputs_stamp < Stamp::from_path(&stamp_file_path)
1021}
1022
1023#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1025struct Stamp {
1026 time: SystemTime,
1027}
1028
1029impl Stamp {
1030 fn from_path(path: &Utf8Path) -> Self {
1032 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1033 stamp.add_path(path);
1034 stamp
1035 }
1036
1037 fn add_path(&mut self, path: &Utf8Path) {
1040 let modified = fs::metadata(path.as_std_path())
1041 .and_then(|metadata| metadata.modified())
1042 .unwrap_or(SystemTime::UNIX_EPOCH);
1043 self.time = self.time.max(modified);
1044 }
1045
1046 fn add_dir(&mut self, path: &Utf8Path) {
1050 let path = path.as_std_path();
1051 for entry in WalkDir::new(path) {
1052 let entry = entry.unwrap();
1053 if entry.file_type().is_file() {
1054 let modified = entry
1055 .metadata()
1056 .ok()
1057 .and_then(|metadata| metadata.modified().ok())
1058 .unwrap_or(SystemTime::UNIX_EPOCH);
1059 self.time = self.time.max(modified);
1060 }
1061 }
1062 }
1063}
1064
1065fn make_test_name_and_filterable_path(
1067 config: &Config,
1068 testpaths: &TestPaths,
1069 revision: Option<&str>,
1070) -> (String, Utf8PathBuf) {
1071 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1073 let debugger = match config.debugger {
1074 Some(d) => format!("-{}", d),
1075 None => String::new(),
1076 };
1077 let mode_suffix = match config.compare_mode {
1078 Some(ref mode) => format!(" ({})", mode.to_str()),
1079 None => String::new(),
1080 };
1081
1082 let name = format!(
1083 "[{}{}{}] {}{}",
1084 config.mode,
1085 debugger,
1086 mode_suffix,
1087 path,
1088 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1089 );
1090
1091 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1095 filterable_path = filterable_path.components().skip(1).collect();
1097
1098 (name, filterable_path)
1099}
1100
1101fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1119 let mut collisions = Vec::new();
1120 for path in found_path_stems {
1121 for ancestor in path.ancestors().skip(1) {
1122 if found_path_stems.contains(ancestor) {
1123 collisions.push((path, ancestor));
1124 }
1125 }
1126 }
1127 if !collisions.is_empty() {
1128 collisions.sort();
1129 let collisions: String = collisions
1130 .into_iter()
1131 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1132 .collect();
1133 panic!(
1134 "{collisions}\n\
1135 Tests cannot have overlapping names. Make sure they use unique prefixes."
1136 );
1137 }
1138}
1139
1140fn early_config_check(config: &Config) {
1141 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1142 let actioned = if config.bless { "blessed" } else { "checked" };
1143 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1144 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1145 }
1146
1147 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1149 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1150 }
1151}