1#![crate_name = "compiletest"]
2#![feature(internal_output_capture)]
7
8#[cfg(test)]
9mod tests;
10
11pub mod common;
12mod debuggers;
13pub mod diagnostics;
14pub mod directives;
15pub mod errors;
16mod executor;
17mod json;
18mod raise_fd_limit;
19mod read2;
20pub mod runtest;
21pub mod 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;
45use crate::executor::{CollectedTest, ColorConfig};
46
47pub fn 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("", "color", "coloring: auto, always, never", "WHEN")
139 .optopt("", "target", "the target to build for", "TARGET")
140 .optopt("", "host", "the host to build for", "HOST")
141 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
142 .optopt("", "gdb", "path to GDB to use for GDB 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 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
150 .reqopt("", "cc", "path to a C compiler", "PATH")
151 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
152 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
153 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
154 .optopt("", "ar", "path to an archiver", "PATH")
155 .optopt("", "target-linker", "path to a linker for the target", "PATH")
156 .optopt("", "host-linker", "path to a linker for the host", "PATH")
157 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
158 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
159 .optopt("", "nodejs", "the name of nodejs", "PATH")
160 .optopt("", "npm", "the name of npm", "PATH")
161 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
162 .optopt(
163 "",
164 "compare-mode",
165 "mode describing what file the actual ui output will be compared to",
166 "COMPARE MODE",
167 )
168 .optflag(
169 "",
170 "rustfix-coverage",
171 "enable this to generate a Rustfix coverage file, which is saved in \
172 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
173 )
174 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
175 .optflag("", "only-modified", "only run tests that result been modified")
176 .optflag("", "nocapture", "")
178 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
179 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
180 .optflag("h", "help", "show this message")
181 .reqopt("", "channel", "current Rust channel", "CHANNEL")
182 .optflag(
183 "",
184 "git-hash",
185 "run tests which rely on commit version being compiled into the binaries",
186 )
187 .optopt("", "edition", "default Rust edition", "EDITION")
188 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
189 .reqopt(
190 "",
191 "git-merge-commit-email",
192 "email address used for finding merge commits",
193 "EMAIL",
194 )
195 .optopt(
196 "",
197 "compiletest-diff-tool",
198 "What custom diff tool to use for displaying compiletest tests.",
199 "COMMAND",
200 )
201 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
202 .optopt(
203 "",
204 "debugger",
205 "only test a specific debugger in debuginfo tests",
206 "gdb | lldb | cdb",
207 )
208 .optopt(
209 "",
210 "default-codegen-backend",
211 "the codegen backend currently used",
212 "CODEGEN BACKEND NAME",
213 )
214 .optopt(
215 "",
216 "override-codegen-backend",
217 "the codegen backend to use instead of the default one",
218 "CODEGEN BACKEND [NAME | PATH]",
219 );
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 target = opt_str2(matches.opt_str("target"));
257 let android_cross_path = opt_path(matches, "android-cross-path");
258 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
260 let (gdb, gdb_version) =
262 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
263 let lldb_version =
265 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
266 let color = match matches.opt_str("color").as_deref() {
267 Some("auto") | None => ColorConfig::AutoColor,
268 Some("always") => ColorConfig::AlwaysColor,
269 Some("never") => ColorConfig::NeverColor,
270 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
271 };
272 let llvm_version =
276 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
277 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
278 );
279
280 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
281 Some(backend) => match CodegenBackend::try_from(backend) {
282 Ok(backend) => backend,
283 Err(error) => {
284 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
285 }
286 },
287 None => CodegenBackend::Llvm,
289 };
290 let override_codegen_backend = matches.opt_str("override-codegen-backend");
291
292 let run_ignored = matches.opt_present("ignored");
293 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
294 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
295 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
296 let has_html_tidy = if mode == TestMode::Rustdoc {
297 Command::new("tidy")
298 .arg("--version")
299 .stdout(Stdio::null())
300 .status()
301 .map_or(false, |status| status.success())
302 } else {
303 false
305 };
306 let has_enzyme = matches.opt_present("has-enzyme");
307 let filters = if mode == TestMode::RunMake {
308 matches
309 .free
310 .iter()
311 .map(|f| {
312 let path = Utf8Path::new(f);
313 let mut iter = path.iter().skip(1);
314
315 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
317 path.parent().unwrap().to_string()
318 } else {
319 f.to_string()
320 }
321 })
322 .collect::<Vec<_>>()
323 } else {
324 matches.free.clone()
325 };
326 let compare_mode = matches.opt_str("compare-mode").map(|s| {
327 s.parse().unwrap_or_else(|_| {
328 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
329 panic!(
330 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
331 variants.join(", ")
332 );
333 })
334 });
335 if matches.opt_present("nocapture") {
336 panic!("`--nocapture` is deprecated; please use `--no-capture`");
337 }
338
339 let stage = match matches.opt_str("stage") {
340 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
341 None => panic!("`--stage` is required"),
342 };
343
344 let src_root = opt_path(matches, "src-root");
345 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
346 assert!(
347 src_test_suite_root.starts_with(&src_root),
348 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
349 src_root,
350 src_test_suite_root
351 );
352
353 let build_root = opt_path(matches, "build-root");
354 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
355 assert!(build_test_suite_root.starts_with(&build_root));
356
357 Config {
358 bless: matches.opt_present("bless"),
359 fail_fast: matches.opt_present("fail-fast")
360 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
361
362 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
363 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
364 rustc_path: opt_path(matches, "rustc-path"),
365 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
366 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
367 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
368 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
369 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
370 python: matches.opt_str("python").unwrap(),
371 jsondocck_path: matches.opt_str("jsondocck-path"),
372 jsondoclint_path: matches.opt_str("jsondoclint-path"),
373 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
374 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
375 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
376
377 src_root,
378 src_test_suite_root,
379
380 build_root,
381 build_test_suite_root,
382
383 sysroot_base: opt_path(matches, "sysroot-base"),
384
385 stage,
386 stage_id: matches.opt_str("stage-id").unwrap(),
387
388 mode,
389 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
390 debugger: matches.opt_str("debugger").map(|debugger| {
391 debugger
392 .parse::<Debugger>()
393 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
394 }),
395 run_ignored,
396 with_rustc_debug_assertions,
397 with_std_debug_assertions,
398 filters,
399 skip: matches.opt_strs("skip"),
400 filter_exact: matches.opt_present("exact"),
401 force_pass_mode: matches.opt_str("pass").map(|mode| {
402 mode.parse::<PassMode>()
403 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
404 }),
405 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
407 "auto" => None,
408 "always" => Some(true),
409 "never" => Some(false),
410 _ => panic!("unknown `--run` option `{}` given", mode),
411 }),
412 runner: matches.opt_str("runner"),
413 host_rustcflags: matches.opt_strs("host-rustcflags"),
414 target_rustcflags: matches.opt_strs("target-rustcflags"),
415 optimize_tests: matches.opt_present("optimize-tests"),
416 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
417 target,
418 host: opt_str2(matches.opt_str("host")),
419 cdb,
420 cdb_version,
421 gdb,
422 gdb_version,
423 lldb_version,
424 llvm_version,
425 system_llvm: matches.opt_present("system-llvm"),
426 android_cross_path,
427 adb_path: opt_str2(matches.opt_str("adb-path")),
428 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
429 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
430 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
431 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
432 lldb_python_dir: matches.opt_str("lldb-python-dir"),
433 verbose: matches.opt_present("verbose"),
434 only_modified: matches.opt_present("only-modified"),
435 color,
436 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
437 compare_mode,
438 rustfix_coverage: matches.opt_present("rustfix-coverage"),
439 has_html_tidy,
440 has_enzyme,
441 channel: matches.opt_str("channel").unwrap(),
442 git_hash: matches.opt_present("git-hash"),
443 edition: matches.opt_str("edition"),
444
445 cc: matches.opt_str("cc").unwrap(),
446 cxx: matches.opt_str("cxx").unwrap(),
447 cflags: matches.opt_str("cflags").unwrap(),
448 cxxflags: matches.opt_str("cxxflags").unwrap(),
449 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
450 target_linker: matches.opt_str("target-linker"),
451 host_linker: matches.opt_str("host-linker"),
452 llvm_components: matches.opt_str("llvm-components").unwrap(),
453 nodejs: matches.opt_str("nodejs"),
454 npm: matches.opt_str("npm"),
455
456 force_rerun: matches.opt_present("force-rerun"),
457
458 target_cfgs: OnceLock::new(),
459 builtin_cfg_names: OnceLock::new(),
460 supported_crate_types: OnceLock::new(),
461
462 nocapture: matches.opt_present("no-capture"),
463
464 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
465 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
466
467 profiler_runtime: matches.opt_present("profiler-runtime"),
468
469 diff_command: matches.opt_str("compiletest-diff-tool"),
470
471 minicore_path: opt_path(matches, "minicore-path"),
472
473 default_codegen_backend,
474 override_codegen_backend,
475 }
476}
477
478pub fn opt_str(maybestr: &Option<String>) -> &str {
479 match *maybestr {
480 None => "(none)",
481 Some(ref s) => s,
482 }
483}
484
485pub fn opt_str2(maybestr: Option<String>) -> String {
486 match maybestr {
487 None => "(none)".to_owned(),
488 Some(s) => s,
489 }
490}
491
492pub fn run_tests(config: Arc<Config>) {
494 debug!(?config, "run_tests");
495
496 if config.rustfix_coverage {
500 let mut coverage_file_path = config.build_test_suite_root.clone();
501 coverage_file_path.push("rustfix_missing_coverage.txt");
502 if coverage_file_path.exists() {
503 if let Err(e) = fs::remove_file(&coverage_file_path) {
504 panic!("Could not delete {} due to {}", coverage_file_path, e)
505 }
506 }
507 }
508
509 unsafe {
513 raise_fd_limit::raise_fd_limit();
514 }
515 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
520
521 unsafe { env::set_var("TARGET", &config.target) };
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
620pub(crate) fn 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.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
840pub fn 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 early_props = EarlyProps::from_file(&cx.config, &test_path);
865
866 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
873 vec![None]
874 } else {
875 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
876 };
877
878 collector.tests.extend(revisions.into_iter().map(|revision| {
881 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
883 let test_name = make_test_name(&cx.config, testpaths, revision);
884 let mut desc = make_test_description(
888 &cx.config,
889 &cx.cache,
890 test_name,
891 &test_path,
892 src_file,
893 revision,
894 &mut collector.poisoned,
895 );
896
897 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
900 desc.ignore = true;
901 desc.ignore_message = Some("up-to-date".into());
905 }
906
907 let config = Arc::clone(&cx.config);
908 let testpaths = testpaths.clone();
909 let revision = revision.map(str::to_owned);
910
911 CollectedTest { desc, config, testpaths, revision }
912 }));
913}
914
915fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
918 output_base_dir(config, testpaths, revision).join("stamp")
919}
920
921fn files_related_to_test(
926 config: &Config,
927 testpaths: &TestPaths,
928 props: &EarlyProps,
929 revision: Option<&str>,
930) -> Vec<Utf8PathBuf> {
931 let mut related = vec![];
932
933 if testpaths.file.is_dir() {
934 for entry in WalkDir::new(&testpaths.file) {
936 let path = entry.unwrap().into_path();
937 if path.is_file() {
938 related.push(Utf8PathBuf::try_from(path).unwrap());
939 }
940 }
941 } else {
942 related.push(testpaths.file.clone());
943 }
944
945 for aux in props.aux.all_aux_path_strings() {
946 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
948 related.push(path);
949 }
950
951 for extension in UI_EXTENSIONS {
953 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
954 related.push(path);
955 }
956
957 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
959
960 related
961}
962
963fn is_up_to_date(
969 cx: &TestCollectorCx,
970 testpaths: &TestPaths,
971 props: &EarlyProps,
972 revision: Option<&str>,
973) -> bool {
974 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
975 let contents = match fs::read_to_string(&stamp_file_path) {
977 Ok(f) => f,
978 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
979 Err(_) => return false,
981 };
982 let expected_hash = runtest::compute_stamp_hash(&cx.config);
983 if contents != expected_hash {
984 return false;
987 }
988
989 let mut inputs_stamp = cx.common_inputs_stamp.clone();
992 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
993 inputs_stamp.add_path(&path);
994 }
995
996 inputs_stamp < Stamp::from_path(&stamp_file_path)
999}
1000
1001#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1003struct Stamp {
1004 time: SystemTime,
1005}
1006
1007impl Stamp {
1008 fn from_path(path: &Utf8Path) -> Self {
1010 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1011 stamp.add_path(path);
1012 stamp
1013 }
1014
1015 fn add_path(&mut self, path: &Utf8Path) {
1018 let modified = fs::metadata(path.as_std_path())
1019 .and_then(|metadata| metadata.modified())
1020 .unwrap_or(SystemTime::UNIX_EPOCH);
1021 self.time = self.time.max(modified);
1022 }
1023
1024 fn add_dir(&mut self, path: &Utf8Path) {
1028 let path = path.as_std_path();
1029 for entry in WalkDir::new(path) {
1030 let entry = entry.unwrap();
1031 if entry.file_type().is_file() {
1032 let modified = entry
1033 .metadata()
1034 .ok()
1035 .and_then(|metadata| metadata.modified().ok())
1036 .unwrap_or(SystemTime::UNIX_EPOCH);
1037 self.time = self.time.max(modified);
1038 }
1039 }
1040 }
1041}
1042
1043fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1045 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1047 let debugger = match config.debugger {
1048 Some(d) => format!("-{}", d),
1049 None => String::new(),
1050 };
1051 let mode_suffix = match config.compare_mode {
1052 Some(ref mode) => format!(" ({})", mode.to_str()),
1053 None => String::new(),
1054 };
1055
1056 format!(
1057 "[{}{}{}] {}{}",
1058 config.mode,
1059 debugger,
1060 mode_suffix,
1061 path,
1062 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1063 )
1064}
1065
1066fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1084 let mut collisions = Vec::new();
1085 for path in found_path_stems {
1086 for ancestor in path.ancestors().skip(1) {
1087 if found_path_stems.contains(ancestor) {
1088 collisions.push((path, ancestor));
1089 }
1090 }
1091 }
1092 if !collisions.is_empty() {
1093 collisions.sort();
1094 let collisions: String = collisions
1095 .into_iter()
1096 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1097 .collect();
1098 panic!(
1099 "{collisions}\n\
1100 Tests cannot have overlapping names. Make sure they use unique prefixes."
1101 );
1102 }
1103}
1104
1105pub fn early_config_check(config: &Config) {
1106 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1107 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1108 }
1109
1110 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1111 let actioned = if config.bless { "blessed" } else { "checked" };
1112 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1113 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1114 }
1115
1116 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1118 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1119 }
1120}