1#![crate_name = "compiletest"]
2#![warn(unreachable_pub)]
3
4#[cfg(test)]
5mod tests;
6
7pub mod cli;
9pub mod rustdoc_gui_test;
10
11mod common;
12mod debuggers;
13mod diagnostics;
14mod directives;
15mod edition;
16mod errors;
17mod executor;
18mod json;
19mod output_capture;
20mod panic_hook;
21mod raise_fd_limit;
22mod read2;
23mod runtest;
24mod util;
25
26use core::panic;
27use std::collections::HashSet;
28use std::fmt::Write;
29use std::io::{self, ErrorKind};
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::{AuxProps, DirectivesCache, FileDirectives};
47use crate::edition::parse_edition;
48use crate::executor::CollectedTest;
49
50fn parse_config(args: Vec<String>) -> Config {
56 let mut opts = Options::new();
57 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
58 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
59 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
60 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
61 .optopt(
62 "",
63 "stage0-rustc-path",
64 "path to rustc to use for compiling run-make recipes",
65 "PATH",
66 )
67 .optopt(
68 "",
69 "query-rustc-path",
70 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
71 "PATH",
72 )
73 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
74 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
75 .reqopt("", "python", "path to python to use for doc tests", "PATH")
76 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
77 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
78 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
79 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
80 .reqopt("", "src-root", "directory containing sources", "PATH")
81 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
82 .reqopt("", "build-root", "path to root build directory", "PATH")
83 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
84 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
85 .reqopt("", "stage", "stage number under test", "N")
86 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
87 .reqopt(
88 "",
89 "mode",
90 "which sort of compile tests to run",
91 "pretty | debug-info | codegen | rustdoc-html \
92 | rustdoc-json | codegen-units | incremental | run-make | ui \
93 | rustdoc-js | mir-opt | assembly | crashes",
94 )
95 .reqopt(
96 "",
97 "suite",
98 "which suite of compile tests to run. used for nicer error reporting.",
99 "SUITE",
100 )
101 .optopt(
102 "",
103 "pass",
104 "force {check,build,run}-pass tests to this mode.",
105 "check | build | run",
106 )
107 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
108 .optflag("", "ignored", "run tests marked as ignored")
109 .optflag("", "has-enzyme", "run tests that require enzyme")
110 .optflag("", "has-offload", "run tests that require offload")
111 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
112 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
113 .optflag("", "with-std-remap-debuginfo", "whether std was built with remapping")
114 .optmulti(
115 "",
116 "skip",
117 "skip tests matching SUBSTRING. Can be passed multiple times",
118 "SUBSTRING",
119 )
120 .optflag("", "exact", "filters match exactly")
121 .optopt(
122 "",
123 "runner",
124 "supervisor program to run tests under \
125 (eg. emulator, valgrind)",
126 "PROGRAM",
127 )
128 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
129 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
130 .optflag(
131 "",
132 "rust-randomized-layout",
133 "set this when rustc/stdlib were compiled with randomized layouts",
134 )
135 .optflag("", "optimize-tests", "run tests with optimizations enabled")
136 .optflag("", "verbose", "run tests verbosely, showing all output")
137 .optflag(
138 "",
139 "verbose-run-make-subprocess-output",
140 "show verbose subprocess output for successful run-make tests",
141 )
142 .optflag(
143 "",
144 "bless",
145 "overwrite stderr/stdout files instead of complaining about a mismatch",
146 )
147 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
148 .optopt("", "target", "the target to build for", "TARGET")
149 .optopt("", "host", "the host to build for", "HOST")
150 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
151 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
152 .optopt("", "lldb", "path to LLDB to use for LLDB debuginfo tests", "PATH")
153 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
154 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
155 .optflag("", "system-llvm", "is LLVM the system LLVM")
156 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
157 .optopt("", "adb-path", "path to the android debugger", "PATH")
158 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
159 .reqopt("", "cc", "path to a C compiler", "PATH")
160 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
161 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
162 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
163 .optopt("", "ar", "path to an archiver", "PATH")
164 .optopt("", "target-linker", "path to a linker for the target", "PATH")
165 .optopt("", "host-linker", "path to a linker for the host", "PATH")
166 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
167 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
168 .optopt("", "nodejs", "the name of nodejs", "PATH")
169 .optopt("", "npm", "the name of npm", "PATH")
170 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
171 .optopt(
172 "",
173 "compare-mode",
174 "mode describing what file the actual ui output will be compared to",
175 "COMPARE MODE",
176 )
177 .optflag(
178 "",
179 "rustfix-coverage",
180 "enable this to generate a Rustfix coverage file, which is saved in \
181 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
182 )
183 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
184 .optflag("", "only-modified", "only run tests that result been modified")
185 .optflag("", "nocapture", "")
187 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
188 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
189 .optflag("h", "help", "show this message")
190 .reqopt("", "channel", "current Rust channel", "CHANNEL")
191 .optflag(
192 "",
193 "git-hash",
194 "run tests which rely on commit version being compiled into the binaries",
195 )
196 .optopt("", "edition", "default Rust edition", "EDITION")
197 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
198 .reqopt(
199 "",
200 "git-merge-commit-email",
201 "email address used for finding merge commits",
202 "EMAIL",
203 )
204 .optopt(
205 "",
206 "compiletest-diff-tool",
207 "What custom diff tool to use for displaying compiletest tests.",
208 "COMMAND",
209 )
210 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
211 .optopt(
212 "",
213 "debugger",
214 "only test a specific debugger in debuginfo tests",
215 "gdb | lldb | cdb",
216 )
217 .optopt(
218 "",
219 "default-codegen-backend",
220 "the codegen backend currently used",
221 "CODEGEN BACKEND NAME",
222 )
223 .optopt(
224 "",
225 "override-codegen-backend",
226 "the codegen backend to use instead of the default one",
227 "CODEGEN BACKEND [NAME | PATH]",
228 )
229 .optflag("", "bypass-ignore-backends", "ignore `//@ ignore-backends` directives")
230 .reqopt("", "jobs", "number of parallel jobs bootstrap was configured with", "JOBS")
231 .optopt(
232 "",
233 "parallel-frontend-threads",
234 "number of parallel threads to use for the frontend when building test artifacts",
235 "THREADS_COUNT",
236 )
237 .optopt("", "iteration-count", "number of times to execute each test", "COUNT");
238
239 let (argv0, args_) = args.split_first().unwrap();
240 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
241 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
242 println!("{}", opts.usage(&message));
243 println!();
244 panic!()
245 }
246
247 let matches = &match opts.parse(args_) {
248 Ok(m) => m,
249 Err(f) => panic!("{:?}", f),
250 };
251
252 if matches.opt_present("h") || matches.opt_present("help") {
253 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
254 println!("{}", opts.usage(&message));
255 println!();
256 panic!()
257 }
258
259 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
260 if path.is_relative() {
261 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
262 } else {
263 path
264 }
265 }
266
267 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
268 match m.opt_str(nm) {
269 Some(s) => Utf8PathBuf::from(&s),
270 None => panic!("no option (=path) found for {}", nm),
271 }
272 }
273
274 let host = matches.opt_str("host").expect("`--host` must be unconditionally specified");
275 let target = matches.opt_str("target").expect("`--target` must be unconditionally specified");
276
277 let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from);
278
279 let adb_path = matches.opt_str("adb-path").map(Utf8PathBuf::from);
280 let adb_test_dir = matches.opt_str("adb-test-dir").map(Utf8PathBuf::from);
281 let adb_device_status = target.contains("android") && adb_test_dir.is_some();
282
283 let cdb = matches.opt_str("cdb").map(Utf8PathBuf::from);
285 let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
286 let gdb = matches.opt_str("gdb").map(Utf8PathBuf::from);
288 let gdb_version = gdb.as_deref().and_then(debuggers::query_gdb_version);
289 let lldb = matches.opt_str("lldb").map(Utf8PathBuf::from);
291 let lldb_version =
292 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
293 let llvm_version =
297 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
298 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
299 );
300
301 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
302 Some(backend) => match CodegenBackend::try_from(backend) {
303 Ok(backend) => backend,
304 Err(error) => {
305 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
306 }
307 },
308 None => CodegenBackend::Llvm,
310 };
311 let override_codegen_backend = matches.opt_str("override-codegen-backend");
312
313 let run_ignored = matches.opt_present("ignored");
314 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
315 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
316 let with_std_remap_debuginfo = matches.opt_present("with-std-remap-debuginfo");
317 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
318 let has_enzyme = matches.opt_present("has-enzyme");
319 let has_offload = matches.opt_present("has-offload");
320 let filters = if mode == TestMode::RunMake {
321 matches
322 .free
323 .iter()
324 .map(|f| {
325 let path = Utf8Path::new(f);
331 let mut iter = path.iter().skip(1);
332
333 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
334 path.parent().unwrap().to_string()
337 } else {
338 f.to_string()
339 }
340 })
341 .collect::<Vec<_>>()
342 } else {
343 matches.free.clone()
350 };
351 let compare_mode = matches.opt_str("compare-mode").map(|s| {
352 s.parse().unwrap_or_else(|_| {
353 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
354 panic!(
355 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
356 variants.join(", ")
357 );
358 })
359 });
360 if matches.opt_present("nocapture") {
361 panic!("`--nocapture` is deprecated; please use `--no-capture`");
362 }
363
364 let stage = match matches.opt_str("stage") {
365 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
366 None => panic!("`--stage` is required"),
367 };
368
369 let src_root = opt_path(matches, "src-root");
370 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
371 assert!(
372 src_test_suite_root.starts_with(&src_root),
373 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
374 src_root,
375 src_test_suite_root
376 );
377
378 let build_root = opt_path(matches, "build-root");
379 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
380 assert!(build_test_suite_root.starts_with(&build_root));
381
382 let jobs = match matches.opt_str("jobs") {
383 Some(jobs) => jobs.parse::<u32>().expect("expected `--jobs` to be an `u32`"),
384 None => panic!("`--jobs` is required"),
385 };
386
387 let parallel_frontend_threads = match matches.opt_str("parallel-frontend-threads") {
388 Some(threads) => {
389 threads.parse::<u32>().expect("expected `--parallel-frontend-threads` to be an `u32`")
390 }
391 None => Config::DEFAULT_PARALLEL_FRONTEND_THREADS,
392 };
393 let iteration_count = match matches.opt_str("iteration-count") {
394 Some(count) => {
395 count.parse::<u32>().expect("expected `--iteration-count` to be a positive integer")
396 }
397 None => Config::DEFAULT_ITERATION_COUNT,
398 };
399 assert!(iteration_count > 0, "`--iteration-count` must be a positive integer");
400
401 Config {
402 bless: matches.opt_present("bless"),
403 fail_fast: matches.opt_present("fail-fast")
404 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
405
406 host_compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
407 target_run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
408 rustc_path: opt_path(matches, "rustc-path"),
409 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
410 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
411 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
412 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
413 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
414 python: matches.opt_str("python").unwrap(),
415 jsondocck_path: matches.opt_str("jsondocck-path").map(Utf8PathBuf::from),
416 jsondoclint_path: matches.opt_str("jsondoclint-path").map(Utf8PathBuf::from),
417 run_clang_based_tests_with: matches
418 .opt_str("run-clang-based-tests-with")
419 .map(Utf8PathBuf::from),
420 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
421 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
422
423 src_root,
424 src_test_suite_root,
425
426 build_root,
427 build_test_suite_root,
428
429 sysroot_base: opt_path(matches, "sysroot-base"),
430
431 stage,
432 stage_id: matches.opt_str("stage-id").unwrap(),
433
434 mode,
435 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
436 debugger: matches.opt_str("debugger").map(|debugger| {
437 debugger
438 .parse::<Debugger>()
439 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
440 }),
441 run_ignored,
442 with_rustc_debug_assertions,
443 with_std_debug_assertions,
444 with_std_remap_debuginfo,
445 filters,
446 skip: matches.opt_strs("skip"),
447 filter_exact: matches.opt_present("exact"),
448 force_pass_mode: matches.opt_str("pass").map(|mode| {
449 mode.parse::<PassMode>()
450 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
451 }),
452 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
454 "auto" => None,
455 "always" => Some(true),
456 "never" => Some(false),
457 _ => panic!("unknown `--run` option `{}` given", mode),
458 }),
459 runner: matches.opt_str("runner"),
460 host_rustcflags: matches.opt_strs("host-rustcflags"),
461 target_rustcflags: matches.opt_strs("target-rustcflags"),
462 optimize_tests: matches.opt_present("optimize-tests"),
463 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
464 target,
465 host,
466 cdb,
467 cdb_version,
468 gdb,
469 gdb_version,
470 lldb,
471 lldb_version,
472 llvm_version,
473 system_llvm: matches.opt_present("system-llvm"),
474 android_cross_path,
475 adb_path,
476 adb_test_dir,
477 adb_device_status,
478 verbose: matches.opt_present("verbose"),
479 verbose_run_make_subprocess_output: matches
480 .opt_present("verbose-run-make-subprocess-output"),
481 only_modified: matches.opt_present("only-modified"),
482 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
483 compare_mode,
484 rustfix_coverage: matches.opt_present("rustfix-coverage"),
485 has_enzyme,
486 has_offload,
487 channel: matches.opt_str("channel").unwrap(),
488 git_hash: matches.opt_present("git-hash"),
489 edition: matches.opt_str("edition").as_deref().map(parse_edition),
490
491 cc: matches.opt_str("cc").unwrap(),
492 cxx: matches.opt_str("cxx").unwrap(),
493 cflags: matches.opt_str("cflags").unwrap(),
494 cxxflags: matches.opt_str("cxxflags").unwrap(),
495 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
496 target_linker: matches.opt_str("target-linker"),
497 host_linker: matches.opt_str("host-linker"),
498 llvm_components: matches.opt_str("llvm-components").unwrap(),
499 nodejs: matches.opt_str("nodejs").map(Utf8PathBuf::from),
500
501 force_rerun: matches.opt_present("force-rerun"),
502
503 target_cfgs: OnceLock::new(),
504 builtin_cfg_names: OnceLock::new(),
505 supported_crate_types: OnceLock::new(),
506
507 capture: !matches.opt_present("no-capture"),
508
509 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
510 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
511
512 profiler_runtime: matches.opt_present("profiler-runtime"),
513
514 diff_command: matches.opt_str("compiletest-diff-tool"),
515
516 minicore_path: opt_path(matches, "minicore-path"),
517
518 default_codegen_backend,
519 override_codegen_backend,
520 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
521
522 jobs,
523
524 parallel_frontend_threads,
525 iteration_count,
526 }
527}
528
529fn run_tests(config: Arc<Config>) {
531 debug!(?config, "run_tests");
532
533 panic_hook::install_panic_hook();
534
535 if config.rustfix_coverage {
539 let mut coverage_file_path = config.build_test_suite_root.clone();
540 coverage_file_path.push("rustfix_missing_coverage.txt");
541 if coverage_file_path.exists() {
542 if let Err(e) = fs::remove_file(&coverage_file_path) {
543 panic!("Could not delete {} due to {}", coverage_file_path, e)
544 }
545 }
546 }
547
548 unsafe {
552 raise_fd_limit::raise_fd_limit();
553 }
554 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
559
560 let mut configs = Vec::new();
561 if let TestMode::DebugInfo = config.mode {
562 if !config.target.contains("emscripten") {
564 match config.debugger {
565 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
566 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
567 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
568 None => {
573 configs.extend(debuggers::configure_cdb(&config));
574 configs.extend(debuggers::configure_gdb(&config));
575 configs.extend(debuggers::configure_lldb(&config));
576 }
577 }
578 }
579 } else {
580 configs.push(config.clone());
581 };
582
583 let mut tests = Vec::new();
586 for c in configs {
587 tests.extend(collect_and_make_tests(c));
588 }
589
590 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
591
592 let ok = executor::run_tests(&config, tests);
596
597 if !ok {
599 let mut msg = String::from("Some tests failed in compiletest");
607 write!(msg, " suite={}", config.suite).unwrap();
608
609 if let Some(compare_mode) = config.compare_mode.as_ref() {
610 write!(msg, " compare_mode={}", compare_mode).unwrap();
611 }
612
613 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
614 write!(msg, " pass_mode={}", pass_mode).unwrap();
615 }
616
617 write!(msg, " mode={}", config.mode).unwrap();
618 write!(msg, " host={}", config.host).unwrap();
619 write!(msg, " target={}", config.target).unwrap();
620
621 println!("{msg}");
622
623 std::process::exit(1);
624 }
625}
626
627struct TestCollectorCx {
629 config: Arc<Config>,
630 cache: DirectivesCache,
631 common_inputs_stamp: Stamp,
632 modified_tests: Vec<Utf8PathBuf>,
633}
634
635struct TestCollector {
637 tests: Vec<CollectedTest>,
638 found_path_stems: HashSet<Utf8PathBuf>,
639 poisoned: bool,
640}
641
642impl TestCollector {
643 fn new() -> Self {
644 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
645 }
646
647 fn merge(&mut self, mut other: Self) {
648 self.tests.append(&mut other.tests);
649 self.found_path_stems.extend(other.found_path_stems);
650 self.poisoned |= other.poisoned;
651 }
652}
653
654fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
664 debug!("making tests from {}", config.src_test_suite_root);
665 let common_inputs_stamp = common_inputs_stamp(&config);
666 let modified_tests =
667 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
668 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
669 });
670 let cache = DirectivesCache::load(&config);
671
672 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
673 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
674 .unwrap_or_else(|reason| {
675 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
676 });
677
678 let TestCollector { tests, found_path_stems, poisoned } = collector;
679
680 if poisoned {
681 eprintln!();
682 panic!("there are errors in tests");
683 }
684
685 check_for_overlapping_test_paths(&found_path_stems);
686
687 tests
688}
689
690fn common_inputs_stamp(config: &Config) -> Stamp {
698 let src_root = &config.src_root;
699
700 let mut stamp = Stamp::from_path(&config.rustc_path);
701
702 let pretty_printer_files = [
704 "src/etc/rust_types.py",
705 "src/etc/gdb_load_rust_pretty_printers.py",
706 "src/etc/gdb_lookup.py",
707 "src/etc/gdb_providers.py",
708 "src/etc/lldb_batchmode.py",
709 "src/etc/lldb_lookup.py",
710 "src/etc/lldb_providers.py",
711 ];
712 for file in &pretty_printer_files {
713 let path = src_root.join(file);
714 stamp.add_path(&path);
715 }
716
717 stamp.add_dir(&src_root.join("src/etc/natvis"));
718
719 stamp.add_dir(&config.target_run_lib_path);
720
721 if let Some(ref rustdoc_path) = config.rustdoc_path {
722 stamp.add_path(&rustdoc_path);
723 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
724 }
725
726 if let Some(coverage_dump_path) = &config.coverage_dump_path {
729 stamp.add_path(coverage_dump_path)
730 }
731
732 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
733
734 stamp.add_dir(&src_root.join("src/tools/compiletest"));
736
737 stamp
738}
739
740fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
745 if !config.only_modified {
748 return Ok(vec![]);
749 }
750
751 let files = get_git_modified_files(
752 &config.git_config(),
753 Some(dir.as_std_path()),
754 &vec!["rs", "stderr", "fixed"],
755 )?;
756 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
758
759 let all_paths = [&files[..], &untracked_files[..]].concat();
760 let full_paths = {
761 let mut full_paths: Vec<Utf8PathBuf> = all_paths
762 .into_iter()
763 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
764 .filter_map(
765 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
766 )
767 .collect();
768 full_paths.dedup();
769 full_paths.sort_unstable();
770 full_paths
771 };
772 Ok(full_paths)
773}
774
775fn collect_tests_from_dir(
778 cx: &TestCollectorCx,
779 dir: &Utf8Path,
780 relative_dir_path: &Utf8Path,
781) -> io::Result<TestCollector> {
782 if dir.join("compiletest-ignore-dir").exists() {
784 return Ok(TestCollector::new());
785 }
786
787 let mut components = dir.components().rev();
788 if let Some(Utf8Component::Normal(last)) = components.next()
789 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
790 && let Some(Utf8Component::Normal(parent)) = components.next()
791 && parent == "tests"
792 && let Ok(backend) = CodegenBackend::try_from(backend)
793 && backend != cx.config.default_codegen_backend
794 {
795 warning!(
797 "Ignoring tests in `{dir}` because they don't match the configured codegen \
798 backend (`{}`)",
799 cx.config.default_codegen_backend.as_str(),
800 );
801 return Ok(TestCollector::new());
802 }
803
804 if cx.config.mode == TestMode::RunMake {
806 let mut collector = TestCollector::new();
807 if dir.join("rmake.rs").exists() {
808 let paths = TestPaths {
809 file: dir.to_path_buf(),
810 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
811 };
812 make_test(cx, &mut collector, &paths);
813 return Ok(collector);
815 }
816 }
817
818 let build_dir = output_relative_path(&cx.config, relative_dir_path);
825 fs::create_dir_all(&build_dir).unwrap();
826
827 fs::read_dir(dir.as_std_path())?
832 .par_bridge()
833 .map(|file| {
834 let mut collector = TestCollector::new();
835 let file = file?;
836 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
837 let file_name = file_path.file_name().unwrap();
838
839 if is_test(file_name)
840 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
841 {
842 debug!(%file_path, "found test file");
844
845 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
847 collector.found_path_stems.insert(rel_test_path);
848
849 let paths =
850 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
851 make_test(cx, &mut collector, &paths);
852 } else if file_path.is_dir() {
853 let relative_file_path = relative_dir_path.join(file_name);
855 if file_name != "auxiliary" {
856 debug!(%file_path, "found directory");
857 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
858 }
859 } else {
860 debug!(%file_path, "found other file/directory");
861 }
862 Ok(collector)
863 })
864 .reduce(
865 || Ok(TestCollector::new()),
866 |a, b| {
867 let mut a = a?;
868 a.merge(b?);
869 Ok(a)
870 },
871 )
872}
873
874fn is_test(file_name: &str) -> bool {
876 if !file_name.ends_with(".rs") {
877 return false;
878 }
879
880 let invalid_prefixes = &[".", "#", "~"];
882 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
883}
884
885fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
888 let test_path = if cx.config.mode == TestMode::RunMake {
892 testpaths.file.join("rmake.rs")
893 } else {
894 testpaths.file.clone()
895 };
896
897 let file_contents =
899 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
900 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
901
902 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
903 panic!("directives check failed:\n{message}");
906 }
907 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
908
909 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
916 vec![None]
917 } else {
918 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
919 };
920
921 collector.tests.extend(revisions.into_iter().map(|revision| {
924 let (test_name, filterable_path) =
926 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
927
928 let mut aux_props = AuxProps::default();
931
932 let mut desc = make_test_description(
936 &cx.config,
937 &cx.cache,
938 test_name,
939 &test_path,
940 &filterable_path,
941 &file_directives,
942 revision,
943 &mut collector.poisoned,
944 &mut aux_props,
945 );
946
947 if !desc.ignore
950 && !cx.config.force_rerun
951 && is_up_to_date(cx, testpaths, &aux_props, revision)
952 {
953 desc.ignore = true;
954 desc.ignore_message = Some("up-to-date".into());
958 }
959
960 let config = Arc::clone(&cx.config);
961 let testpaths = testpaths.clone();
962 let revision = revision.map(str::to_owned);
963
964 CollectedTest { desc, config, testpaths, revision }
965 }));
966}
967
968fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
971 output_base_dir(config, testpaths, revision).join("stamp")
972}
973
974fn files_related_to_test(
979 config: &Config,
980 testpaths: &TestPaths,
981 aux_props: &AuxProps,
982 revision: Option<&str>,
983) -> Vec<Utf8PathBuf> {
984 let mut related = vec![];
985
986 if testpaths.file.is_dir() {
987 for entry in WalkDir::new(&testpaths.file) {
989 let path = entry.unwrap().into_path();
990 if path.is_file() {
991 related.push(Utf8PathBuf::try_from(path).unwrap());
992 }
993 }
994 } else {
995 related.push(testpaths.file.clone());
996 }
997
998 for aux in aux_props.all_aux_path_strings() {
999 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
1004 related.push(path);
1005 }
1006
1007 for extension in UI_EXTENSIONS {
1009 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1010 related.push(path);
1011 }
1012
1013 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1015
1016 related
1017}
1018
1019fn is_up_to_date(
1025 cx: &TestCollectorCx,
1026 testpaths: &TestPaths,
1027 aux_props: &AuxProps,
1028 revision: Option<&str>,
1029) -> bool {
1030 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1031 let contents = match fs::read_to_string(&stamp_file_path) {
1033 Ok(f) => f,
1034 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1035 Err(_) => return false,
1037 };
1038 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1039 if contents != expected_hash {
1040 return false;
1043 }
1044
1045 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1048 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1049 inputs_stamp.add_path(&path);
1050 }
1051
1052 inputs_stamp < Stamp::from_path(&stamp_file_path)
1055}
1056
1057#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1059struct Stamp {
1060 time: SystemTime,
1061}
1062
1063impl Stamp {
1064 fn from_path(path: &Utf8Path) -> Self {
1066 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1067 stamp.add_path(path);
1068 stamp
1069 }
1070
1071 fn add_path(&mut self, path: &Utf8Path) {
1074 let modified = fs::metadata(path.as_std_path())
1075 .and_then(|metadata| metadata.modified())
1076 .unwrap_or(SystemTime::UNIX_EPOCH);
1077 self.time = self.time.max(modified);
1078 }
1079
1080 fn add_dir(&mut self, path: &Utf8Path) {
1084 let path = path.as_std_path();
1085 for entry in WalkDir::new(path) {
1086 let entry = entry.unwrap();
1087 if entry.file_type().is_file() {
1088 let modified = entry
1089 .metadata()
1090 .ok()
1091 .and_then(|metadata| metadata.modified().ok())
1092 .unwrap_or(SystemTime::UNIX_EPOCH);
1093 self.time = self.time.max(modified);
1094 }
1095 }
1096 }
1097}
1098
1099fn make_test_name_and_filterable_path(
1101 config: &Config,
1102 testpaths: &TestPaths,
1103 revision: Option<&str>,
1104) -> (String, Utf8PathBuf) {
1105 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1107 let debugger = match config.debugger {
1108 Some(d) => format!("-{}", d),
1109 None => String::new(),
1110 };
1111 let mode_suffix = match config.compare_mode {
1112 Some(ref mode) => format!(" ({})", mode.to_str()),
1113 None => String::new(),
1114 };
1115
1116 let name = format!(
1117 "[{}{}{}] {}{}",
1118 config.mode,
1119 debugger,
1120 mode_suffix,
1121 path,
1122 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1123 );
1124
1125 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1129 filterable_path = filterable_path.components().skip(1).collect();
1131
1132 (name, filterable_path)
1133}
1134
1135fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1153 let mut collisions = Vec::new();
1154 for path in found_path_stems {
1155 for ancestor in path.ancestors().skip(1) {
1156 if found_path_stems.contains(ancestor) {
1157 collisions.push((path, ancestor));
1158 }
1159 }
1160 }
1161 if !collisions.is_empty() {
1162 collisions.sort();
1163 let collisions: String = collisions
1164 .into_iter()
1165 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1166 .collect();
1167 panic!(
1168 "{collisions}\n\
1169 Tests cannot have overlapping names. Make sure they use unique prefixes."
1170 );
1171 }
1172}
1173
1174fn early_config_check(config: &Config) {
1175 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1176 let actioned = if config.bless { "blessed" } else { "checked" };
1177 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1178 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1179 }
1180
1181 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1183 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1184 }
1185}