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::process::{Command, Stdio};
28use std::sync::{Arc, OnceLock};
29use std::time::SystemTime;
30use std::{env, fs, vec};
31
32use build_helper::git::{get_git_modified_files, get_git_untracked_files};
33use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
34use getopts::Options;
35use rayon::iter::{ParallelBridge, ParallelIterator};
36use tracing::debug;
37use walkdir::WalkDir;
38
39use self::directives::{EarlyProps, make_test_description};
40use crate::common::{
41 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
42 expected_output_path, output_base_dir, output_relative_path,
43};
44use crate::directives::{DirectivesCache, FileDirectives};
45use crate::edition::parse_edition;
46use crate::executor::{CollectedTest, ColorConfig};
47
48fn parse_config(args: Vec<String>) -> Config {
54 let mut opts = Options::new();
55 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
56 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
57 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
58 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
59 .optopt(
60 "",
61 "stage0-rustc-path",
62 "path to rustc to use for compiling run-make recipes",
63 "PATH",
64 )
65 .optopt(
66 "",
67 "query-rustc-path",
68 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
69 "PATH",
70 )
71 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
72 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
73 .reqopt("", "python", "path to python to use for doc tests", "PATH")
74 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
75 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
76 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
77 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
78 .reqopt("", "src-root", "directory containing sources", "PATH")
79 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
80 .reqopt("", "build-root", "path to root build directory", "PATH")
81 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
82 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
83 .reqopt("", "stage", "stage number under test", "N")
84 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
85 .reqopt(
86 "",
87 "mode",
88 "which sort of compile tests to run",
89 "pretty | debug-info | codegen | rustdoc \
90 | rustdoc-json | codegen-units | incremental | run-make | ui \
91 | rustdoc-js | mir-opt | assembly | crashes",
92 )
93 .reqopt(
94 "",
95 "suite",
96 "which suite of compile tests to run. used for nicer error reporting.",
97 "SUITE",
98 )
99 .optopt(
100 "",
101 "pass",
102 "force {check,build,run}-pass tests to this mode.",
103 "check | build | run",
104 )
105 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
106 .optflag("", "ignored", "run tests marked as ignored")
107 .optflag("", "has-enzyme", "run tests that require enzyme")
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 .optmulti(
111 "",
112 "skip",
113 "skip tests matching SUBSTRING. Can be passed multiple times",
114 "SUBSTRING",
115 )
116 .optflag("", "exact", "filters match exactly")
117 .optopt(
118 "",
119 "runner",
120 "supervisor program to run tests under \
121 (eg. emulator, valgrind)",
122 "PROGRAM",
123 )
124 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
125 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
126 .optflag(
127 "",
128 "rust-randomized-layout",
129 "set this when rustc/stdlib were compiled with randomized layouts",
130 )
131 .optflag("", "optimize-tests", "run tests with optimizations enabled")
132 .optflag("", "verbose", "run tests verbosely, showing all output")
133 .optflag(
134 "",
135 "bless",
136 "overwrite stderr/stdout files instead of complaining about a mismatch",
137 )
138 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
139 .optopt("", "color", "coloring: auto, always, never", "WHEN")
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-version", "the version of LLDB used", "VERSION STRING")
145 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
146 .optflag("", "system-llvm", "is LLVM the system LLVM")
147 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
148 .optopt("", "adb-path", "path to the android debugger", "PATH")
149 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
150 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "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
222 let (argv0, args_) = args.split_first().unwrap();
223 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
224 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
225 println!("{}", opts.usage(&message));
226 println!();
227 panic!()
228 }
229
230 let matches = &match opts.parse(args_) {
231 Ok(m) => m,
232 Err(f) => panic!("{:?}", f),
233 };
234
235 if matches.opt_present("h") || matches.opt_present("help") {
236 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
237 println!("{}", opts.usage(&message));
238 println!();
239 panic!()
240 }
241
242 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
243 if path.is_relative() {
244 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
245 } else {
246 path
247 }
248 }
249
250 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
251 match m.opt_str(nm) {
252 Some(s) => Utf8PathBuf::from(&s),
253 None => panic!("no option (=path) found for {}", nm),
254 }
255 }
256
257 let target = opt_str2(matches.opt_str("target"));
258 let android_cross_path = opt_path(matches, "android-cross-path");
259 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
261 let (gdb, gdb_version) =
263 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
264 let lldb_version =
266 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
267 let color = match matches.opt_str("color").as_deref() {
268 Some("auto") | None => ColorConfig::AutoColor,
269 Some("always") => ColorConfig::AlwaysColor,
270 Some("never") => ColorConfig::NeverColor,
271 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
272 };
273 let llvm_version =
277 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
278 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
279 );
280
281 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
282 Some(backend) => match CodegenBackend::try_from(backend) {
283 Ok(backend) => backend,
284 Err(error) => {
285 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
286 }
287 },
288 None => CodegenBackend::Llvm,
290 };
291 let override_codegen_backend = matches.opt_str("override-codegen-backend");
292
293 let run_ignored = matches.opt_present("ignored");
294 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
295 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
296 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
297 let has_html_tidy = if mode == TestMode::Rustdoc {
298 Command::new("tidy")
299 .arg("--version")
300 .stdout(Stdio::null())
301 .status()
302 .map_or(false, |status| status.success())
303 } else {
304 false
306 };
307 let has_enzyme = matches.opt_present("has-enzyme");
308 let filters = if mode == TestMode::RunMake {
309 matches
310 .free
311 .iter()
312 .map(|f| {
313 let path = Utf8Path::new(f);
319 let mut iter = path.iter().skip(1);
320
321 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
322 path.parent().unwrap().to_string()
325 } else {
326 f.to_string()
327 }
328 })
329 .collect::<Vec<_>>()
330 } else {
331 matches.free.clone()
338 };
339 let compare_mode = matches.opt_str("compare-mode").map(|s| {
340 s.parse().unwrap_or_else(|_| {
341 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
342 panic!(
343 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
344 variants.join(", ")
345 );
346 })
347 });
348 if matches.opt_present("nocapture") {
349 panic!("`--nocapture` is deprecated; please use `--no-capture`");
350 }
351
352 let stage = match matches.opt_str("stage") {
353 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
354 None => panic!("`--stage` is required"),
355 };
356
357 let src_root = opt_path(matches, "src-root");
358 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
359 assert!(
360 src_test_suite_root.starts_with(&src_root),
361 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
362 src_root,
363 src_test_suite_root
364 );
365
366 let build_root = opt_path(matches, "build-root");
367 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
368 assert!(build_test_suite_root.starts_with(&build_root));
369
370 Config {
371 bless: matches.opt_present("bless"),
372 fail_fast: matches.opt_present("fail-fast")
373 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
374
375 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
376 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
377 rustc_path: opt_path(matches, "rustc-path"),
378 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
379 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
380 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
381 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
382 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
383 python: matches.opt_str("python").unwrap(),
384 jsondocck_path: matches.opt_str("jsondocck-path"),
385 jsondoclint_path: matches.opt_str("jsondoclint-path"),
386 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
387 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
388 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
389
390 src_root,
391 src_test_suite_root,
392
393 build_root,
394 build_test_suite_root,
395
396 sysroot_base: opt_path(matches, "sysroot-base"),
397
398 stage,
399 stage_id: matches.opt_str("stage-id").unwrap(),
400
401 mode,
402 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
403 debugger: matches.opt_str("debugger").map(|debugger| {
404 debugger
405 .parse::<Debugger>()
406 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
407 }),
408 run_ignored,
409 with_rustc_debug_assertions,
410 with_std_debug_assertions,
411 filters,
412 skip: matches.opt_strs("skip"),
413 filter_exact: matches.opt_present("exact"),
414 force_pass_mode: matches.opt_str("pass").map(|mode| {
415 mode.parse::<PassMode>()
416 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
417 }),
418 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
420 "auto" => None,
421 "always" => Some(true),
422 "never" => Some(false),
423 _ => panic!("unknown `--run` option `{}` given", mode),
424 }),
425 runner: matches.opt_str("runner"),
426 host_rustcflags: matches.opt_strs("host-rustcflags"),
427 target_rustcflags: matches.opt_strs("target-rustcflags"),
428 optimize_tests: matches.opt_present("optimize-tests"),
429 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
430 target,
431 host: opt_str2(matches.opt_str("host")),
432 cdb,
433 cdb_version,
434 gdb,
435 gdb_version,
436 lldb_version,
437 llvm_version,
438 system_llvm: matches.opt_present("system-llvm"),
439 android_cross_path,
440 adb_path: opt_str2(matches.opt_str("adb-path")),
441 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
442 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
443 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
444 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
445 lldb_python_dir: matches.opt_str("lldb-python-dir"),
446 verbose: matches.opt_present("verbose"),
447 only_modified: matches.opt_present("only-modified"),
448 color,
449 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
450 compare_mode,
451 rustfix_coverage: matches.opt_present("rustfix-coverage"),
452 has_html_tidy,
453 has_enzyme,
454 channel: matches.opt_str("channel").unwrap(),
455 git_hash: matches.opt_present("git-hash"),
456 edition: matches.opt_str("edition").as_deref().map(parse_edition),
457
458 cc: matches.opt_str("cc").unwrap(),
459 cxx: matches.opt_str("cxx").unwrap(),
460 cflags: matches.opt_str("cflags").unwrap(),
461 cxxflags: matches.opt_str("cxxflags").unwrap(),
462 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
463 target_linker: matches.opt_str("target-linker"),
464 host_linker: matches.opt_str("host-linker"),
465 llvm_components: matches.opt_str("llvm-components").unwrap(),
466 nodejs: matches.opt_str("nodejs"),
467
468 force_rerun: matches.opt_present("force-rerun"),
469
470 target_cfgs: OnceLock::new(),
471 builtin_cfg_names: OnceLock::new(),
472 supported_crate_types: OnceLock::new(),
473
474 nocapture: matches.opt_present("no-capture"),
475
476 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
477 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
478
479 profiler_runtime: matches.opt_present("profiler-runtime"),
480
481 diff_command: matches.opt_str("compiletest-diff-tool"),
482
483 minicore_path: opt_path(matches, "minicore-path"),
484
485 default_codegen_backend,
486 override_codegen_backend,
487 }
488}
489
490fn opt_str2(maybestr: Option<String>) -> String {
491 match maybestr {
492 None => "(none)".to_owned(),
493 Some(s) => s,
494 }
495}
496
497fn run_tests(config: Arc<Config>) {
499 debug!(?config, "run_tests");
500
501 panic_hook::install_panic_hook();
502
503 if config.rustfix_coverage {
507 let mut coverage_file_path = config.build_test_suite_root.clone();
508 coverage_file_path.push("rustfix_missing_coverage.txt");
509 if coverage_file_path.exists() {
510 if let Err(e) = fs::remove_file(&coverage_file_path) {
511 panic!("Could not delete {} due to {}", coverage_file_path, e)
512 }
513 }
514 }
515
516 unsafe {
520 raise_fd_limit::raise_fd_limit();
521 }
522 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
527
528 unsafe { env::set_var("TARGET", &config.target) };
532
533 let mut configs = Vec::new();
534 if let TestMode::DebugInfo = config.mode {
535 if !config.target.contains("emscripten") {
537 match config.debugger {
538 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
539 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
540 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
541 None => {
546 configs.extend(debuggers::configure_cdb(&config));
547 configs.extend(debuggers::configure_gdb(&config));
548 configs.extend(debuggers::configure_lldb(&config));
549 }
550 }
551 }
552 } else {
553 configs.push(config.clone());
554 };
555
556 let mut tests = Vec::new();
559 for c in configs {
560 tests.extend(collect_and_make_tests(c));
561 }
562
563 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
564
565 let ok = executor::run_tests(&config, tests);
569
570 if !ok {
572 let mut msg = String::from("Some tests failed in compiletest");
580 write!(msg, " suite={}", config.suite).unwrap();
581
582 if let Some(compare_mode) = config.compare_mode.as_ref() {
583 write!(msg, " compare_mode={}", compare_mode).unwrap();
584 }
585
586 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
587 write!(msg, " pass_mode={}", pass_mode).unwrap();
588 }
589
590 write!(msg, " mode={}", config.mode).unwrap();
591 write!(msg, " host={}", config.host).unwrap();
592 write!(msg, " target={}", config.target).unwrap();
593
594 println!("{msg}");
595
596 std::process::exit(1);
597 }
598}
599
600struct TestCollectorCx {
602 config: Arc<Config>,
603 cache: DirectivesCache,
604 common_inputs_stamp: Stamp,
605 modified_tests: Vec<Utf8PathBuf>,
606}
607
608struct TestCollector {
610 tests: Vec<CollectedTest>,
611 found_path_stems: HashSet<Utf8PathBuf>,
612 poisoned: bool,
613}
614
615impl TestCollector {
616 fn new() -> Self {
617 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
618 }
619
620 fn merge(&mut self, mut other: Self) {
621 self.tests.append(&mut other.tests);
622 self.found_path_stems.extend(other.found_path_stems);
623 self.poisoned |= other.poisoned;
624 }
625}
626
627fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
637 debug!("making tests from {}", config.src_test_suite_root);
638 let common_inputs_stamp = common_inputs_stamp(&config);
639 let modified_tests =
640 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
641 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
642 });
643 let cache = DirectivesCache::load(&config);
644
645 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
646 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
647 .unwrap_or_else(|reason| {
648 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
649 });
650
651 let TestCollector { tests, found_path_stems, poisoned } = collector;
652
653 if poisoned {
654 eprintln!();
655 panic!("there are errors in tests");
656 }
657
658 check_for_overlapping_test_paths(&found_path_stems);
659
660 tests
661}
662
663fn common_inputs_stamp(config: &Config) -> Stamp {
671 let src_root = &config.src_root;
672
673 let mut stamp = Stamp::from_path(&config.rustc_path);
674
675 let pretty_printer_files = [
677 "src/etc/rust_types.py",
678 "src/etc/gdb_load_rust_pretty_printers.py",
679 "src/etc/gdb_lookup.py",
680 "src/etc/gdb_providers.py",
681 "src/etc/lldb_batchmode.py",
682 "src/etc/lldb_lookup.py",
683 "src/etc/lldb_providers.py",
684 ];
685 for file in &pretty_printer_files {
686 let path = src_root.join(file);
687 stamp.add_path(&path);
688 }
689
690 stamp.add_dir(&src_root.join("src/etc/natvis"));
691
692 stamp.add_dir(&config.run_lib_path);
693
694 if let Some(ref rustdoc_path) = config.rustdoc_path {
695 stamp.add_path(&rustdoc_path);
696 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
697 }
698
699 if let Some(coverage_dump_path) = &config.coverage_dump_path {
702 stamp.add_path(coverage_dump_path)
703 }
704
705 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
706
707 stamp.add_dir(&src_root.join("src/tools/compiletest"));
709
710 stamp
711}
712
713fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
718 if !config.only_modified {
721 return Ok(vec![]);
722 }
723
724 let files = get_git_modified_files(
725 &config.git_config(),
726 Some(dir.as_std_path()),
727 &vec!["rs", "stderr", "fixed"],
728 )?;
729 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
731
732 let all_paths = [&files[..], &untracked_files[..]].concat();
733 let full_paths = {
734 let mut full_paths: Vec<Utf8PathBuf> = all_paths
735 .into_iter()
736 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
737 .filter_map(
738 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
739 )
740 .collect();
741 full_paths.dedup();
742 full_paths.sort_unstable();
743 full_paths
744 };
745 Ok(full_paths)
746}
747
748fn collect_tests_from_dir(
751 cx: &TestCollectorCx,
752 dir: &Utf8Path,
753 relative_dir_path: &Utf8Path,
754) -> io::Result<TestCollector> {
755 if dir.join("compiletest-ignore-dir").exists() {
757 return Ok(TestCollector::new());
758 }
759
760 let mut components = dir.components().rev();
761 if let Some(Utf8Component::Normal(last)) = components.next()
762 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
763 && let Some(Utf8Component::Normal(parent)) = components.next()
764 && parent == "tests"
765 && let Ok(backend) = CodegenBackend::try_from(backend)
766 && backend != cx.config.default_codegen_backend
767 {
768 warning!(
770 "Ignoring tests in `{dir}` because they don't match the configured codegen \
771 backend (`{}`)",
772 cx.config.default_codegen_backend.as_str(),
773 );
774 return Ok(TestCollector::new());
775 }
776
777 if cx.config.mode == TestMode::RunMake {
779 let mut collector = TestCollector::new();
780 if dir.join("rmake.rs").exists() {
781 let paths = TestPaths {
782 file: dir.to_path_buf(),
783 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
784 };
785 make_test(cx, &mut collector, &paths);
786 return Ok(collector);
788 }
789 }
790
791 let build_dir = output_relative_path(&cx.config, relative_dir_path);
798 fs::create_dir_all(&build_dir).unwrap();
799
800 fs::read_dir(dir.as_std_path())?
805 .par_bridge()
806 .map(|file| {
807 let mut collector = TestCollector::new();
808 let file = file?;
809 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
810 let file_name = file_path.file_name().unwrap();
811
812 if is_test(file_name)
813 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
814 {
815 debug!(%file_path, "found test file");
817
818 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
820 collector.found_path_stems.insert(rel_test_path);
821
822 let paths =
823 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
824 make_test(cx, &mut collector, &paths);
825 } else if file_path.is_dir() {
826 let relative_file_path = relative_dir_path.join(file_name);
828 if file_name != "auxiliary" {
829 debug!(%file_path, "found directory");
830 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
831 }
832 } else {
833 debug!(%file_path, "found other file/directory");
834 }
835 Ok(collector)
836 })
837 .reduce(
838 || Ok(TestCollector::new()),
839 |a, b| {
840 let mut a = a?;
841 a.merge(b?);
842 Ok(a)
843 },
844 )
845}
846
847fn is_test(file_name: &str) -> bool {
849 if !file_name.ends_with(".rs") {
850 return false;
851 }
852
853 let invalid_prefixes = &[".", "#", "~"];
855 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
856}
857
858fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
861 let test_path = if cx.config.mode == TestMode::RunMake {
865 testpaths.file.join("rmake.rs")
866 } else {
867 testpaths.file.clone()
868 };
869
870 let file_contents =
872 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
873 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
874 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
875
876 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
883 vec![None]
884 } else {
885 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
886 };
887
888 collector.tests.extend(revisions.into_iter().map(|revision| {
891 let (test_name, filterable_path) =
893 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
894 let mut desc = make_test_description(
898 &cx.config,
899 &cx.cache,
900 test_name,
901 &test_path,
902 &filterable_path,
903 &file_directives,
904 revision,
905 &mut collector.poisoned,
906 );
907
908 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
911 desc.ignore = true;
912 desc.ignore_message = Some("up-to-date".into());
916 }
917
918 let config = Arc::clone(&cx.config);
919 let testpaths = testpaths.clone();
920 let revision = revision.map(str::to_owned);
921
922 CollectedTest { desc, config, testpaths, revision }
923 }));
924}
925
926fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
929 output_base_dir(config, testpaths, revision).join("stamp")
930}
931
932fn files_related_to_test(
937 config: &Config,
938 testpaths: &TestPaths,
939 props: &EarlyProps,
940 revision: Option<&str>,
941) -> Vec<Utf8PathBuf> {
942 let mut related = vec![];
943
944 if testpaths.file.is_dir() {
945 for entry in WalkDir::new(&testpaths.file) {
947 let path = entry.unwrap().into_path();
948 if path.is_file() {
949 related.push(Utf8PathBuf::try_from(path).unwrap());
950 }
951 }
952 } else {
953 related.push(testpaths.file.clone());
954 }
955
956 for aux in props.aux.all_aux_path_strings() {
957 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
959 related.push(path);
960 }
961
962 for extension in UI_EXTENSIONS {
964 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
965 related.push(path);
966 }
967
968 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
970
971 related
972}
973
974fn is_up_to_date(
980 cx: &TestCollectorCx,
981 testpaths: &TestPaths,
982 props: &EarlyProps,
983 revision: Option<&str>,
984) -> bool {
985 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
986 let contents = match fs::read_to_string(&stamp_file_path) {
988 Ok(f) => f,
989 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
990 Err(_) => return false,
992 };
993 let expected_hash = runtest::compute_stamp_hash(&cx.config);
994 if contents != expected_hash {
995 return false;
998 }
999
1000 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1003 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1004 inputs_stamp.add_path(&path);
1005 }
1006
1007 inputs_stamp < Stamp::from_path(&stamp_file_path)
1010}
1011
1012#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1014struct Stamp {
1015 time: SystemTime,
1016}
1017
1018impl Stamp {
1019 fn from_path(path: &Utf8Path) -> Self {
1021 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1022 stamp.add_path(path);
1023 stamp
1024 }
1025
1026 fn add_path(&mut self, path: &Utf8Path) {
1029 let modified = fs::metadata(path.as_std_path())
1030 .and_then(|metadata| metadata.modified())
1031 .unwrap_or(SystemTime::UNIX_EPOCH);
1032 self.time = self.time.max(modified);
1033 }
1034
1035 fn add_dir(&mut self, path: &Utf8Path) {
1039 let path = path.as_std_path();
1040 for entry in WalkDir::new(path) {
1041 let entry = entry.unwrap();
1042 if entry.file_type().is_file() {
1043 let modified = entry
1044 .metadata()
1045 .ok()
1046 .and_then(|metadata| metadata.modified().ok())
1047 .unwrap_or(SystemTime::UNIX_EPOCH);
1048 self.time = self.time.max(modified);
1049 }
1050 }
1051 }
1052}
1053
1054fn make_test_name_and_filterable_path(
1056 config: &Config,
1057 testpaths: &TestPaths,
1058 revision: Option<&str>,
1059) -> (String, Utf8PathBuf) {
1060 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1062 let debugger = match config.debugger {
1063 Some(d) => format!("-{}", d),
1064 None => String::new(),
1065 };
1066 let mode_suffix = match config.compare_mode {
1067 Some(ref mode) => format!(" ({})", mode.to_str()),
1068 None => String::new(),
1069 };
1070
1071 let name = format!(
1072 "[{}{}{}] {}{}",
1073 config.mode,
1074 debugger,
1075 mode_suffix,
1076 path,
1077 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1078 );
1079
1080 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1084 filterable_path = filterable_path.components().skip(1).collect();
1086
1087 (name, filterable_path)
1088}
1089
1090fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1108 let mut collisions = Vec::new();
1109 for path in found_path_stems {
1110 for ancestor in path.ancestors().skip(1) {
1111 if found_path_stems.contains(ancestor) {
1112 collisions.push((path, ancestor));
1113 }
1114 }
1115 }
1116 if !collisions.is_empty() {
1117 collisions.sort();
1118 let collisions: String = collisions
1119 .into_iter()
1120 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1121 .collect();
1122 panic!(
1123 "{collisions}\n\
1124 Tests cannot have overlapping names. Make sure they use unique prefixes."
1125 );
1126 }
1127}
1128
1129fn early_config_check(config: &Config) {
1130 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1131 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1132 }
1133
1134 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1135 let actioned = if config.bless { "blessed" } else { "checked" };
1136 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1137 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1138 }
1139
1140 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1142 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1143 }
1144}