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 \
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("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
108 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
109 .optmulti(
110 "",
111 "skip",
112 "skip tests matching SUBSTRING. Can be passed multiple times",
113 "SUBSTRING",
114 )
115 .optflag("", "exact", "filters match exactly")
116 .optopt(
117 "",
118 "runner",
119 "supervisor program to run tests under \
120 (eg. emulator, valgrind)",
121 "PROGRAM",
122 )
123 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
124 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
125 .optflag(
126 "",
127 "rust-randomized-layout",
128 "set this when rustc/stdlib were compiled with randomized layouts",
129 )
130 .optflag("", "optimize-tests", "run tests with optimizations enabled")
131 .optflag("", "verbose", "run tests verbosely, showing all output")
132 .optflag(
133 "",
134 "bless",
135 "overwrite stderr/stdout files instead of complaining about a mismatch",
136 )
137 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
138 .optopt("", "target", "the target to build for", "TARGET")
139 .optopt("", "host", "the host to build for", "HOST")
140 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
141 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
142 .optopt("", "lldb", "path to LLDB to use for LLDB debuginfo tests", "PATH")
143 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
144 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
145 .optflag("", "system-llvm", "is LLVM the system LLVM")
146 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
147 .optopt("", "adb-path", "path to the android debugger", "PATH")
148 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
149 .reqopt("", "cc", "path to a C compiler", "PATH")
150 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
151 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
152 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
153 .optopt("", "ar", "path to an archiver", "PATH")
154 .optopt("", "target-linker", "path to a linker for the target", "PATH")
155 .optopt("", "host-linker", "path to a linker for the host", "PATH")
156 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
157 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
158 .optopt("", "nodejs", "the name of nodejs", "PATH")
159 .optopt("", "npm", "the name of npm", "PATH")
160 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
161 .optopt(
162 "",
163 "compare-mode",
164 "mode describing what file the actual ui output will be compared to",
165 "COMPARE MODE",
166 )
167 .optflag(
168 "",
169 "rustfix-coverage",
170 "enable this to generate a Rustfix coverage file, which is saved in \
171 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
172 )
173 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
174 .optflag("", "only-modified", "only run tests that result been modified")
175 .optflag("", "nocapture", "")
177 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
178 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
179 .optflag("h", "help", "show this message")
180 .reqopt("", "channel", "current Rust channel", "CHANNEL")
181 .optflag(
182 "",
183 "git-hash",
184 "run tests which rely on commit version being compiled into the binaries",
185 )
186 .optopt("", "edition", "default Rust edition", "EDITION")
187 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
188 .reqopt(
189 "",
190 "git-merge-commit-email",
191 "email address used for finding merge commits",
192 "EMAIL",
193 )
194 .optopt(
195 "",
196 "compiletest-diff-tool",
197 "What custom diff tool to use for displaying compiletest tests.",
198 "COMMAND",
199 )
200 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
201 .optopt(
202 "",
203 "debugger",
204 "only test a specific debugger in debuginfo tests",
205 "gdb | lldb | cdb",
206 )
207 .optopt(
208 "",
209 "default-codegen-backend",
210 "the codegen backend currently used",
211 "CODEGEN BACKEND NAME",
212 )
213 .optopt(
214 "",
215 "override-codegen-backend",
216 "the codegen backend to use instead of the default one",
217 "CODEGEN BACKEND [NAME | PATH]",
218 )
219 .optflag("", "bypass-ignore-backends", "ignore `//@ ignore-backends` directives");
220
221 let (argv0, args_) = args.split_first().unwrap();
222 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
223 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
224 println!("{}", opts.usage(&message));
225 println!();
226 panic!()
227 }
228
229 let matches = &match opts.parse(args_) {
230 Ok(m) => m,
231 Err(f) => panic!("{:?}", f),
232 };
233
234 if matches.opt_present("h") || matches.opt_present("help") {
235 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
236 println!("{}", opts.usage(&message));
237 println!();
238 panic!()
239 }
240
241 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
242 if path.is_relative() {
243 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
244 } else {
245 path
246 }
247 }
248
249 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
250 match m.opt_str(nm) {
251 Some(s) => Utf8PathBuf::from(&s),
252 None => panic!("no option (=path) found for {}", nm),
253 }
254 }
255
256 let host = matches.opt_str("host").expect("`--host` must be unconditionally specified");
257 let target = matches.opt_str("target").expect("`--target` must be unconditionally specified");
258
259 let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from);
260
261 let adb_path = matches.opt_str("adb-path").map(Utf8PathBuf::from);
262 let adb_test_dir = matches.opt_str("adb-test-dir").map(Utf8PathBuf::from);
263 let adb_device_status = target.contains("android") && adb_test_dir.is_some();
264
265 let cdb = debuggers::discover_cdb(matches.opt_str("cdb"), &target);
267 let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
268 let gdb = matches.opt_str("gdb").map(Utf8PathBuf::from);
270 let gdb_version = gdb.as_deref().and_then(debuggers::query_gdb_version);
271 let lldb = matches.opt_str("lldb").map(Utf8PathBuf::from);
273 let lldb_version =
274 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
275 let llvm_version =
279 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
280 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
281 );
282
283 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
284 Some(backend) => match CodegenBackend::try_from(backend) {
285 Ok(backend) => backend,
286 Err(error) => {
287 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
288 }
289 },
290 None => CodegenBackend::Llvm,
292 };
293 let override_codegen_backend = matches.opt_str("override-codegen-backend");
294
295 let run_ignored = matches.opt_present("ignored");
296 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
297 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
298 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
299 let has_enzyme = matches.opt_present("has-enzyme");
300 let filters = if mode == TestMode::RunMake {
301 matches
302 .free
303 .iter()
304 .map(|f| {
305 let path = Utf8Path::new(f);
311 let mut iter = path.iter().skip(1);
312
313 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
314 path.parent().unwrap().to_string()
317 } else {
318 f.to_string()
319 }
320 })
321 .collect::<Vec<_>>()
322 } else {
323 matches.free.clone()
330 };
331 let compare_mode = matches.opt_str("compare-mode").map(|s| {
332 s.parse().unwrap_or_else(|_| {
333 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
334 panic!(
335 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
336 variants.join(", ")
337 );
338 })
339 });
340 if matches.opt_present("nocapture") {
341 panic!("`--nocapture` is deprecated; please use `--no-capture`");
342 }
343
344 let stage = match matches.opt_str("stage") {
345 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
346 None => panic!("`--stage` is required"),
347 };
348
349 let src_root = opt_path(matches, "src-root");
350 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
351 assert!(
352 src_test_suite_root.starts_with(&src_root),
353 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
354 src_root,
355 src_test_suite_root
356 );
357
358 let build_root = opt_path(matches, "build-root");
359 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
360 assert!(build_test_suite_root.starts_with(&build_root));
361
362 Config {
363 bless: matches.opt_present("bless"),
364 fail_fast: matches.opt_present("fail-fast")
365 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
366
367 host_compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
368 target_run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
369 rustc_path: opt_path(matches, "rustc-path"),
370 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
371 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
372 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
373 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
374 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
375 python: matches.opt_str("python").unwrap(),
376 jsondocck_path: matches.opt_str("jsondocck-path").map(Utf8PathBuf::from),
377 jsondoclint_path: matches.opt_str("jsondoclint-path").map(Utf8PathBuf::from),
378 run_clang_based_tests_with: matches
379 .opt_str("run-clang-based-tests-with")
380 .map(Utf8PathBuf::from),
381 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
382 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
383
384 src_root,
385 src_test_suite_root,
386
387 build_root,
388 build_test_suite_root,
389
390 sysroot_base: opt_path(matches, "sysroot-base"),
391
392 stage,
393 stage_id: matches.opt_str("stage-id").unwrap(),
394
395 mode,
396 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
397 debugger: matches.opt_str("debugger").map(|debugger| {
398 debugger
399 .parse::<Debugger>()
400 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
401 }),
402 run_ignored,
403 with_rustc_debug_assertions,
404 with_std_debug_assertions,
405 filters,
406 skip: matches.opt_strs("skip"),
407 filter_exact: matches.opt_present("exact"),
408 force_pass_mode: matches.opt_str("pass").map(|mode| {
409 mode.parse::<PassMode>()
410 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
411 }),
412 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
414 "auto" => None,
415 "always" => Some(true),
416 "never" => Some(false),
417 _ => panic!("unknown `--run` option `{}` given", mode),
418 }),
419 runner: matches.opt_str("runner"),
420 host_rustcflags: matches.opt_strs("host-rustcflags"),
421 target_rustcflags: matches.opt_strs("target-rustcflags"),
422 optimize_tests: matches.opt_present("optimize-tests"),
423 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
424 target,
425 host,
426 cdb,
427 cdb_version,
428 gdb,
429 gdb_version,
430 lldb,
431 lldb_version,
432 llvm_version,
433 system_llvm: matches.opt_present("system-llvm"),
434 android_cross_path,
435 adb_path,
436 adb_test_dir,
437 adb_device_status,
438 verbose: matches.opt_present("verbose"),
439 only_modified: matches.opt_present("only-modified"),
440 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
441 compare_mode,
442 rustfix_coverage: matches.opt_present("rustfix-coverage"),
443 has_enzyme,
444 channel: matches.opt_str("channel").unwrap(),
445 git_hash: matches.opt_present("git-hash"),
446 edition: matches.opt_str("edition").as_deref().map(parse_edition),
447
448 cc: matches.opt_str("cc").unwrap(),
449 cxx: matches.opt_str("cxx").unwrap(),
450 cflags: matches.opt_str("cflags").unwrap(),
451 cxxflags: matches.opt_str("cxxflags").unwrap(),
452 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
453 target_linker: matches.opt_str("target-linker"),
454 host_linker: matches.opt_str("host-linker"),
455 llvm_components: matches.opt_str("llvm-components").unwrap(),
456 nodejs: matches.opt_str("nodejs").map(Utf8PathBuf::from),
457
458 force_rerun: matches.opt_present("force-rerun"),
459
460 target_cfgs: OnceLock::new(),
461 builtin_cfg_names: OnceLock::new(),
462 supported_crate_types: OnceLock::new(),
463
464 capture: !matches.opt_present("no-capture"),
465
466 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
467 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
468
469 profiler_runtime: matches.opt_present("profiler-runtime"),
470
471 diff_command: matches.opt_str("compiletest-diff-tool"),
472
473 minicore_path: opt_path(matches, "minicore-path"),
474
475 default_codegen_backend,
476 override_codegen_backend,
477 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
478 }
479}
480
481fn run_tests(config: Arc<Config>) {
483 debug!(?config, "run_tests");
484
485 panic_hook::install_panic_hook();
486
487 if config.rustfix_coverage {
491 let mut coverage_file_path = config.build_test_suite_root.clone();
492 coverage_file_path.push("rustfix_missing_coverage.txt");
493 if coverage_file_path.exists() {
494 if let Err(e) = fs::remove_file(&coverage_file_path) {
495 panic!("Could not delete {} due to {}", coverage_file_path, e)
496 }
497 }
498 }
499
500 unsafe {
504 raise_fd_limit::raise_fd_limit();
505 }
506 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
511
512 let mut configs = Vec::new();
513 if let TestMode::DebugInfo = config.mode {
514 if !config.target.contains("emscripten") {
516 match config.debugger {
517 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
518 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
519 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
520 None => {
525 configs.extend(debuggers::configure_cdb(&config));
526 configs.extend(debuggers::configure_gdb(&config));
527 configs.extend(debuggers::configure_lldb(&config));
528 }
529 }
530 }
531 } else {
532 configs.push(config.clone());
533 };
534
535 let mut tests = Vec::new();
538 for c in configs {
539 tests.extend(collect_and_make_tests(c));
540 }
541
542 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
543
544 let ok = executor::run_tests(&config, tests);
548
549 if !ok {
551 let mut msg = String::from("Some tests failed in compiletest");
559 write!(msg, " suite={}", config.suite).unwrap();
560
561 if let Some(compare_mode) = config.compare_mode.as_ref() {
562 write!(msg, " compare_mode={}", compare_mode).unwrap();
563 }
564
565 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
566 write!(msg, " pass_mode={}", pass_mode).unwrap();
567 }
568
569 write!(msg, " mode={}", config.mode).unwrap();
570 write!(msg, " host={}", config.host).unwrap();
571 write!(msg, " target={}", config.target).unwrap();
572
573 println!("{msg}");
574
575 std::process::exit(1);
576 }
577}
578
579struct TestCollectorCx {
581 config: Arc<Config>,
582 cache: DirectivesCache,
583 common_inputs_stamp: Stamp,
584 modified_tests: Vec<Utf8PathBuf>,
585}
586
587struct TestCollector {
589 tests: Vec<CollectedTest>,
590 found_path_stems: HashSet<Utf8PathBuf>,
591 poisoned: bool,
592}
593
594impl TestCollector {
595 fn new() -> Self {
596 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
597 }
598
599 fn merge(&mut self, mut other: Self) {
600 self.tests.append(&mut other.tests);
601 self.found_path_stems.extend(other.found_path_stems);
602 self.poisoned |= other.poisoned;
603 }
604}
605
606fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
616 debug!("making tests from {}", config.src_test_suite_root);
617 let common_inputs_stamp = common_inputs_stamp(&config);
618 let modified_tests =
619 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
620 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
621 });
622 let cache = DirectivesCache::load(&config);
623
624 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
625 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
626 .unwrap_or_else(|reason| {
627 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
628 });
629
630 let TestCollector { tests, found_path_stems, poisoned } = collector;
631
632 if poisoned {
633 eprintln!();
634 panic!("there are errors in tests");
635 }
636
637 check_for_overlapping_test_paths(&found_path_stems);
638
639 tests
640}
641
642fn common_inputs_stamp(config: &Config) -> Stamp {
650 let src_root = &config.src_root;
651
652 let mut stamp = Stamp::from_path(&config.rustc_path);
653
654 let pretty_printer_files = [
656 "src/etc/rust_types.py",
657 "src/etc/gdb_load_rust_pretty_printers.py",
658 "src/etc/gdb_lookup.py",
659 "src/etc/gdb_providers.py",
660 "src/etc/lldb_batchmode.py",
661 "src/etc/lldb_lookup.py",
662 "src/etc/lldb_providers.py",
663 ];
664 for file in &pretty_printer_files {
665 let path = src_root.join(file);
666 stamp.add_path(&path);
667 }
668
669 stamp.add_dir(&src_root.join("src/etc/natvis"));
670
671 stamp.add_dir(&config.target_run_lib_path);
672
673 if let Some(ref rustdoc_path) = config.rustdoc_path {
674 stamp.add_path(&rustdoc_path);
675 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
676 }
677
678 if let Some(coverage_dump_path) = &config.coverage_dump_path {
681 stamp.add_path(coverage_dump_path)
682 }
683
684 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
685
686 stamp.add_dir(&src_root.join("src/tools/compiletest"));
688
689 stamp
690}
691
692fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
697 if !config.only_modified {
700 return Ok(vec![]);
701 }
702
703 let files = get_git_modified_files(
704 &config.git_config(),
705 Some(dir.as_std_path()),
706 &vec!["rs", "stderr", "fixed"],
707 )?;
708 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
710
711 let all_paths = [&files[..], &untracked_files[..]].concat();
712 let full_paths = {
713 let mut full_paths: Vec<Utf8PathBuf> = all_paths
714 .into_iter()
715 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
716 .filter_map(
717 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
718 )
719 .collect();
720 full_paths.dedup();
721 full_paths.sort_unstable();
722 full_paths
723 };
724 Ok(full_paths)
725}
726
727fn collect_tests_from_dir(
730 cx: &TestCollectorCx,
731 dir: &Utf8Path,
732 relative_dir_path: &Utf8Path,
733) -> io::Result<TestCollector> {
734 if dir.join("compiletest-ignore-dir").exists() {
736 return Ok(TestCollector::new());
737 }
738
739 let mut components = dir.components().rev();
740 if let Some(Utf8Component::Normal(last)) = components.next()
741 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
742 && let Some(Utf8Component::Normal(parent)) = components.next()
743 && parent == "tests"
744 && let Ok(backend) = CodegenBackend::try_from(backend)
745 && backend != cx.config.default_codegen_backend
746 {
747 warning!(
749 "Ignoring tests in `{dir}` because they don't match the configured codegen \
750 backend (`{}`)",
751 cx.config.default_codegen_backend.as_str(),
752 );
753 return Ok(TestCollector::new());
754 }
755
756 if cx.config.mode == TestMode::RunMake {
758 let mut collector = TestCollector::new();
759 if dir.join("rmake.rs").exists() {
760 let paths = TestPaths {
761 file: dir.to_path_buf(),
762 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
763 };
764 make_test(cx, &mut collector, &paths);
765 return Ok(collector);
767 }
768 }
769
770 let build_dir = output_relative_path(&cx.config, relative_dir_path);
777 fs::create_dir_all(&build_dir).unwrap();
778
779 fs::read_dir(dir.as_std_path())?
784 .par_bridge()
785 .map(|file| {
786 let mut collector = TestCollector::new();
787 let file = file?;
788 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
789 let file_name = file_path.file_name().unwrap();
790
791 if is_test(file_name)
792 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
793 {
794 debug!(%file_path, "found test file");
796
797 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
799 collector.found_path_stems.insert(rel_test_path);
800
801 let paths =
802 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
803 make_test(cx, &mut collector, &paths);
804 } else if file_path.is_dir() {
805 let relative_file_path = relative_dir_path.join(file_name);
807 if file_name != "auxiliary" {
808 debug!(%file_path, "found directory");
809 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
810 }
811 } else {
812 debug!(%file_path, "found other file/directory");
813 }
814 Ok(collector)
815 })
816 .reduce(
817 || Ok(TestCollector::new()),
818 |a, b| {
819 let mut a = a?;
820 a.merge(b?);
821 Ok(a)
822 },
823 )
824}
825
826fn is_test(file_name: &str) -> bool {
828 if !file_name.ends_with(".rs") {
829 return false;
830 }
831
832 let invalid_prefixes = &[".", "#", "~"];
834 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
835}
836
837fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
840 let test_path = if cx.config.mode == TestMode::RunMake {
844 testpaths.file.join("rmake.rs")
845 } else {
846 testpaths.file.clone()
847 };
848
849 let file_contents =
851 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
852 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
853
854 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
855 panic!("directives check failed:\n{message}");
858 }
859 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
860
861 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
868 vec![None]
869 } else {
870 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
871 };
872
873 collector.tests.extend(revisions.into_iter().map(|revision| {
876 let (test_name, filterable_path) =
878 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
879
880 let mut aux_props = AuxProps::default();
883
884 let mut desc = make_test_description(
888 &cx.config,
889 &cx.cache,
890 test_name,
891 &test_path,
892 &filterable_path,
893 &file_directives,
894 revision,
895 &mut collector.poisoned,
896 &mut aux_props,
897 );
898
899 if !desc.ignore
902 && !cx.config.force_rerun
903 && is_up_to_date(cx, testpaths, &aux_props, revision)
904 {
905 desc.ignore = true;
906 desc.ignore_message = Some("up-to-date".into());
910 }
911
912 let config = Arc::clone(&cx.config);
913 let testpaths = testpaths.clone();
914 let revision = revision.map(str::to_owned);
915
916 CollectedTest { desc, config, testpaths, revision }
917 }));
918}
919
920fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
923 output_base_dir(config, testpaths, revision).join("stamp")
924}
925
926fn files_related_to_test(
931 config: &Config,
932 testpaths: &TestPaths,
933 aux_props: &AuxProps,
934 revision: Option<&str>,
935) -> Vec<Utf8PathBuf> {
936 let mut related = vec![];
937
938 if testpaths.file.is_dir() {
939 for entry in WalkDir::new(&testpaths.file) {
941 let path = entry.unwrap().into_path();
942 if path.is_file() {
943 related.push(Utf8PathBuf::try_from(path).unwrap());
944 }
945 }
946 } else {
947 related.push(testpaths.file.clone());
948 }
949
950 for aux in aux_props.all_aux_path_strings() {
951 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
956 related.push(path);
957 }
958
959 for extension in UI_EXTENSIONS {
961 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
962 related.push(path);
963 }
964
965 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
967
968 related
969}
970
971fn is_up_to_date(
977 cx: &TestCollectorCx,
978 testpaths: &TestPaths,
979 aux_props: &AuxProps,
980 revision: Option<&str>,
981) -> bool {
982 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
983 let contents = match fs::read_to_string(&stamp_file_path) {
985 Ok(f) => f,
986 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
987 Err(_) => return false,
989 };
990 let expected_hash = runtest::compute_stamp_hash(&cx.config);
991 if contents != expected_hash {
992 return false;
995 }
996
997 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1000 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1001 inputs_stamp.add_path(&path);
1002 }
1003
1004 inputs_stamp < Stamp::from_path(&stamp_file_path)
1007}
1008
1009#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1011struct Stamp {
1012 time: SystemTime,
1013}
1014
1015impl Stamp {
1016 fn from_path(path: &Utf8Path) -> Self {
1018 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1019 stamp.add_path(path);
1020 stamp
1021 }
1022
1023 fn add_path(&mut self, path: &Utf8Path) {
1026 let modified = fs::metadata(path.as_std_path())
1027 .and_then(|metadata| metadata.modified())
1028 .unwrap_or(SystemTime::UNIX_EPOCH);
1029 self.time = self.time.max(modified);
1030 }
1031
1032 fn add_dir(&mut self, path: &Utf8Path) {
1036 let path = path.as_std_path();
1037 for entry in WalkDir::new(path) {
1038 let entry = entry.unwrap();
1039 if entry.file_type().is_file() {
1040 let modified = entry
1041 .metadata()
1042 .ok()
1043 .and_then(|metadata| metadata.modified().ok())
1044 .unwrap_or(SystemTime::UNIX_EPOCH);
1045 self.time = self.time.max(modified);
1046 }
1047 }
1048 }
1049}
1050
1051fn make_test_name_and_filterable_path(
1053 config: &Config,
1054 testpaths: &TestPaths,
1055 revision: Option<&str>,
1056) -> (String, Utf8PathBuf) {
1057 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1059 let debugger = match config.debugger {
1060 Some(d) => format!("-{}", d),
1061 None => String::new(),
1062 };
1063 let mode_suffix = match config.compare_mode {
1064 Some(ref mode) => format!(" ({})", mode.to_str()),
1065 None => String::new(),
1066 };
1067
1068 let name = format!(
1069 "[{}{}{}] {}{}",
1070 config.mode,
1071 debugger,
1072 mode_suffix,
1073 path,
1074 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1075 );
1076
1077 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1081 filterable_path = filterable_path.components().skip(1).collect();
1083
1084 (name, filterable_path)
1085}
1086
1087fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1105 let mut collisions = Vec::new();
1106 for path in found_path_stems {
1107 for ancestor in path.ancestors().skip(1) {
1108 if found_path_stems.contains(ancestor) {
1109 collisions.push((path, ancestor));
1110 }
1111 }
1112 }
1113 if !collisions.is_empty() {
1114 collisions.sort();
1115 let collisions: String = collisions
1116 .into_iter()
1117 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1118 .collect();
1119 panic!(
1120 "{collisions}\n\
1121 Tests cannot have overlapping names. Make sure they use unique prefixes."
1122 );
1123 }
1124}
1125
1126fn early_config_check(config: &Config) {
1127 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1128 let actioned = if config.bless { "blessed" } else { "checked" };
1129 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1130 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1131 }
1132
1133 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1135 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1136 }
1137}