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 .optflag("", "bypass-ignore-backends", "ignore `//@ ignore-backends` directives");
222
223 let (argv0, args_) = args.split_first().unwrap();
224 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
225 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
226 println!("{}", opts.usage(&message));
227 println!();
228 panic!()
229 }
230
231 let matches = &match opts.parse(args_) {
232 Ok(m) => m,
233 Err(f) => panic!("{:?}", f),
234 };
235
236 if matches.opt_present("h") || matches.opt_present("help") {
237 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
238 println!("{}", opts.usage(&message));
239 println!();
240 panic!()
241 }
242
243 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
244 if path.is_relative() {
245 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
246 } else {
247 path
248 }
249 }
250
251 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
252 match m.opt_str(nm) {
253 Some(s) => Utf8PathBuf::from(&s),
254 None => panic!("no option (=path) found for {}", nm),
255 }
256 }
257
258 let target = opt_str2(matches.opt_str("target"));
259 let android_cross_path = opt_path(matches, "android-cross-path");
260 let cdb = debuggers::discover_cdb(matches.opt_str("cdb"), &target);
262 let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
263 let gdb = debuggers::discover_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
265 let gdb_version = gdb.as_deref().and_then(debuggers::query_gdb_version);
266 let lldb_version =
268 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
269 let color = match matches.opt_str("color").as_deref() {
270 Some("auto") | None => ColorConfig::AutoColor,
271 Some("always") => ColorConfig::AlwaysColor,
272 Some("never") => ColorConfig::NeverColor,
273 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
274 };
275 let llvm_version =
279 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
280 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
281 );
282
283 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
284 Some(backend) => match CodegenBackend::try_from(backend) {
285 Ok(backend) => backend,
286 Err(error) => {
287 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
288 }
289 },
290 None => CodegenBackend::Llvm,
292 };
293 let override_codegen_backend = matches.opt_str("override-codegen-backend");
294
295 let run_ignored = matches.opt_present("ignored");
296 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
297 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
298 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
299 let has_html_tidy = if mode == TestMode::Rustdoc {
300 Command::new("tidy")
301 .arg("--version")
302 .stdout(Stdio::null())
303 .status()
304 .map_or(false, |status| status.success())
305 } else {
306 false
308 };
309 let has_enzyme = matches.opt_present("has-enzyme");
310 let filters = if mode == TestMode::RunMake {
311 matches
312 .free
313 .iter()
314 .map(|f| {
315 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() {
324 path.parent().unwrap().to_string()
327 } else {
328 f.to_string()
329 }
330 })
331 .collect::<Vec<_>>()
332 } else {
333 matches.free.clone()
340 };
341 let compare_mode = matches.opt_str("compare-mode").map(|s| {
342 s.parse().unwrap_or_else(|_| {
343 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
344 panic!(
345 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
346 variants.join(", ")
347 );
348 })
349 });
350 if matches.opt_present("nocapture") {
351 panic!("`--nocapture` is deprecated; please use `--no-capture`");
352 }
353
354 let stage = match matches.opt_str("stage") {
355 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
356 None => panic!("`--stage` is required"),
357 };
358
359 let src_root = opt_path(matches, "src-root");
360 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
361 assert!(
362 src_test_suite_root.starts_with(&src_root),
363 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
364 src_root,
365 src_test_suite_root
366 );
367
368 let build_root = opt_path(matches, "build-root");
369 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
370 assert!(build_test_suite_root.starts_with(&build_root));
371
372 Config {
373 bless: matches.opt_present("bless"),
374 fail_fast: matches.opt_present("fail-fast")
375 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
376
377 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
378 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
379 rustc_path: opt_path(matches, "rustc-path"),
380 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
381 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
382 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
383 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
384 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
385 python: matches.opt_str("python").unwrap(),
386 jsondocck_path: matches.opt_str("jsondocck-path"),
387 jsondoclint_path: matches.opt_str("jsondoclint-path"),
388 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
389 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
390 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
391
392 src_root,
393 src_test_suite_root,
394
395 build_root,
396 build_test_suite_root,
397
398 sysroot_base: opt_path(matches, "sysroot-base"),
399
400 stage,
401 stage_id: matches.opt_str("stage-id").unwrap(),
402
403 mode,
404 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
405 debugger: matches.opt_str("debugger").map(|debugger| {
406 debugger
407 .parse::<Debugger>()
408 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
409 }),
410 run_ignored,
411 with_rustc_debug_assertions,
412 with_std_debug_assertions,
413 filters,
414 skip: matches.opt_strs("skip"),
415 filter_exact: matches.opt_present("exact"),
416 force_pass_mode: matches.opt_str("pass").map(|mode| {
417 mode.parse::<PassMode>()
418 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
419 }),
420 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
422 "auto" => None,
423 "always" => Some(true),
424 "never" => Some(false),
425 _ => panic!("unknown `--run` option `{}` given", mode),
426 }),
427 runner: matches.opt_str("runner"),
428 host_rustcflags: matches.opt_strs("host-rustcflags"),
429 target_rustcflags: matches.opt_strs("target-rustcflags"),
430 optimize_tests: matches.opt_present("optimize-tests"),
431 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
432 target,
433 host: opt_str2(matches.opt_str("host")),
434 cdb,
435 cdb_version,
436 gdb,
437 gdb_version,
438 lldb_version,
439 llvm_version,
440 system_llvm: matches.opt_present("system-llvm"),
441 android_cross_path,
442 adb_path: opt_str2(matches.opt_str("adb-path")),
443 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
444 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
445 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
446 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
447 lldb_python_dir: matches.opt_str("lldb-python-dir"),
448 verbose: matches.opt_present("verbose"),
449 only_modified: matches.opt_present("only-modified"),
450 color,
451 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
452 compare_mode,
453 rustfix_coverage: matches.opt_present("rustfix-coverage"),
454 has_html_tidy,
455 has_enzyme,
456 channel: matches.opt_str("channel").unwrap(),
457 git_hash: matches.opt_present("git-hash"),
458 edition: matches.opt_str("edition").as_deref().map(parse_edition),
459
460 cc: matches.opt_str("cc").unwrap(),
461 cxx: matches.opt_str("cxx").unwrap(),
462 cflags: matches.opt_str("cflags").unwrap(),
463 cxxflags: matches.opt_str("cxxflags").unwrap(),
464 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
465 target_linker: matches.opt_str("target-linker"),
466 host_linker: matches.opt_str("host-linker"),
467 llvm_components: matches.opt_str("llvm-components").unwrap(),
468 nodejs: matches.opt_str("nodejs"),
469
470 force_rerun: matches.opt_present("force-rerun"),
471
472 target_cfgs: OnceLock::new(),
473 builtin_cfg_names: OnceLock::new(),
474 supported_crate_types: OnceLock::new(),
475
476 nocapture: matches.opt_present("no-capture"),
477
478 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
479 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
480
481 profiler_runtime: matches.opt_present("profiler-runtime"),
482
483 diff_command: matches.opt_str("compiletest-diff-tool"),
484
485 minicore_path: opt_path(matches, "minicore-path"),
486
487 default_codegen_backend,
488 override_codegen_backend,
489 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
490 }
491}
492
493fn opt_str2(maybestr: Option<String>) -> String {
494 match maybestr {
495 None => "(none)".to_owned(),
496 Some(s) => s,
497 }
498}
499
500fn run_tests(config: Arc<Config>) {
502 debug!(?config, "run_tests");
503
504 panic_hook::install_panic_hook();
505
506 if config.rustfix_coverage {
510 let mut coverage_file_path = config.build_test_suite_root.clone();
511 coverage_file_path.push("rustfix_missing_coverage.txt");
512 if coverage_file_path.exists() {
513 if let Err(e) = fs::remove_file(&coverage_file_path) {
514 panic!("Could not delete {} due to {}", coverage_file_path, e)
515 }
516 }
517 }
518
519 unsafe {
523 raise_fd_limit::raise_fd_limit();
524 }
525 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
530
531 let mut configs = Vec::new();
532 if let TestMode::DebugInfo = config.mode {
533 if !config.target.contains("emscripten") {
535 match config.debugger {
536 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
537 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
538 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
539 None => {
544 configs.extend(debuggers::configure_cdb(&config));
545 configs.extend(debuggers::configure_gdb(&config));
546 configs.extend(debuggers::configure_lldb(&config));
547 }
548 }
549 }
550 } else {
551 configs.push(config.clone());
552 };
553
554 let mut tests = Vec::new();
557 for c in configs {
558 tests.extend(collect_and_make_tests(c));
559 }
560
561 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
562
563 let ok = executor::run_tests(&config, tests);
567
568 if !ok {
570 let mut msg = String::from("Some tests failed in compiletest");
578 write!(msg, " suite={}", config.suite).unwrap();
579
580 if let Some(compare_mode) = config.compare_mode.as_ref() {
581 write!(msg, " compare_mode={}", compare_mode).unwrap();
582 }
583
584 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
585 write!(msg, " pass_mode={}", pass_mode).unwrap();
586 }
587
588 write!(msg, " mode={}", config.mode).unwrap();
589 write!(msg, " host={}", config.host).unwrap();
590 write!(msg, " target={}", config.target).unwrap();
591
592 println!("{msg}");
593
594 std::process::exit(1);
595 }
596}
597
598struct TestCollectorCx {
600 config: Arc<Config>,
601 cache: DirectivesCache,
602 common_inputs_stamp: Stamp,
603 modified_tests: Vec<Utf8PathBuf>,
604}
605
606struct TestCollector {
608 tests: Vec<CollectedTest>,
609 found_path_stems: HashSet<Utf8PathBuf>,
610 poisoned: bool,
611}
612
613impl TestCollector {
614 fn new() -> Self {
615 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
616 }
617
618 fn merge(&mut self, mut other: Self) {
619 self.tests.append(&mut other.tests);
620 self.found_path_stems.extend(other.found_path_stems);
621 self.poisoned |= other.poisoned;
622 }
623}
624
625fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
635 debug!("making tests from {}", config.src_test_suite_root);
636 let common_inputs_stamp = common_inputs_stamp(&config);
637 let modified_tests =
638 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
639 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
640 });
641 let cache = DirectivesCache::load(&config);
642
643 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
644 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
645 .unwrap_or_else(|reason| {
646 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
647 });
648
649 let TestCollector { tests, found_path_stems, poisoned } = collector;
650
651 if poisoned {
652 eprintln!();
653 panic!("there are errors in tests");
654 }
655
656 check_for_overlapping_test_paths(&found_path_stems);
657
658 tests
659}
660
661fn common_inputs_stamp(config: &Config) -> Stamp {
669 let src_root = &config.src_root;
670
671 let mut stamp = Stamp::from_path(&config.rustc_path);
672
673 let pretty_printer_files = [
675 "src/etc/rust_types.py",
676 "src/etc/gdb_load_rust_pretty_printers.py",
677 "src/etc/gdb_lookup.py",
678 "src/etc/gdb_providers.py",
679 "src/etc/lldb_batchmode.py",
680 "src/etc/lldb_lookup.py",
681 "src/etc/lldb_providers.py",
682 ];
683 for file in &pretty_printer_files {
684 let path = src_root.join(file);
685 stamp.add_path(&path);
686 }
687
688 stamp.add_dir(&src_root.join("src/etc/natvis"));
689
690 stamp.add_dir(&config.run_lib_path);
691
692 if let Some(ref rustdoc_path) = config.rustdoc_path {
693 stamp.add_path(&rustdoc_path);
694 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
695 }
696
697 if let Some(coverage_dump_path) = &config.coverage_dump_path {
700 stamp.add_path(coverage_dump_path)
701 }
702
703 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
704
705 stamp.add_dir(&src_root.join("src/tools/compiletest"));
707
708 stamp
709}
710
711fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
716 if !config.only_modified {
719 return Ok(vec![]);
720 }
721
722 let files = get_git_modified_files(
723 &config.git_config(),
724 Some(dir.as_std_path()),
725 &vec!["rs", "stderr", "fixed"],
726 )?;
727 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
729
730 let all_paths = [&files[..], &untracked_files[..]].concat();
731 let full_paths = {
732 let mut full_paths: Vec<Utf8PathBuf> = all_paths
733 .into_iter()
734 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
735 .filter_map(
736 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
737 )
738 .collect();
739 full_paths.dedup();
740 full_paths.sort_unstable();
741 full_paths
742 };
743 Ok(full_paths)
744}
745
746fn collect_tests_from_dir(
749 cx: &TestCollectorCx,
750 dir: &Utf8Path,
751 relative_dir_path: &Utf8Path,
752) -> io::Result<TestCollector> {
753 if dir.join("compiletest-ignore-dir").exists() {
755 return Ok(TestCollector::new());
756 }
757
758 let mut components = dir.components().rev();
759 if let Some(Utf8Component::Normal(last)) = components.next()
760 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
761 && let Some(Utf8Component::Normal(parent)) = components.next()
762 && parent == "tests"
763 && let Ok(backend) = CodegenBackend::try_from(backend)
764 && backend != cx.config.default_codegen_backend
765 {
766 warning!(
768 "Ignoring tests in `{dir}` because they don't match the configured codegen \
769 backend (`{}`)",
770 cx.config.default_codegen_backend.as_str(),
771 );
772 return Ok(TestCollector::new());
773 }
774
775 if cx.config.mode == TestMode::RunMake {
777 let mut collector = TestCollector::new();
778 if dir.join("rmake.rs").exists() {
779 let paths = TestPaths {
780 file: dir.to_path_buf(),
781 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
782 };
783 make_test(cx, &mut collector, &paths);
784 return Ok(collector);
786 }
787 }
788
789 let build_dir = output_relative_path(&cx.config, relative_dir_path);
796 fs::create_dir_all(&build_dir).unwrap();
797
798 fs::read_dir(dir.as_std_path())?
803 .par_bridge()
804 .map(|file| {
805 let mut collector = TestCollector::new();
806 let file = file?;
807 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
808 let file_name = file_path.file_name().unwrap();
809
810 if is_test(file_name)
811 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
812 {
813 debug!(%file_path, "found test file");
815
816 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
818 collector.found_path_stems.insert(rel_test_path);
819
820 let paths =
821 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
822 make_test(cx, &mut collector, &paths);
823 } else if file_path.is_dir() {
824 let relative_file_path = relative_dir_path.join(file_name);
826 if file_name != "auxiliary" {
827 debug!(%file_path, "found directory");
828 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
829 }
830 } else {
831 debug!(%file_path, "found other file/directory");
832 }
833 Ok(collector)
834 })
835 .reduce(
836 || Ok(TestCollector::new()),
837 |a, b| {
838 let mut a = a?;
839 a.merge(b?);
840 Ok(a)
841 },
842 )
843}
844
845fn is_test(file_name: &str) -> bool {
847 if !file_name.ends_with(".rs") {
848 return false;
849 }
850
851 let invalid_prefixes = &[".", "#", "~"];
853 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
854}
855
856fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
859 let test_path = if cx.config.mode == TestMode::RunMake {
863 testpaths.file.join("rmake.rs")
864 } else {
865 testpaths.file.clone()
866 };
867
868 let file_contents =
870 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
871 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
872 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
873
874 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
881 vec![None]
882 } else {
883 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
884 };
885
886 collector.tests.extend(revisions.into_iter().map(|revision| {
889 let (test_name, filterable_path) =
891 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
892
893 let mut aux_props = AuxProps::default();
896
897 let mut desc = make_test_description(
901 &cx.config,
902 &cx.cache,
903 test_name,
904 &test_path,
905 &filterable_path,
906 &file_directives,
907 revision,
908 &mut collector.poisoned,
909 &mut aux_props,
910 );
911
912 if !desc.ignore
915 && !cx.config.force_rerun
916 && is_up_to_date(cx, testpaths, &aux_props, revision)
917 {
918 desc.ignore = true;
919 desc.ignore_message = Some("up-to-date".into());
923 }
924
925 let config = Arc::clone(&cx.config);
926 let testpaths = testpaths.clone();
927 let revision = revision.map(str::to_owned);
928
929 CollectedTest { desc, config, testpaths, revision }
930 }));
931}
932
933fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
936 output_base_dir(config, testpaths, revision).join("stamp")
937}
938
939fn files_related_to_test(
944 config: &Config,
945 testpaths: &TestPaths,
946 aux_props: &AuxProps,
947 revision: Option<&str>,
948) -> Vec<Utf8PathBuf> {
949 let mut related = vec![];
950
951 if testpaths.file.is_dir() {
952 for entry in WalkDir::new(&testpaths.file) {
954 let path = entry.unwrap().into_path();
955 if path.is_file() {
956 related.push(Utf8PathBuf::try_from(path).unwrap());
957 }
958 }
959 } else {
960 related.push(testpaths.file.clone());
961 }
962
963 for aux in aux_props.all_aux_path_strings() {
964 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
969 related.push(path);
970 }
971
972 for extension in UI_EXTENSIONS {
974 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
975 related.push(path);
976 }
977
978 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
980
981 related
982}
983
984fn is_up_to_date(
990 cx: &TestCollectorCx,
991 testpaths: &TestPaths,
992 aux_props: &AuxProps,
993 revision: Option<&str>,
994) -> bool {
995 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
996 let contents = match fs::read_to_string(&stamp_file_path) {
998 Ok(f) => f,
999 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1000 Err(_) => return false,
1002 };
1003 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1004 if contents != expected_hash {
1005 return false;
1008 }
1009
1010 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1013 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1014 inputs_stamp.add_path(&path);
1015 }
1016
1017 inputs_stamp < Stamp::from_path(&stamp_file_path)
1020}
1021
1022#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1024struct Stamp {
1025 time: SystemTime,
1026}
1027
1028impl Stamp {
1029 fn from_path(path: &Utf8Path) -> Self {
1031 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1032 stamp.add_path(path);
1033 stamp
1034 }
1035
1036 fn add_path(&mut self, path: &Utf8Path) {
1039 let modified = fs::metadata(path.as_std_path())
1040 .and_then(|metadata| metadata.modified())
1041 .unwrap_or(SystemTime::UNIX_EPOCH);
1042 self.time = self.time.max(modified);
1043 }
1044
1045 fn add_dir(&mut self, path: &Utf8Path) {
1049 let path = path.as_std_path();
1050 for entry in WalkDir::new(path) {
1051 let entry = entry.unwrap();
1052 if entry.file_type().is_file() {
1053 let modified = entry
1054 .metadata()
1055 .ok()
1056 .and_then(|metadata| metadata.modified().ok())
1057 .unwrap_or(SystemTime::UNIX_EPOCH);
1058 self.time = self.time.max(modified);
1059 }
1060 }
1061 }
1062}
1063
1064fn make_test_name_and_filterable_path(
1066 config: &Config,
1067 testpaths: &TestPaths,
1068 revision: Option<&str>,
1069) -> (String, Utf8PathBuf) {
1070 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1072 let debugger = match config.debugger {
1073 Some(d) => format!("-{}", d),
1074 None => String::new(),
1075 };
1076 let mode_suffix = match config.compare_mode {
1077 Some(ref mode) => format!(" ({})", mode.to_str()),
1078 None => String::new(),
1079 };
1080
1081 let name = format!(
1082 "[{}{}{}] {}{}",
1083 config.mode,
1084 debugger,
1085 mode_suffix,
1086 path,
1087 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1088 );
1089
1090 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1094 filterable_path = filterable_path.components().skip(1).collect();
1096
1097 (name, filterable_path)
1098}
1099
1100fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1118 let mut collisions = Vec::new();
1119 for path in found_path_stems {
1120 for ancestor in path.ancestors().skip(1) {
1121 if found_path_stems.contains(ancestor) {
1122 collisions.push((path, ancestor));
1123 }
1124 }
1125 }
1126 if !collisions.is_empty() {
1127 collisions.sort();
1128 let collisions: String = collisions
1129 .into_iter()
1130 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1131 .collect();
1132 panic!(
1133 "{collisions}\n\
1134 Tests cannot have overlapping names. Make sure they use unique prefixes."
1135 );
1136 }
1137}
1138
1139fn early_config_check(config: &Config) {
1140 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1141 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1142 }
1143
1144 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1145 let actioned = if config.bless { "blessed" } else { "checked" };
1146 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1147 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1148 }
1149
1150 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1152 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1153 }
1154}