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::{AuxProps, 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 let mut configs = Vec::new();
529 if let TestMode::DebugInfo = config.mode {
530 if !config.target.contains("emscripten") {
532 match config.debugger {
533 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
534 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
535 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
536 None => {
541 configs.extend(debuggers::configure_cdb(&config));
542 configs.extend(debuggers::configure_gdb(&config));
543 configs.extend(debuggers::configure_lldb(&config));
544 }
545 }
546 }
547 } else {
548 configs.push(config.clone());
549 };
550
551 let mut tests = Vec::new();
554 for c in configs {
555 tests.extend(collect_and_make_tests(c));
556 }
557
558 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
559
560 let ok = executor::run_tests(&config, tests);
564
565 if !ok {
567 let mut msg = String::from("Some tests failed in compiletest");
575 write!(msg, " suite={}", config.suite).unwrap();
576
577 if let Some(compare_mode) = config.compare_mode.as_ref() {
578 write!(msg, " compare_mode={}", compare_mode).unwrap();
579 }
580
581 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
582 write!(msg, " pass_mode={}", pass_mode).unwrap();
583 }
584
585 write!(msg, " mode={}", config.mode).unwrap();
586 write!(msg, " host={}", config.host).unwrap();
587 write!(msg, " target={}", config.target).unwrap();
588
589 println!("{msg}");
590
591 std::process::exit(1);
592 }
593}
594
595struct TestCollectorCx {
597 config: Arc<Config>,
598 cache: DirectivesCache,
599 common_inputs_stamp: Stamp,
600 modified_tests: Vec<Utf8PathBuf>,
601}
602
603struct TestCollector {
605 tests: Vec<CollectedTest>,
606 found_path_stems: HashSet<Utf8PathBuf>,
607 poisoned: bool,
608}
609
610impl TestCollector {
611 fn new() -> Self {
612 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
613 }
614
615 fn merge(&mut self, mut other: Self) {
616 self.tests.append(&mut other.tests);
617 self.found_path_stems.extend(other.found_path_stems);
618 self.poisoned |= other.poisoned;
619 }
620}
621
622fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
632 debug!("making tests from {}", config.src_test_suite_root);
633 let common_inputs_stamp = common_inputs_stamp(&config);
634 let modified_tests =
635 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
636 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
637 });
638 let cache = DirectivesCache::load(&config);
639
640 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
641 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
642 .unwrap_or_else(|reason| {
643 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
644 });
645
646 let TestCollector { tests, found_path_stems, poisoned } = collector;
647
648 if poisoned {
649 eprintln!();
650 panic!("there are errors in tests");
651 }
652
653 check_for_overlapping_test_paths(&found_path_stems);
654
655 tests
656}
657
658fn common_inputs_stamp(config: &Config) -> Stamp {
666 let src_root = &config.src_root;
667
668 let mut stamp = Stamp::from_path(&config.rustc_path);
669
670 let pretty_printer_files = [
672 "src/etc/rust_types.py",
673 "src/etc/gdb_load_rust_pretty_printers.py",
674 "src/etc/gdb_lookup.py",
675 "src/etc/gdb_providers.py",
676 "src/etc/lldb_batchmode.py",
677 "src/etc/lldb_lookup.py",
678 "src/etc/lldb_providers.py",
679 ];
680 for file in &pretty_printer_files {
681 let path = src_root.join(file);
682 stamp.add_path(&path);
683 }
684
685 stamp.add_dir(&src_root.join("src/etc/natvis"));
686
687 stamp.add_dir(&config.run_lib_path);
688
689 if let Some(ref rustdoc_path) = config.rustdoc_path {
690 stamp.add_path(&rustdoc_path);
691 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
692 }
693
694 if let Some(coverage_dump_path) = &config.coverage_dump_path {
697 stamp.add_path(coverage_dump_path)
698 }
699
700 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
701
702 stamp.add_dir(&src_root.join("src/tools/compiletest"));
704
705 stamp
706}
707
708fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
713 if !config.only_modified {
716 return Ok(vec![]);
717 }
718
719 let files = get_git_modified_files(
720 &config.git_config(),
721 Some(dir.as_std_path()),
722 &vec!["rs", "stderr", "fixed"],
723 )?;
724 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
726
727 let all_paths = [&files[..], &untracked_files[..]].concat();
728 let full_paths = {
729 let mut full_paths: Vec<Utf8PathBuf> = all_paths
730 .into_iter()
731 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
732 .filter_map(
733 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
734 )
735 .collect();
736 full_paths.dedup();
737 full_paths.sort_unstable();
738 full_paths
739 };
740 Ok(full_paths)
741}
742
743fn collect_tests_from_dir(
746 cx: &TestCollectorCx,
747 dir: &Utf8Path,
748 relative_dir_path: &Utf8Path,
749) -> io::Result<TestCollector> {
750 if dir.join("compiletest-ignore-dir").exists() {
752 return Ok(TestCollector::new());
753 }
754
755 let mut components = dir.components().rev();
756 if let Some(Utf8Component::Normal(last)) = components.next()
757 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
758 && let Some(Utf8Component::Normal(parent)) = components.next()
759 && parent == "tests"
760 && let Ok(backend) = CodegenBackend::try_from(backend)
761 && backend != cx.config.default_codegen_backend
762 {
763 warning!(
765 "Ignoring tests in `{dir}` because they don't match the configured codegen \
766 backend (`{}`)",
767 cx.config.default_codegen_backend.as_str(),
768 );
769 return Ok(TestCollector::new());
770 }
771
772 if cx.config.mode == TestMode::RunMake {
774 let mut collector = TestCollector::new();
775 if dir.join("rmake.rs").exists() {
776 let paths = TestPaths {
777 file: dir.to_path_buf(),
778 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
779 };
780 make_test(cx, &mut collector, &paths);
781 return Ok(collector);
783 }
784 }
785
786 let build_dir = output_relative_path(&cx.config, relative_dir_path);
793 fs::create_dir_all(&build_dir).unwrap();
794
795 fs::read_dir(dir.as_std_path())?
800 .par_bridge()
801 .map(|file| {
802 let mut collector = TestCollector::new();
803 let file = file?;
804 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
805 let file_name = file_path.file_name().unwrap();
806
807 if is_test(file_name)
808 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
809 {
810 debug!(%file_path, "found test file");
812
813 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
815 collector.found_path_stems.insert(rel_test_path);
816
817 let paths =
818 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
819 make_test(cx, &mut collector, &paths);
820 } else if file_path.is_dir() {
821 let relative_file_path = relative_dir_path.join(file_name);
823 if file_name != "auxiliary" {
824 debug!(%file_path, "found directory");
825 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
826 }
827 } else {
828 debug!(%file_path, "found other file/directory");
829 }
830 Ok(collector)
831 })
832 .reduce(
833 || Ok(TestCollector::new()),
834 |a, b| {
835 let mut a = a?;
836 a.merge(b?);
837 Ok(a)
838 },
839 )
840}
841
842fn is_test(file_name: &str) -> bool {
844 if !file_name.ends_with(".rs") {
845 return false;
846 }
847
848 let invalid_prefixes = &[".", "#", "~"];
850 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
851}
852
853fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
856 let test_path = if cx.config.mode == TestMode::RunMake {
860 testpaths.file.join("rmake.rs")
861 } else {
862 testpaths.file.clone()
863 };
864
865 let file_contents =
867 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
868 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
869 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
870
871 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
878 vec![None]
879 } else {
880 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
881 };
882
883 collector.tests.extend(revisions.into_iter().map(|revision| {
886 let (test_name, filterable_path) =
888 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
889
890 let mut aux_props = AuxProps::default();
893
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 &mut aux_props,
907 );
908
909 if !desc.ignore
912 && !cx.config.force_rerun
913 && is_up_to_date(cx, testpaths, &aux_props, revision)
914 {
915 desc.ignore = true;
916 desc.ignore_message = Some("up-to-date".into());
920 }
921
922 let config = Arc::clone(&cx.config);
923 let testpaths = testpaths.clone();
924 let revision = revision.map(str::to_owned);
925
926 CollectedTest { desc, config, testpaths, revision }
927 }));
928}
929
930fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
933 output_base_dir(config, testpaths, revision).join("stamp")
934}
935
936fn files_related_to_test(
941 config: &Config,
942 testpaths: &TestPaths,
943 aux_props: &AuxProps,
944 revision: Option<&str>,
945) -> Vec<Utf8PathBuf> {
946 let mut related = vec![];
947
948 if testpaths.file.is_dir() {
949 for entry in WalkDir::new(&testpaths.file) {
951 let path = entry.unwrap().into_path();
952 if path.is_file() {
953 related.push(Utf8PathBuf::try_from(path).unwrap());
954 }
955 }
956 } else {
957 related.push(testpaths.file.clone());
958 }
959
960 for aux in aux_props.all_aux_path_strings() {
961 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
966 related.push(path);
967 }
968
969 for extension in UI_EXTENSIONS {
971 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
972 related.push(path);
973 }
974
975 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
977
978 related
979}
980
981fn is_up_to_date(
987 cx: &TestCollectorCx,
988 testpaths: &TestPaths,
989 aux_props: &AuxProps,
990 revision: Option<&str>,
991) -> bool {
992 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
993 let contents = match fs::read_to_string(&stamp_file_path) {
995 Ok(f) => f,
996 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
997 Err(_) => return false,
999 };
1000 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1001 if contents != expected_hash {
1002 return false;
1005 }
1006
1007 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1010 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1011 inputs_stamp.add_path(&path);
1012 }
1013
1014 inputs_stamp < Stamp::from_path(&stamp_file_path)
1017}
1018
1019#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1021struct Stamp {
1022 time: SystemTime,
1023}
1024
1025impl Stamp {
1026 fn from_path(path: &Utf8Path) -> Self {
1028 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1029 stamp.add_path(path);
1030 stamp
1031 }
1032
1033 fn add_path(&mut self, path: &Utf8Path) {
1036 let modified = fs::metadata(path.as_std_path())
1037 .and_then(|metadata| metadata.modified())
1038 .unwrap_or(SystemTime::UNIX_EPOCH);
1039 self.time = self.time.max(modified);
1040 }
1041
1042 fn add_dir(&mut self, path: &Utf8Path) {
1046 let path = path.as_std_path();
1047 for entry in WalkDir::new(path) {
1048 let entry = entry.unwrap();
1049 if entry.file_type().is_file() {
1050 let modified = entry
1051 .metadata()
1052 .ok()
1053 .and_then(|metadata| metadata.modified().ok())
1054 .unwrap_or(SystemTime::UNIX_EPOCH);
1055 self.time = self.time.max(modified);
1056 }
1057 }
1058 }
1059}
1060
1061fn make_test_name_and_filterable_path(
1063 config: &Config,
1064 testpaths: &TestPaths,
1065 revision: Option<&str>,
1066) -> (String, Utf8PathBuf) {
1067 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1069 let debugger = match config.debugger {
1070 Some(d) => format!("-{}", d),
1071 None => String::new(),
1072 };
1073 let mode_suffix = match config.compare_mode {
1074 Some(ref mode) => format!(" ({})", mode.to_str()),
1075 None => String::new(),
1076 };
1077
1078 let name = format!(
1079 "[{}{}{}] {}{}",
1080 config.mode,
1081 debugger,
1082 mode_suffix,
1083 path,
1084 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1085 );
1086
1087 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1091 filterable_path = filterable_path.components().skip(1).collect();
1093
1094 (name, filterable_path)
1095}
1096
1097fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1115 let mut collisions = Vec::new();
1116 for path in found_path_stems {
1117 for ancestor in path.ancestors().skip(1) {
1118 if found_path_stems.contains(ancestor) {
1119 collisions.push((path, ancestor));
1120 }
1121 }
1122 }
1123 if !collisions.is_empty() {
1124 collisions.sort();
1125 let collisions: String = collisions
1126 .into_iter()
1127 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1128 .collect();
1129 panic!(
1130 "{collisions}\n\
1131 Tests cannot have overlapping names. Make sure they use unique prefixes."
1132 );
1133 }
1134}
1135
1136fn early_config_check(config: &Config) {
1137 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1138 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1139 }
1140
1141 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1142 let actioned = if config.bless { "blessed" } else { "checked" };
1143 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1144 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1145 }
1146
1147 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1149 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1150 }
1151}