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 panic_hook;
19mod raise_fd_limit;
20mod read2;
21pub mod runtest;
22pub mod util;
23
24use core::panic;
25use std::collections::HashSet;
26use std::fmt::Write;
27use std::io::{self, ErrorKind};
28use std::process::{Command, Stdio};
29use std::sync::{Arc, OnceLock};
30use std::time::SystemTime;
31use std::{env, fs, vec};
32
33use build_helper::git::{get_git_modified_files, get_git_untracked_files};
34use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
35use getopts::Options;
36use rayon::iter::{ParallelBridge, ParallelIterator};
37use tracing::debug;
38use walkdir::WalkDir;
39
40use self::directives::{EarlyProps, make_test_description};
41use crate::common::{
42 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
43 expected_output_path, output_base_dir, output_relative_path,
44};
45use crate::directives::DirectivesCache;
46use crate::executor::{CollectedTest, ColorConfig};
47
48pub fn 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);
314 let mut iter = path.iter().skip(1);
315
316 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
318 path.parent().unwrap().to_string()
319 } else {
320 f.to_string()
321 }
322 })
323 .collect::<Vec<_>>()
324 } else {
325 matches.free.clone()
326 };
327 let compare_mode = matches.opt_str("compare-mode").map(|s| {
328 s.parse().unwrap_or_else(|_| {
329 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
330 panic!(
331 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
332 variants.join(", ")
333 );
334 })
335 });
336 if matches.opt_present("nocapture") {
337 panic!("`--nocapture` is deprecated; please use `--no-capture`");
338 }
339
340 let stage = match matches.opt_str("stage") {
341 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
342 None => panic!("`--stage` is required"),
343 };
344
345 let src_root = opt_path(matches, "src-root");
346 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
347 assert!(
348 src_test_suite_root.starts_with(&src_root),
349 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
350 src_root,
351 src_test_suite_root
352 );
353
354 let build_root = opt_path(matches, "build-root");
355 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
356 assert!(build_test_suite_root.starts_with(&build_root));
357
358 Config {
359 bless: matches.opt_present("bless"),
360 fail_fast: matches.opt_present("fail-fast")
361 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
362
363 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
364 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
365 rustc_path: opt_path(matches, "rustc-path"),
366 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
367 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
368 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
369 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
370 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
371 python: matches.opt_str("python").unwrap(),
372 jsondocck_path: matches.opt_str("jsondocck-path"),
373 jsondoclint_path: matches.opt_str("jsondoclint-path"),
374 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
375 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
376 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
377
378 src_root,
379 src_test_suite_root,
380
381 build_root,
382 build_test_suite_root,
383
384 sysroot_base: opt_path(matches, "sysroot-base"),
385
386 stage,
387 stage_id: matches.opt_str("stage-id").unwrap(),
388
389 mode,
390 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
391 debugger: matches.opt_str("debugger").map(|debugger| {
392 debugger
393 .parse::<Debugger>()
394 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
395 }),
396 run_ignored,
397 with_rustc_debug_assertions,
398 with_std_debug_assertions,
399 filters,
400 skip: matches.opt_strs("skip"),
401 filter_exact: matches.opt_present("exact"),
402 force_pass_mode: matches.opt_str("pass").map(|mode| {
403 mode.parse::<PassMode>()
404 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
405 }),
406 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
408 "auto" => None,
409 "always" => Some(true),
410 "never" => Some(false),
411 _ => panic!("unknown `--run` option `{}` given", mode),
412 }),
413 runner: matches.opt_str("runner"),
414 host_rustcflags: matches.opt_strs("host-rustcflags"),
415 target_rustcflags: matches.opt_strs("target-rustcflags"),
416 optimize_tests: matches.opt_present("optimize-tests"),
417 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
418 target,
419 host: opt_str2(matches.opt_str("host")),
420 cdb,
421 cdb_version,
422 gdb,
423 gdb_version,
424 lldb_version,
425 llvm_version,
426 system_llvm: matches.opt_present("system-llvm"),
427 android_cross_path,
428 adb_path: opt_str2(matches.opt_str("adb-path")),
429 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
430 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
431 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
432 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
433 lldb_python_dir: matches.opt_str("lldb-python-dir"),
434 verbose: matches.opt_present("verbose"),
435 only_modified: matches.opt_present("only-modified"),
436 color,
437 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
438 compare_mode,
439 rustfix_coverage: matches.opt_present("rustfix-coverage"),
440 has_html_tidy,
441 has_enzyme,
442 channel: matches.opt_str("channel").unwrap(),
443 git_hash: matches.opt_present("git-hash"),
444 edition: matches.opt_str("edition"),
445
446 cc: matches.opt_str("cc").unwrap(),
447 cxx: matches.opt_str("cxx").unwrap(),
448 cflags: matches.opt_str("cflags").unwrap(),
449 cxxflags: matches.opt_str("cxxflags").unwrap(),
450 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
451 target_linker: matches.opt_str("target-linker"),
452 host_linker: matches.opt_str("host-linker"),
453 llvm_components: matches.opt_str("llvm-components").unwrap(),
454 nodejs: matches.opt_str("nodejs"),
455 npm: matches.opt_str("npm"),
456
457 force_rerun: matches.opt_present("force-rerun"),
458
459 target_cfgs: OnceLock::new(),
460 builtin_cfg_names: OnceLock::new(),
461 supported_crate_types: OnceLock::new(),
462
463 nocapture: matches.opt_present("no-capture"),
464
465 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
466 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
467
468 profiler_runtime: matches.opt_present("profiler-runtime"),
469
470 diff_command: matches.opt_str("compiletest-diff-tool"),
471
472 minicore_path: opt_path(matches, "minicore-path"),
473
474 default_codegen_backend,
475 override_codegen_backend,
476 }
477}
478
479pub fn opt_str(maybestr: &Option<String>) -> &str {
480 match *maybestr {
481 None => "(none)",
482 Some(ref s) => s,
483 }
484}
485
486pub fn opt_str2(maybestr: Option<String>) -> String {
487 match maybestr {
488 None => "(none)".to_owned(),
489 Some(s) => s,
490 }
491}
492
493pub fn run_tests(config: Arc<Config>) {
495 debug!(?config, "run_tests");
496
497 panic_hook::install_panic_hook();
498
499 if config.rustfix_coverage {
503 let mut coverage_file_path = config.build_test_suite_root.clone();
504 coverage_file_path.push("rustfix_missing_coverage.txt");
505 if coverage_file_path.exists() {
506 if let Err(e) = fs::remove_file(&coverage_file_path) {
507 panic!("Could not delete {} due to {}", coverage_file_path, e)
508 }
509 }
510 }
511
512 unsafe {
516 raise_fd_limit::raise_fd_limit();
517 }
518 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
523
524 unsafe { env::set_var("TARGET", &config.target) };
528
529 let mut configs = Vec::new();
530 if let TestMode::DebugInfo = config.mode {
531 if !config.target.contains("emscripten") {
533 match config.debugger {
534 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
535 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
536 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
537 None => {
542 configs.extend(debuggers::configure_cdb(&config));
543 configs.extend(debuggers::configure_gdb(&config));
544 configs.extend(debuggers::configure_lldb(&config));
545 }
546 }
547 }
548 } else {
549 configs.push(config.clone());
550 };
551
552 let mut tests = Vec::new();
555 for c in configs {
556 tests.extend(collect_and_make_tests(c));
557 }
558
559 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
560
561 let ok = executor::run_tests(&config, tests);
565
566 if !ok {
568 let mut msg = String::from("Some tests failed in compiletest");
576 write!(msg, " suite={}", config.suite).unwrap();
577
578 if let Some(compare_mode) = config.compare_mode.as_ref() {
579 write!(msg, " compare_mode={}", compare_mode).unwrap();
580 }
581
582 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
583 write!(msg, " pass_mode={}", pass_mode).unwrap();
584 }
585
586 write!(msg, " mode={}", config.mode).unwrap();
587 write!(msg, " host={}", config.host).unwrap();
588 write!(msg, " target={}", config.target).unwrap();
589
590 println!("{msg}");
591
592 std::process::exit(1);
593 }
594}
595
596struct TestCollectorCx {
598 config: Arc<Config>,
599 cache: DirectivesCache,
600 common_inputs_stamp: Stamp,
601 modified_tests: Vec<Utf8PathBuf>,
602}
603
604struct TestCollector {
606 tests: Vec<CollectedTest>,
607 found_path_stems: HashSet<Utf8PathBuf>,
608 poisoned: bool,
609}
610
611impl TestCollector {
612 fn new() -> Self {
613 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
614 }
615
616 fn merge(&mut self, mut other: Self) {
617 self.tests.append(&mut other.tests);
618 self.found_path_stems.extend(other.found_path_stems);
619 self.poisoned |= other.poisoned;
620 }
621}
622
623pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
633 debug!("making tests from {}", config.src_test_suite_root);
634 let common_inputs_stamp = common_inputs_stamp(&config);
635 let modified_tests =
636 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
637 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
638 });
639 let cache = DirectivesCache::load(&config);
640
641 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
642 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
643 .unwrap_or_else(|reason| {
644 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
645 });
646
647 let TestCollector { tests, found_path_stems, poisoned } = collector;
648
649 if poisoned {
650 eprintln!();
651 panic!("there are errors in tests");
652 }
653
654 check_for_overlapping_test_paths(&found_path_stems);
655
656 tests
657}
658
659fn common_inputs_stamp(config: &Config) -> Stamp {
667 let src_root = &config.src_root;
668
669 let mut stamp = Stamp::from_path(&config.rustc_path);
670
671 let pretty_printer_files = [
673 "src/etc/rust_types.py",
674 "src/etc/gdb_load_rust_pretty_printers.py",
675 "src/etc/gdb_lookup.py",
676 "src/etc/gdb_providers.py",
677 "src/etc/lldb_batchmode.py",
678 "src/etc/lldb_lookup.py",
679 "src/etc/lldb_providers.py",
680 ];
681 for file in &pretty_printer_files {
682 let path = src_root.join(file);
683 stamp.add_path(&path);
684 }
685
686 stamp.add_dir(&src_root.join("src/etc/natvis"));
687
688 stamp.add_dir(&config.run_lib_path);
689
690 if let Some(ref rustdoc_path) = config.rustdoc_path {
691 stamp.add_path(&rustdoc_path);
692 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
693 }
694
695 if let Some(coverage_dump_path) = &config.coverage_dump_path {
698 stamp.add_path(coverage_dump_path)
699 }
700
701 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
702
703 stamp.add_dir(&src_root.join("src/tools/compiletest"));
705
706 stamp
707}
708
709fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
714 if !config.only_modified {
717 return Ok(vec![]);
718 }
719
720 let files = get_git_modified_files(
721 &config.git_config(),
722 Some(dir.as_std_path()),
723 &vec!["rs", "stderr", "fixed"],
724 )?;
725 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
727
728 let all_paths = [&files[..], &untracked_files[..]].concat();
729 let full_paths = {
730 let mut full_paths: Vec<Utf8PathBuf> = all_paths
731 .into_iter()
732 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
733 .filter_map(
734 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
735 )
736 .collect();
737 full_paths.dedup();
738 full_paths.sort_unstable();
739 full_paths
740 };
741 Ok(full_paths)
742}
743
744fn collect_tests_from_dir(
747 cx: &TestCollectorCx,
748 dir: &Utf8Path,
749 relative_dir_path: &Utf8Path,
750) -> io::Result<TestCollector> {
751 if dir.join("compiletest-ignore-dir").exists() {
753 return Ok(TestCollector::new());
754 }
755
756 let mut components = dir.components().rev();
757 if let Some(Utf8Component::Normal(last)) = components.next()
758 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
759 && let Some(Utf8Component::Normal(parent)) = components.next()
760 && parent == "tests"
761 && let Ok(backend) = CodegenBackend::try_from(backend)
762 && backend != cx.config.default_codegen_backend
763 {
764 warning!(
766 "Ignoring tests in `{dir}` because they don't match the configured codegen \
767 backend (`{}`)",
768 cx.config.default_codegen_backend.as_str(),
769 );
770 return Ok(TestCollector::new());
771 }
772
773 if cx.config.mode == TestMode::RunMake {
775 let mut collector = TestCollector::new();
776 if dir.join("rmake.rs").exists() {
777 let paths = TestPaths {
778 file: dir.to_path_buf(),
779 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
780 };
781 make_test(cx, &mut collector, &paths);
782 return Ok(collector);
784 }
785 }
786
787 let build_dir = output_relative_path(&cx.config, relative_dir_path);
794 fs::create_dir_all(&build_dir).unwrap();
795
796 fs::read_dir(dir.as_std_path())?
801 .par_bridge()
802 .map(|file| {
803 let mut collector = TestCollector::new();
804 let file = file?;
805 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
806 let file_name = file_path.file_name().unwrap();
807
808 if is_test(file_name)
809 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
810 {
811 debug!(%file_path, "found test file");
813
814 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
816 collector.found_path_stems.insert(rel_test_path);
817
818 let paths =
819 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
820 make_test(cx, &mut collector, &paths);
821 } else if file_path.is_dir() {
822 let relative_file_path = relative_dir_path.join(file_name);
824 if file_name != "auxiliary" {
825 debug!(%file_path, "found directory");
826 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
827 }
828 } else {
829 debug!(%file_path, "found other file/directory");
830 }
831 Ok(collector)
832 })
833 .reduce(
834 || Ok(TestCollector::new()),
835 |a, b| {
836 let mut a = a?;
837 a.merge(b?);
838 Ok(a)
839 },
840 )
841}
842
843pub fn is_test(file_name: &str) -> bool {
845 if !file_name.ends_with(".rs") {
846 return false;
847 }
848
849 let invalid_prefixes = &[".", "#", "~"];
851 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
852}
853
854fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
857 let test_path = if cx.config.mode == TestMode::RunMake {
861 testpaths.file.join("rmake.rs")
862 } else {
863 testpaths.file.clone()
864 };
865
866 let early_props = EarlyProps::from_file(&cx.config, &test_path);
868
869 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
876 vec![None]
877 } else {
878 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
879 };
880
881 collector.tests.extend(revisions.into_iter().map(|revision| {
884 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
886 let test_name = make_test_name(&cx.config, testpaths, revision);
887 let mut desc = make_test_description(
891 &cx.config,
892 &cx.cache,
893 test_name,
894 &test_path,
895 src_file,
896 revision,
897 &mut collector.poisoned,
898 );
899
900 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
903 desc.ignore = true;
904 desc.ignore_message = Some("up-to-date".into());
908 }
909
910 let config = Arc::clone(&cx.config);
911 let testpaths = testpaths.clone();
912 let revision = revision.map(str::to_owned);
913
914 CollectedTest { desc, config, testpaths, revision }
915 }));
916}
917
918fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
921 output_base_dir(config, testpaths, revision).join("stamp")
922}
923
924fn files_related_to_test(
929 config: &Config,
930 testpaths: &TestPaths,
931 props: &EarlyProps,
932 revision: Option<&str>,
933) -> Vec<Utf8PathBuf> {
934 let mut related = vec![];
935
936 if testpaths.file.is_dir() {
937 for entry in WalkDir::new(&testpaths.file) {
939 let path = entry.unwrap().into_path();
940 if path.is_file() {
941 related.push(Utf8PathBuf::try_from(path).unwrap());
942 }
943 }
944 } else {
945 related.push(testpaths.file.clone());
946 }
947
948 for aux in props.aux.all_aux_path_strings() {
949 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
951 related.push(path);
952 }
953
954 for extension in UI_EXTENSIONS {
956 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
957 related.push(path);
958 }
959
960 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
962
963 related
964}
965
966fn is_up_to_date(
972 cx: &TestCollectorCx,
973 testpaths: &TestPaths,
974 props: &EarlyProps,
975 revision: Option<&str>,
976) -> bool {
977 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
978 let contents = match fs::read_to_string(&stamp_file_path) {
980 Ok(f) => f,
981 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
982 Err(_) => return false,
984 };
985 let expected_hash = runtest::compute_stamp_hash(&cx.config);
986 if contents != expected_hash {
987 return false;
990 }
991
992 let mut inputs_stamp = cx.common_inputs_stamp.clone();
995 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
996 inputs_stamp.add_path(&path);
997 }
998
999 inputs_stamp < Stamp::from_path(&stamp_file_path)
1002}
1003
1004#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1006struct Stamp {
1007 time: SystemTime,
1008}
1009
1010impl Stamp {
1011 fn from_path(path: &Utf8Path) -> Self {
1013 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1014 stamp.add_path(path);
1015 stamp
1016 }
1017
1018 fn add_path(&mut self, path: &Utf8Path) {
1021 let modified = fs::metadata(path.as_std_path())
1022 .and_then(|metadata| metadata.modified())
1023 .unwrap_or(SystemTime::UNIX_EPOCH);
1024 self.time = self.time.max(modified);
1025 }
1026
1027 fn add_dir(&mut self, path: &Utf8Path) {
1031 let path = path.as_std_path();
1032 for entry in WalkDir::new(path) {
1033 let entry = entry.unwrap();
1034 if entry.file_type().is_file() {
1035 let modified = entry
1036 .metadata()
1037 .ok()
1038 .and_then(|metadata| metadata.modified().ok())
1039 .unwrap_or(SystemTime::UNIX_EPOCH);
1040 self.time = self.time.max(modified);
1041 }
1042 }
1043 }
1044}
1045
1046fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1048 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1050 let debugger = match config.debugger {
1051 Some(d) => format!("-{}", d),
1052 None => String::new(),
1053 };
1054 let mode_suffix = match config.compare_mode {
1055 Some(ref mode) => format!(" ({})", mode.to_str()),
1056 None => String::new(),
1057 };
1058
1059 format!(
1060 "[{}{}{}] {}{}",
1061 config.mode,
1062 debugger,
1063 mode_suffix,
1064 path,
1065 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1066 )
1067}
1068
1069fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1087 let mut collisions = Vec::new();
1088 for path in found_path_stems {
1089 for ancestor in path.ancestors().skip(1) {
1090 if found_path_stems.contains(ancestor) {
1091 collisions.push((path, ancestor));
1092 }
1093 }
1094 }
1095 if !collisions.is_empty() {
1096 collisions.sort();
1097 let collisions: String = collisions
1098 .into_iter()
1099 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1100 .collect();
1101 panic!(
1102 "{collisions}\n\
1103 Tests cannot have overlapping names. Make sure they use unique prefixes."
1104 );
1105 }
1106}
1107
1108pub fn early_config_check(config: &Config) {
1109 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1110 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1111 }
1112
1113 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1114 let actioned = if config.bless { "blessed" } else { "checked" };
1115 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1116 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1117 }
1118
1119 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1121 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1122 }
1123}