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 output_capture;
19mod panic_hook;
20mod raise_fd_limit;
21mod read2;
22pub mod runtest;
23pub mod util;
24
25use core::panic;
26use std::collections::HashSet;
27use std::fmt::Write;
28use std::io::{self, ErrorKind};
29use std::process::{Command, Stdio};
30use std::sync::{Arc, OnceLock};
31use std::time::SystemTime;
32use std::{env, fs, vec};
33
34use build_helper::git::{get_git_modified_files, get_git_untracked_files};
35use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
36use getopts::Options;
37use rayon::iter::{ParallelBridge, ParallelIterator};
38use tracing::debug;
39use walkdir::WalkDir;
40
41use self::directives::{EarlyProps, make_test_description};
42use crate::common::{
43 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
44 expected_output_path, output_base_dir, output_relative_path,
45};
46use crate::directives::DirectivesCache;
47use crate::executor::{CollectedTest, ColorConfig};
48
49pub fn parse_config(args: Vec<String>) -> Config {
55 let mut opts = Options::new();
56 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
57 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
58 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
59 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
60 .optopt(
61 "",
62 "stage0-rustc-path",
63 "path to rustc to use for compiling run-make recipes",
64 "PATH",
65 )
66 .optopt(
67 "",
68 "query-rustc-path",
69 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
70 "PATH",
71 )
72 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
73 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
74 .reqopt("", "python", "path to python to use for doc tests", "PATH")
75 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
76 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
77 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
78 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
79 .reqopt("", "src-root", "directory containing sources", "PATH")
80 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
81 .reqopt("", "build-root", "path to root build directory", "PATH")
82 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
83 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
84 .reqopt("", "stage", "stage number under test", "N")
85 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
86 .reqopt(
87 "",
88 "mode",
89 "which sort of compile tests to run",
90 "pretty | debug-info | codegen | rustdoc \
91 | rustdoc-json | codegen-units | incremental | run-make | ui \
92 | rustdoc-js | mir-opt | assembly | crashes",
93 )
94 .reqopt(
95 "",
96 "suite",
97 "which suite of compile tests to run. used for nicer error reporting.",
98 "SUITE",
99 )
100 .optopt(
101 "",
102 "pass",
103 "force {check,build,run}-pass tests to this mode.",
104 "check | build | run",
105 )
106 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
107 .optflag("", "ignored", "run tests marked as ignored")
108 .optflag("", "has-enzyme", "run tests that require enzyme")
109 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
110 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
111 .optmulti(
112 "",
113 "skip",
114 "skip tests matching SUBSTRING. Can be passed multiple times",
115 "SUBSTRING",
116 )
117 .optflag("", "exact", "filters match exactly")
118 .optopt(
119 "",
120 "runner",
121 "supervisor program to run tests under \
122 (eg. emulator, valgrind)",
123 "PROGRAM",
124 )
125 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
126 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
127 .optflag(
128 "",
129 "rust-randomized-layout",
130 "set this when rustc/stdlib were compiled with randomized layouts",
131 )
132 .optflag("", "optimize-tests", "run tests with optimizations enabled")
133 .optflag("", "verbose", "run tests verbosely, showing all output")
134 .optflag(
135 "",
136 "bless",
137 "overwrite stderr/stdout files instead of complaining about a mismatch",
138 )
139 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
140 .optopt("", "color", "coloring: auto, always, never", "WHEN")
141 .optopt("", "target", "the target to build for", "TARGET")
142 .optopt("", "host", "the host to build for", "HOST")
143 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
144 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
145 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
146 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
147 .optflag("", "system-llvm", "is LLVM the system LLVM")
148 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
149 .optopt("", "adb-path", "path to the android debugger", "PATH")
150 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
151 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
152 .reqopt("", "cc", "path to a C compiler", "PATH")
153 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
154 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
155 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
156 .optopt("", "ar", "path to an archiver", "PATH")
157 .optopt("", "target-linker", "path to a linker for the target", "PATH")
158 .optopt("", "host-linker", "path to a linker for the host", "PATH")
159 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
160 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
161 .optopt("", "nodejs", "the name of nodejs", "PATH")
162 .optopt("", "npm", "the name of npm", "PATH")
163 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
164 .optopt(
165 "",
166 "compare-mode",
167 "mode describing what file the actual ui output will be compared to",
168 "COMPARE MODE",
169 )
170 .optflag(
171 "",
172 "rustfix-coverage",
173 "enable this to generate a Rustfix coverage file, which is saved in \
174 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
175 )
176 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
177 .optflag("", "only-modified", "only run tests that result been modified")
178 .optflag("", "nocapture", "")
180 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
181 .optopt(
182 "N",
183 "new-output-capture",
184 "enables or disables the new output-capture implementation",
185 "off|on",
186 )
187 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
188 .optflag("h", "help", "show this message")
189 .reqopt("", "channel", "current Rust channel", "CHANNEL")
190 .optflag(
191 "",
192 "git-hash",
193 "run tests which rely on commit version being compiled into the binaries",
194 )
195 .optopt("", "edition", "default Rust edition", "EDITION")
196 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
197 .reqopt(
198 "",
199 "git-merge-commit-email",
200 "email address used for finding merge commits",
201 "EMAIL",
202 )
203 .optopt(
204 "",
205 "compiletest-diff-tool",
206 "What custom diff tool to use for displaying compiletest tests.",
207 "COMMAND",
208 )
209 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
210 .optopt(
211 "",
212 "debugger",
213 "only test a specific debugger in debuginfo tests",
214 "gdb | lldb | cdb",
215 )
216 .optopt(
217 "",
218 "default-codegen-backend",
219 "the codegen backend currently used",
220 "CODEGEN BACKEND NAME",
221 )
222 .optopt(
223 "",
224 "override-codegen-backend",
225 "the codegen backend to use instead of the default one",
226 "CODEGEN BACKEND [NAME | PATH]",
227 );
228
229 let (argv0, args_) = args.split_first().unwrap();
230 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
231 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
232 println!("{}", opts.usage(&message));
233 println!();
234 panic!()
235 }
236
237 let matches = &match opts.parse(args_) {
238 Ok(m) => m,
239 Err(f) => panic!("{:?}", f),
240 };
241
242 if matches.opt_present("h") || matches.opt_present("help") {
243 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
244 println!("{}", opts.usage(&message));
245 println!();
246 panic!()
247 }
248
249 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
250 if path.is_relative() {
251 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
252 } else {
253 path
254 }
255 }
256
257 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
258 match m.opt_str(nm) {
259 Some(s) => Utf8PathBuf::from(&s),
260 None => panic!("no option (=path) found for {}", nm),
261 }
262 }
263
264 let target = opt_str2(matches.opt_str("target"));
265 let android_cross_path = opt_path(matches, "android-cross-path");
266 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
268 let (gdb, gdb_version) =
270 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
271 let lldb_version =
273 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
274 let color = match matches.opt_str("color").as_deref() {
275 Some("auto") | None => ColorConfig::AutoColor,
276 Some("always") => ColorConfig::AlwaysColor,
277 Some("never") => ColorConfig::NeverColor,
278 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
279 };
280 let llvm_version =
284 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
285 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
286 );
287
288 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
289 Some(backend) => match CodegenBackend::try_from(backend) {
290 Ok(backend) => backend,
291 Err(error) => {
292 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
293 }
294 },
295 None => CodegenBackend::Llvm,
297 };
298 let override_codegen_backend = matches.opt_str("override-codegen-backend");
299
300 let run_ignored = matches.opt_present("ignored");
301 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
302 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
303 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
304 let has_html_tidy = if mode == TestMode::Rustdoc {
305 Command::new("tidy")
306 .arg("--version")
307 .stdout(Stdio::null())
308 .status()
309 .map_or(false, |status| status.success())
310 } else {
311 false
313 };
314 let has_enzyme = matches.opt_present("has-enzyme");
315 let filters = if mode == TestMode::RunMake {
316 matches
317 .free
318 .iter()
319 .map(|f| {
320 let path = Utf8Path::new(f);
321 let mut iter = path.iter().skip(1);
322
323 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
325 path.parent().unwrap().to_string()
326 } else {
327 f.to_string()
328 }
329 })
330 .collect::<Vec<_>>()
331 } else {
332 matches.free.clone()
333 };
334 let compare_mode = matches.opt_str("compare-mode").map(|s| {
335 s.parse().unwrap_or_else(|_| {
336 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
337 panic!(
338 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
339 variants.join(", ")
340 );
341 })
342 });
343 if matches.opt_present("nocapture") {
344 panic!("`--nocapture` is deprecated; please use `--no-capture`");
345 }
346
347 let stage = match matches.opt_str("stage") {
348 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
349 None => panic!("`--stage` is required"),
350 };
351
352 let src_root = opt_path(matches, "src-root");
353 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
354 assert!(
355 src_test_suite_root.starts_with(&src_root),
356 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
357 src_root,
358 src_test_suite_root
359 );
360
361 let build_root = opt_path(matches, "build-root");
362 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
363 assert!(build_test_suite_root.starts_with(&build_root));
364
365 Config {
366 bless: matches.opt_present("bless"),
367 fail_fast: matches.opt_present("fail-fast")
368 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
369
370 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
371 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
372 rustc_path: opt_path(matches, "rustc-path"),
373 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
374 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
375 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
376 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
377 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
378 python: matches.opt_str("python").unwrap(),
379 jsondocck_path: matches.opt_str("jsondocck-path"),
380 jsondoclint_path: matches.opt_str("jsondoclint-path"),
381 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
382 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
383 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
384
385 src_root,
386 src_test_suite_root,
387
388 build_root,
389 build_test_suite_root,
390
391 sysroot_base: opt_path(matches, "sysroot-base"),
392
393 stage,
394 stage_id: matches.opt_str("stage-id").unwrap(),
395
396 mode,
397 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
398 debugger: matches.opt_str("debugger").map(|debugger| {
399 debugger
400 .parse::<Debugger>()
401 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
402 }),
403 run_ignored,
404 with_rustc_debug_assertions,
405 with_std_debug_assertions,
406 filters,
407 skip: matches.opt_strs("skip"),
408 filter_exact: matches.opt_present("exact"),
409 force_pass_mode: matches.opt_str("pass").map(|mode| {
410 mode.parse::<PassMode>()
411 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
412 }),
413 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
415 "auto" => None,
416 "always" => Some(true),
417 "never" => Some(false),
418 _ => panic!("unknown `--run` option `{}` given", mode),
419 }),
420 runner: matches.opt_str("runner"),
421 host_rustcflags: matches.opt_strs("host-rustcflags"),
422 target_rustcflags: matches.opt_strs("target-rustcflags"),
423 optimize_tests: matches.opt_present("optimize-tests"),
424 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
425 target,
426 host: opt_str2(matches.opt_str("host")),
427 cdb,
428 cdb_version,
429 gdb,
430 gdb_version,
431 lldb_version,
432 llvm_version,
433 system_llvm: matches.opt_present("system-llvm"),
434 android_cross_path,
435 adb_path: opt_str2(matches.opt_str("adb-path")),
436 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
437 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
438 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
439 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
440 lldb_python_dir: matches.opt_str("lldb-python-dir"),
441 verbose: matches.opt_present("verbose"),
442 only_modified: matches.opt_present("only-modified"),
443 color,
444 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
445 compare_mode,
446 rustfix_coverage: matches.opt_present("rustfix-coverage"),
447 has_html_tidy,
448 has_enzyme,
449 channel: matches.opt_str("channel").unwrap(),
450 git_hash: matches.opt_present("git-hash"),
451 edition: matches.opt_str("edition"),
452
453 cc: matches.opt_str("cc").unwrap(),
454 cxx: matches.opt_str("cxx").unwrap(),
455 cflags: matches.opt_str("cflags").unwrap(),
456 cxxflags: matches.opt_str("cxxflags").unwrap(),
457 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
458 target_linker: matches.opt_str("target-linker"),
459 host_linker: matches.opt_str("host-linker"),
460 llvm_components: matches.opt_str("llvm-components").unwrap(),
461 nodejs: matches.opt_str("nodejs"),
462 npm: matches.opt_str("npm"),
463
464 force_rerun: matches.opt_present("force-rerun"),
465
466 target_cfgs: OnceLock::new(),
467 builtin_cfg_names: OnceLock::new(),
468 supported_crate_types: OnceLock::new(),
469
470 nocapture: matches.opt_present("no-capture"),
471 new_output_capture: {
472 let value = matches
473 .opt_str("new-output-capture")
474 .or_else(|| env::var("COMPILETEST_NEW_OUTPUT_CAPTURE").ok())
475 .unwrap_or_else(|| "off".to_owned());
476 parse_bool_option(&value)
477 .unwrap_or_else(|| panic!("unknown `--new-output-capture` value `{value}` given"))
478 },
479
480 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
481 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
482
483 profiler_runtime: matches.opt_present("profiler-runtime"),
484
485 diff_command: matches.opt_str("compiletest-diff-tool"),
486
487 minicore_path: opt_path(matches, "minicore-path"),
488
489 default_codegen_backend,
490 override_codegen_backend,
491 }
492}
493
494fn parse_bool_option(value: &str) -> Option<bool> {
500 match value {
501 "off" | "no" | "n" | "false" => Some(false),
502 "on" | "yes" | "y" | "true" => Some(true),
503 _ => None,
504 }
505}
506
507pub fn opt_str(maybestr: &Option<String>) -> &str {
508 match *maybestr {
509 None => "(none)",
510 Some(ref s) => s,
511 }
512}
513
514pub fn opt_str2(maybestr: Option<String>) -> String {
515 match maybestr {
516 None => "(none)".to_owned(),
517 Some(s) => s,
518 }
519}
520
521pub fn run_tests(config: Arc<Config>) {
523 debug!(?config, "run_tests");
524
525 panic_hook::install_panic_hook();
526
527 if config.rustfix_coverage {
531 let mut coverage_file_path = config.build_test_suite_root.clone();
532 coverage_file_path.push("rustfix_missing_coverage.txt");
533 if coverage_file_path.exists() {
534 if let Err(e) = fs::remove_file(&coverage_file_path) {
535 panic!("Could not delete {} due to {}", coverage_file_path, e)
536 }
537 }
538 }
539
540 unsafe {
544 raise_fd_limit::raise_fd_limit();
545 }
546 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
551
552 unsafe { env::set_var("TARGET", &config.target) };
556
557 let mut configs = Vec::new();
558 if let TestMode::DebugInfo = config.mode {
559 if !config.target.contains("emscripten") {
561 match config.debugger {
562 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
563 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
564 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
565 None => {
570 configs.extend(debuggers::configure_cdb(&config));
571 configs.extend(debuggers::configure_gdb(&config));
572 configs.extend(debuggers::configure_lldb(&config));
573 }
574 }
575 }
576 } else {
577 configs.push(config.clone());
578 };
579
580 let mut tests = Vec::new();
583 for c in configs {
584 tests.extend(collect_and_make_tests(c));
585 }
586
587 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
588
589 let ok = executor::run_tests(&config, tests);
593
594 if !ok {
596 let mut msg = String::from("Some tests failed in compiletest");
604 write!(msg, " suite={}", config.suite).unwrap();
605
606 if let Some(compare_mode) = config.compare_mode.as_ref() {
607 write!(msg, " compare_mode={}", compare_mode).unwrap();
608 }
609
610 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
611 write!(msg, " pass_mode={}", pass_mode).unwrap();
612 }
613
614 write!(msg, " mode={}", config.mode).unwrap();
615 write!(msg, " host={}", config.host).unwrap();
616 write!(msg, " target={}", config.target).unwrap();
617
618 println!("{msg}");
619
620 std::process::exit(1);
621 }
622}
623
624struct TestCollectorCx {
626 config: Arc<Config>,
627 cache: DirectivesCache,
628 common_inputs_stamp: Stamp,
629 modified_tests: Vec<Utf8PathBuf>,
630}
631
632struct TestCollector {
634 tests: Vec<CollectedTest>,
635 found_path_stems: HashSet<Utf8PathBuf>,
636 poisoned: bool,
637}
638
639impl TestCollector {
640 fn new() -> Self {
641 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
642 }
643
644 fn merge(&mut self, mut other: Self) {
645 self.tests.append(&mut other.tests);
646 self.found_path_stems.extend(other.found_path_stems);
647 self.poisoned |= other.poisoned;
648 }
649}
650
651pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
661 debug!("making tests from {}", config.src_test_suite_root);
662 let common_inputs_stamp = common_inputs_stamp(&config);
663 let modified_tests =
664 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
665 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
666 });
667 let cache = DirectivesCache::load(&config);
668
669 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
670 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
671 .unwrap_or_else(|reason| {
672 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
673 });
674
675 let TestCollector { tests, found_path_stems, poisoned } = collector;
676
677 if poisoned {
678 eprintln!();
679 panic!("there are errors in tests");
680 }
681
682 check_for_overlapping_test_paths(&found_path_stems);
683
684 tests
685}
686
687fn common_inputs_stamp(config: &Config) -> Stamp {
695 let src_root = &config.src_root;
696
697 let mut stamp = Stamp::from_path(&config.rustc_path);
698
699 let pretty_printer_files = [
701 "src/etc/rust_types.py",
702 "src/etc/gdb_load_rust_pretty_printers.py",
703 "src/etc/gdb_lookup.py",
704 "src/etc/gdb_providers.py",
705 "src/etc/lldb_batchmode.py",
706 "src/etc/lldb_lookup.py",
707 "src/etc/lldb_providers.py",
708 ];
709 for file in &pretty_printer_files {
710 let path = src_root.join(file);
711 stamp.add_path(&path);
712 }
713
714 stamp.add_dir(&src_root.join("src/etc/natvis"));
715
716 stamp.add_dir(&config.run_lib_path);
717
718 if let Some(ref rustdoc_path) = config.rustdoc_path {
719 stamp.add_path(&rustdoc_path);
720 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
721 }
722
723 if let Some(coverage_dump_path) = &config.coverage_dump_path {
726 stamp.add_path(coverage_dump_path)
727 }
728
729 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
730
731 stamp.add_dir(&src_root.join("src/tools/compiletest"));
733
734 stamp
735}
736
737fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
742 if !config.only_modified {
745 return Ok(vec![]);
746 }
747
748 let files = get_git_modified_files(
749 &config.git_config(),
750 Some(dir.as_std_path()),
751 &vec!["rs", "stderr", "fixed"],
752 )?;
753 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
755
756 let all_paths = [&files[..], &untracked_files[..]].concat();
757 let full_paths = {
758 let mut full_paths: Vec<Utf8PathBuf> = all_paths
759 .into_iter()
760 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
761 .filter_map(
762 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
763 )
764 .collect();
765 full_paths.dedup();
766 full_paths.sort_unstable();
767 full_paths
768 };
769 Ok(full_paths)
770}
771
772fn collect_tests_from_dir(
775 cx: &TestCollectorCx,
776 dir: &Utf8Path,
777 relative_dir_path: &Utf8Path,
778) -> io::Result<TestCollector> {
779 if dir.join("compiletest-ignore-dir").exists() {
781 return Ok(TestCollector::new());
782 }
783
784 let mut components = dir.components().rev();
785 if let Some(Utf8Component::Normal(last)) = components.next()
786 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
787 && let Some(Utf8Component::Normal(parent)) = components.next()
788 && parent == "tests"
789 && let Ok(backend) = CodegenBackend::try_from(backend)
790 && backend != cx.config.default_codegen_backend
791 {
792 warning!(
794 "Ignoring tests in `{dir}` because they don't match the configured codegen \
795 backend (`{}`)",
796 cx.config.default_codegen_backend.as_str(),
797 );
798 return Ok(TestCollector::new());
799 }
800
801 if cx.config.mode == TestMode::RunMake {
803 let mut collector = TestCollector::new();
804 if dir.join("rmake.rs").exists() {
805 let paths = TestPaths {
806 file: dir.to_path_buf(),
807 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
808 };
809 make_test(cx, &mut collector, &paths);
810 return Ok(collector);
812 }
813 }
814
815 let build_dir = output_relative_path(&cx.config, relative_dir_path);
822 fs::create_dir_all(&build_dir).unwrap();
823
824 fs::read_dir(dir.as_std_path())?
829 .par_bridge()
830 .map(|file| {
831 let mut collector = TestCollector::new();
832 let file = file?;
833 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
834 let file_name = file_path.file_name().unwrap();
835
836 if is_test(file_name)
837 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
838 {
839 debug!(%file_path, "found test file");
841
842 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
844 collector.found_path_stems.insert(rel_test_path);
845
846 let paths =
847 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
848 make_test(cx, &mut collector, &paths);
849 } else if file_path.is_dir() {
850 let relative_file_path = relative_dir_path.join(file_name);
852 if file_name != "auxiliary" {
853 debug!(%file_path, "found directory");
854 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
855 }
856 } else {
857 debug!(%file_path, "found other file/directory");
858 }
859 Ok(collector)
860 })
861 .reduce(
862 || Ok(TestCollector::new()),
863 |a, b| {
864 let mut a = a?;
865 a.merge(b?);
866 Ok(a)
867 },
868 )
869}
870
871pub fn is_test(file_name: &str) -> bool {
873 if !file_name.ends_with(".rs") {
874 return false;
875 }
876
877 let invalid_prefixes = &[".", "#", "~"];
879 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
880}
881
882fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
885 let test_path = if cx.config.mode == TestMode::RunMake {
889 testpaths.file.join("rmake.rs")
890 } else {
891 testpaths.file.clone()
892 };
893
894 let early_props = EarlyProps::from_file(&cx.config, &test_path);
896
897 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
904 vec![None]
905 } else {
906 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
907 };
908
909 collector.tests.extend(revisions.into_iter().map(|revision| {
912 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
914 let test_name = make_test_name(&cx.config, testpaths, revision);
915 let mut desc = make_test_description(
919 &cx.config,
920 &cx.cache,
921 test_name,
922 &test_path,
923 src_file,
924 revision,
925 &mut collector.poisoned,
926 );
927
928 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
931 desc.ignore = true;
932 desc.ignore_message = Some("up-to-date".into());
936 }
937
938 let config = Arc::clone(&cx.config);
939 let testpaths = testpaths.clone();
940 let revision = revision.map(str::to_owned);
941
942 CollectedTest { desc, config, testpaths, revision }
943 }));
944}
945
946fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
949 output_base_dir(config, testpaths, revision).join("stamp")
950}
951
952fn files_related_to_test(
957 config: &Config,
958 testpaths: &TestPaths,
959 props: &EarlyProps,
960 revision: Option<&str>,
961) -> Vec<Utf8PathBuf> {
962 let mut related = vec![];
963
964 if testpaths.file.is_dir() {
965 for entry in WalkDir::new(&testpaths.file) {
967 let path = entry.unwrap().into_path();
968 if path.is_file() {
969 related.push(Utf8PathBuf::try_from(path).unwrap());
970 }
971 }
972 } else {
973 related.push(testpaths.file.clone());
974 }
975
976 for aux in props.aux.all_aux_path_strings() {
977 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
979 related.push(path);
980 }
981
982 for extension in UI_EXTENSIONS {
984 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
985 related.push(path);
986 }
987
988 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
990
991 related
992}
993
994fn is_up_to_date(
1000 cx: &TestCollectorCx,
1001 testpaths: &TestPaths,
1002 props: &EarlyProps,
1003 revision: Option<&str>,
1004) -> bool {
1005 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1006 let contents = match fs::read_to_string(&stamp_file_path) {
1008 Ok(f) => f,
1009 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1010 Err(_) => return false,
1012 };
1013 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1014 if contents != expected_hash {
1015 return false;
1018 }
1019
1020 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1023 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1024 inputs_stamp.add_path(&path);
1025 }
1026
1027 inputs_stamp < Stamp::from_path(&stamp_file_path)
1030}
1031
1032#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1034struct Stamp {
1035 time: SystemTime,
1036}
1037
1038impl Stamp {
1039 fn from_path(path: &Utf8Path) -> Self {
1041 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1042 stamp.add_path(path);
1043 stamp
1044 }
1045
1046 fn add_path(&mut self, path: &Utf8Path) {
1049 let modified = fs::metadata(path.as_std_path())
1050 .and_then(|metadata| metadata.modified())
1051 .unwrap_or(SystemTime::UNIX_EPOCH);
1052 self.time = self.time.max(modified);
1053 }
1054
1055 fn add_dir(&mut self, path: &Utf8Path) {
1059 let path = path.as_std_path();
1060 for entry in WalkDir::new(path) {
1061 let entry = entry.unwrap();
1062 if entry.file_type().is_file() {
1063 let modified = entry
1064 .metadata()
1065 .ok()
1066 .and_then(|metadata| metadata.modified().ok())
1067 .unwrap_or(SystemTime::UNIX_EPOCH);
1068 self.time = self.time.max(modified);
1069 }
1070 }
1071 }
1072}
1073
1074fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1076 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1078 let debugger = match config.debugger {
1079 Some(d) => format!("-{}", d),
1080 None => String::new(),
1081 };
1082 let mode_suffix = match config.compare_mode {
1083 Some(ref mode) => format!(" ({})", mode.to_str()),
1084 None => String::new(),
1085 };
1086
1087 format!(
1088 "[{}{}{}] {}{}",
1089 config.mode,
1090 debugger,
1091 mode_suffix,
1092 path,
1093 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1094 )
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
1136pub fn 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}