1#![crate_name = "compiletest"]
2
3#[cfg(test)]
4mod tests;
5
6pub mod common;
7mod debuggers;
8pub mod diagnostics;
9pub mod directives;
10pub mod edition;
11pub mod errors;
12mod executor;
13mod json;
14mod output_capture;
15mod panic_hook;
16mod raise_fd_limit;
17mod read2;
18pub mod runtest;
19pub mod util;
20
21use core::panic;
22use std::collections::HashSet;
23use std::fmt::Write;
24use std::io::{self, ErrorKind};
25use std::process::{Command, Stdio};
26use std::sync::{Arc, OnceLock};
27use std::time::SystemTime;
28use std::{env, fs, vec};
29
30use build_helper::git::{get_git_modified_files, get_git_untracked_files};
31use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
32use getopts::Options;
33use rayon::iter::{ParallelBridge, ParallelIterator};
34use tracing::debug;
35use walkdir::WalkDir;
36
37use self::directives::{EarlyProps, make_test_description};
38use crate::common::{
39 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
40 expected_output_path, output_base_dir, output_relative_path,
41};
42use crate::directives::DirectivesCache;
43use crate::edition::parse_edition;
44use crate::executor::{CollectedTest, ColorConfig};
45
46pub fn parse_config(args: Vec<String>) -> Config {
52 let mut opts = Options::new();
53 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
54 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
55 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
56 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
57 .optopt(
58 "",
59 "stage0-rustc-path",
60 "path to rustc to use for compiling run-make recipes",
61 "PATH",
62 )
63 .optopt(
64 "",
65 "query-rustc-path",
66 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
67 "PATH",
68 )
69 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
70 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
71 .reqopt("", "python", "path to python to use for doc tests", "PATH")
72 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
73 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
74 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
75 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
76 .reqopt("", "src-root", "directory containing sources", "PATH")
77 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
78 .reqopt("", "build-root", "path to root build directory", "PATH")
79 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
80 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
81 .reqopt("", "stage", "stage number under test", "N")
82 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
83 .reqopt(
84 "",
85 "mode",
86 "which sort of compile tests to run",
87 "pretty | debug-info | codegen | rustdoc \
88 | rustdoc-json | codegen-units | incremental | run-make | ui \
89 | rustdoc-js | mir-opt | assembly | crashes",
90 )
91 .reqopt(
92 "",
93 "suite",
94 "which suite of compile tests to run. used for nicer error reporting.",
95 "SUITE",
96 )
97 .optopt(
98 "",
99 "pass",
100 "force {check,build,run}-pass tests to this mode.",
101 "check | build | run",
102 )
103 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
104 .optflag("", "ignored", "run tests marked as ignored")
105 .optflag("", "has-enzyme", "run tests that require enzyme")
106 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
107 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
108 .optmulti(
109 "",
110 "skip",
111 "skip tests matching SUBSTRING. Can be passed multiple times",
112 "SUBSTRING",
113 )
114 .optflag("", "exact", "filters match exactly")
115 .optopt(
116 "",
117 "runner",
118 "supervisor program to run tests under \
119 (eg. emulator, valgrind)",
120 "PROGRAM",
121 )
122 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
123 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
124 .optflag(
125 "",
126 "rust-randomized-layout",
127 "set this when rustc/stdlib were compiled with randomized layouts",
128 )
129 .optflag("", "optimize-tests", "run tests with optimizations enabled")
130 .optflag("", "verbose", "run tests verbosely, showing all output")
131 .optflag(
132 "",
133 "bless",
134 "overwrite stderr/stdout files instead of complaining about a mismatch",
135 )
136 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
137 .optopt("", "color", "coloring: auto, always, never", "WHEN")
138 .optopt("", "target", "the target to build for", "TARGET")
139 .optopt("", "host", "the host to build for", "HOST")
140 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
141 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
142 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
143 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
144 .optflag("", "system-llvm", "is LLVM the system LLVM")
145 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
146 .optopt("", "adb-path", "path to the android debugger", "PATH")
147 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
148 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
149 .reqopt("", "cc", "path to a C compiler", "PATH")
150 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
151 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
152 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
153 .optopt("", "ar", "path to an archiver", "PATH")
154 .optopt("", "target-linker", "path to a linker for the target", "PATH")
155 .optopt("", "host-linker", "path to a linker for the host", "PATH")
156 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
157 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
158 .optopt("", "nodejs", "the name of nodejs", "PATH")
159 .optopt("", "npm", "the name of npm", "PATH")
160 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
161 .optopt(
162 "",
163 "compare-mode",
164 "mode describing what file the actual ui output will be compared to",
165 "COMPARE MODE",
166 )
167 .optflag(
168 "",
169 "rustfix-coverage",
170 "enable this to generate a Rustfix coverage file, which is saved in \
171 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
172 )
173 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
174 .optflag("", "only-modified", "only run tests that result been modified")
175 .optflag("", "nocapture", "")
177 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
178 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
179 .optflag("h", "help", "show this message")
180 .reqopt("", "channel", "current Rust channel", "CHANNEL")
181 .optflag(
182 "",
183 "git-hash",
184 "run tests which rely on commit version being compiled into the binaries",
185 )
186 .optopt("", "edition", "default Rust edition", "EDITION")
187 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
188 .reqopt(
189 "",
190 "git-merge-commit-email",
191 "email address used for finding merge commits",
192 "EMAIL",
193 )
194 .optopt(
195 "",
196 "compiletest-diff-tool",
197 "What custom diff tool to use for displaying compiletest tests.",
198 "COMMAND",
199 )
200 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
201 .optopt(
202 "",
203 "debugger",
204 "only test a specific debugger in debuginfo tests",
205 "gdb | lldb | cdb",
206 )
207 .optopt(
208 "",
209 "default-codegen-backend",
210 "the codegen backend currently used",
211 "CODEGEN BACKEND NAME",
212 )
213 .optopt(
214 "",
215 "override-codegen-backend",
216 "the codegen backend to use instead of the default one",
217 "CODEGEN BACKEND [NAME | PATH]",
218 );
219
220 let (argv0, args_) = args.split_first().unwrap();
221 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
222 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
223 println!("{}", opts.usage(&message));
224 println!();
225 panic!()
226 }
227
228 let matches = &match opts.parse(args_) {
229 Ok(m) => m,
230 Err(f) => panic!("{:?}", f),
231 };
232
233 if matches.opt_present("h") || matches.opt_present("help") {
234 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
235 println!("{}", opts.usage(&message));
236 println!();
237 panic!()
238 }
239
240 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
241 if path.is_relative() {
242 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
243 } else {
244 path
245 }
246 }
247
248 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
249 match m.opt_str(nm) {
250 Some(s) => Utf8PathBuf::from(&s),
251 None => panic!("no option (=path) found for {}", nm),
252 }
253 }
254
255 let target = opt_str2(matches.opt_str("target"));
256 let android_cross_path = opt_path(matches, "android-cross-path");
257 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
259 let (gdb, gdb_version) =
261 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
262 let lldb_version =
264 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
265 let color = match matches.opt_str("color").as_deref() {
266 Some("auto") | None => ColorConfig::AutoColor,
267 Some("always") => ColorConfig::AlwaysColor,
268 Some("never") => ColorConfig::NeverColor,
269 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
270 };
271 let llvm_version =
275 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
276 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
277 );
278
279 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
280 Some(backend) => match CodegenBackend::try_from(backend) {
281 Ok(backend) => backend,
282 Err(error) => {
283 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
284 }
285 },
286 None => CodegenBackend::Llvm,
288 };
289 let override_codegen_backend = matches.opt_str("override-codegen-backend");
290
291 let run_ignored = matches.opt_present("ignored");
292 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
293 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
294 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
295 let has_html_tidy = if mode == TestMode::Rustdoc {
296 Command::new("tidy")
297 .arg("--version")
298 .stdout(Stdio::null())
299 .status()
300 .map_or(false, |status| status.success())
301 } else {
302 false
304 };
305 let has_enzyme = matches.opt_present("has-enzyme");
306 let filters = if mode == TestMode::RunMake {
307 matches
308 .free
309 .iter()
310 .map(|f| {
311 let path = Utf8Path::new(f);
317 let mut iter = path.iter().skip(1);
318
319 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
320 path.parent().unwrap().to_string()
323 } else {
324 f.to_string()
325 }
326 })
327 .collect::<Vec<_>>()
328 } else {
329 matches.free.clone()
336 };
337 let compare_mode = matches.opt_str("compare-mode").map(|s| {
338 s.parse().unwrap_or_else(|_| {
339 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
340 panic!(
341 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
342 variants.join(", ")
343 );
344 })
345 });
346 if matches.opt_present("nocapture") {
347 panic!("`--nocapture` is deprecated; please use `--no-capture`");
348 }
349
350 let stage = match matches.opt_str("stage") {
351 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
352 None => panic!("`--stage` is required"),
353 };
354
355 let src_root = opt_path(matches, "src-root");
356 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
357 assert!(
358 src_test_suite_root.starts_with(&src_root),
359 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
360 src_root,
361 src_test_suite_root
362 );
363
364 let build_root = opt_path(matches, "build-root");
365 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
366 assert!(build_test_suite_root.starts_with(&build_root));
367
368 Config {
369 bless: matches.opt_present("bless"),
370 fail_fast: matches.opt_present("fail-fast")
371 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
372
373 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
374 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
375 rustc_path: opt_path(matches, "rustc-path"),
376 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
377 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
378 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
379 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
380 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
381 python: matches.opt_str("python").unwrap(),
382 jsondocck_path: matches.opt_str("jsondocck-path"),
383 jsondoclint_path: matches.opt_str("jsondoclint-path"),
384 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
385 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
386 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
387
388 src_root,
389 src_test_suite_root,
390
391 build_root,
392 build_test_suite_root,
393
394 sysroot_base: opt_path(matches, "sysroot-base"),
395
396 stage,
397 stage_id: matches.opt_str("stage-id").unwrap(),
398
399 mode,
400 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
401 debugger: matches.opt_str("debugger").map(|debugger| {
402 debugger
403 .parse::<Debugger>()
404 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
405 }),
406 run_ignored,
407 with_rustc_debug_assertions,
408 with_std_debug_assertions,
409 filters,
410 skip: matches.opt_strs("skip"),
411 filter_exact: matches.opt_present("exact"),
412 force_pass_mode: matches.opt_str("pass").map(|mode| {
413 mode.parse::<PassMode>()
414 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
415 }),
416 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
418 "auto" => None,
419 "always" => Some(true),
420 "never" => Some(false),
421 _ => panic!("unknown `--run` option `{}` given", mode),
422 }),
423 runner: matches.opt_str("runner"),
424 host_rustcflags: matches.opt_strs("host-rustcflags"),
425 target_rustcflags: matches.opt_strs("target-rustcflags"),
426 optimize_tests: matches.opt_present("optimize-tests"),
427 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
428 target,
429 host: opt_str2(matches.opt_str("host")),
430 cdb,
431 cdb_version,
432 gdb,
433 gdb_version,
434 lldb_version,
435 llvm_version,
436 system_llvm: matches.opt_present("system-llvm"),
437 android_cross_path,
438 adb_path: opt_str2(matches.opt_str("adb-path")),
439 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
440 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
441 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
442 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
443 lldb_python_dir: matches.opt_str("lldb-python-dir"),
444 verbose: matches.opt_present("verbose"),
445 only_modified: matches.opt_present("only-modified"),
446 color,
447 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
448 compare_mode,
449 rustfix_coverage: matches.opt_present("rustfix-coverage"),
450 has_html_tidy,
451 has_enzyme,
452 channel: matches.opt_str("channel").unwrap(),
453 git_hash: matches.opt_present("git-hash"),
454 edition: matches.opt_str("edition").as_deref().map(parse_edition),
455
456 cc: matches.opt_str("cc").unwrap(),
457 cxx: matches.opt_str("cxx").unwrap(),
458 cflags: matches.opt_str("cflags").unwrap(),
459 cxxflags: matches.opt_str("cxxflags").unwrap(),
460 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
461 target_linker: matches.opt_str("target-linker"),
462 host_linker: matches.opt_str("host-linker"),
463 llvm_components: matches.opt_str("llvm-components").unwrap(),
464 nodejs: matches.opt_str("nodejs"),
465 npm: matches.opt_str("npm"),
466
467 force_rerun: matches.opt_present("force-rerun"),
468
469 target_cfgs: OnceLock::new(),
470 builtin_cfg_names: OnceLock::new(),
471 supported_crate_types: OnceLock::new(),
472
473 nocapture: matches.opt_present("no-capture"),
474
475 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
476 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
477
478 profiler_runtime: matches.opt_present("profiler-runtime"),
479
480 diff_command: matches.opt_str("compiletest-diff-tool"),
481
482 minicore_path: opt_path(matches, "minicore-path"),
483
484 default_codegen_backend,
485 override_codegen_backend,
486 }
487}
488
489pub fn opt_str(maybestr: &Option<String>) -> &str {
490 match *maybestr {
491 None => "(none)",
492 Some(ref s) => s,
493 }
494}
495
496pub fn opt_str2(maybestr: Option<String>) -> String {
497 match maybestr {
498 None => "(none)".to_owned(),
499 Some(s) => s,
500 }
501}
502
503pub fn run_tests(config: Arc<Config>) {
505 debug!(?config, "run_tests");
506
507 panic_hook::install_panic_hook();
508
509 if config.rustfix_coverage {
513 let mut coverage_file_path = config.build_test_suite_root.clone();
514 coverage_file_path.push("rustfix_missing_coverage.txt");
515 if coverage_file_path.exists() {
516 if let Err(e) = fs::remove_file(&coverage_file_path) {
517 panic!("Could not delete {} due to {}", coverage_file_path, e)
518 }
519 }
520 }
521
522 unsafe {
526 raise_fd_limit::raise_fd_limit();
527 }
528 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
533
534 unsafe { env::set_var("TARGET", &config.target) };
538
539 let mut configs = Vec::new();
540 if let TestMode::DebugInfo = config.mode {
541 if !config.target.contains("emscripten") {
543 match config.debugger {
544 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
545 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
546 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
547 None => {
552 configs.extend(debuggers::configure_cdb(&config));
553 configs.extend(debuggers::configure_gdb(&config));
554 configs.extend(debuggers::configure_lldb(&config));
555 }
556 }
557 }
558 } else {
559 configs.push(config.clone());
560 };
561
562 let mut tests = Vec::new();
565 for c in configs {
566 tests.extend(collect_and_make_tests(c));
567 }
568
569 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
570
571 let ok = executor::run_tests(&config, tests);
575
576 if !ok {
578 let mut msg = String::from("Some tests failed in compiletest");
586 write!(msg, " suite={}", config.suite).unwrap();
587
588 if let Some(compare_mode) = config.compare_mode.as_ref() {
589 write!(msg, " compare_mode={}", compare_mode).unwrap();
590 }
591
592 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
593 write!(msg, " pass_mode={}", pass_mode).unwrap();
594 }
595
596 write!(msg, " mode={}", config.mode).unwrap();
597 write!(msg, " host={}", config.host).unwrap();
598 write!(msg, " target={}", config.target).unwrap();
599
600 println!("{msg}");
601
602 std::process::exit(1);
603 }
604}
605
606struct TestCollectorCx {
608 config: Arc<Config>,
609 cache: DirectivesCache,
610 common_inputs_stamp: Stamp,
611 modified_tests: Vec<Utf8PathBuf>,
612}
613
614struct TestCollector {
616 tests: Vec<CollectedTest>,
617 found_path_stems: HashSet<Utf8PathBuf>,
618 poisoned: bool,
619}
620
621impl TestCollector {
622 fn new() -> Self {
623 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
624 }
625
626 fn merge(&mut self, mut other: Self) {
627 self.tests.append(&mut other.tests);
628 self.found_path_stems.extend(other.found_path_stems);
629 self.poisoned |= other.poisoned;
630 }
631}
632
633pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
643 debug!("making tests from {}", config.src_test_suite_root);
644 let common_inputs_stamp = common_inputs_stamp(&config);
645 let modified_tests =
646 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
647 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
648 });
649 let cache = DirectivesCache::load(&config);
650
651 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
652 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
653 .unwrap_or_else(|reason| {
654 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
655 });
656
657 let TestCollector { tests, found_path_stems, poisoned } = collector;
658
659 if poisoned {
660 eprintln!();
661 panic!("there are errors in tests");
662 }
663
664 check_for_overlapping_test_paths(&found_path_stems);
665
666 tests
667}
668
669fn common_inputs_stamp(config: &Config) -> Stamp {
677 let src_root = &config.src_root;
678
679 let mut stamp = Stamp::from_path(&config.rustc_path);
680
681 let pretty_printer_files = [
683 "src/etc/rust_types.py",
684 "src/etc/gdb_load_rust_pretty_printers.py",
685 "src/etc/gdb_lookup.py",
686 "src/etc/gdb_providers.py",
687 "src/etc/lldb_batchmode.py",
688 "src/etc/lldb_lookup.py",
689 "src/etc/lldb_providers.py",
690 ];
691 for file in &pretty_printer_files {
692 let path = src_root.join(file);
693 stamp.add_path(&path);
694 }
695
696 stamp.add_dir(&src_root.join("src/etc/natvis"));
697
698 stamp.add_dir(&config.run_lib_path);
699
700 if let Some(ref rustdoc_path) = config.rustdoc_path {
701 stamp.add_path(&rustdoc_path);
702 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
703 }
704
705 if let Some(coverage_dump_path) = &config.coverage_dump_path {
708 stamp.add_path(coverage_dump_path)
709 }
710
711 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
712
713 stamp.add_dir(&src_root.join("src/tools/compiletest"));
715
716 stamp
717}
718
719fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
724 if !config.only_modified {
727 return Ok(vec![]);
728 }
729
730 let files = get_git_modified_files(
731 &config.git_config(),
732 Some(dir.as_std_path()),
733 &vec!["rs", "stderr", "fixed"],
734 )?;
735 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
737
738 let all_paths = [&files[..], &untracked_files[..]].concat();
739 let full_paths = {
740 let mut full_paths: Vec<Utf8PathBuf> = all_paths
741 .into_iter()
742 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
743 .filter_map(
744 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
745 )
746 .collect();
747 full_paths.dedup();
748 full_paths.sort_unstable();
749 full_paths
750 };
751 Ok(full_paths)
752}
753
754fn collect_tests_from_dir(
757 cx: &TestCollectorCx,
758 dir: &Utf8Path,
759 relative_dir_path: &Utf8Path,
760) -> io::Result<TestCollector> {
761 if dir.join("compiletest-ignore-dir").exists() {
763 return Ok(TestCollector::new());
764 }
765
766 let mut components = dir.components().rev();
767 if let Some(Utf8Component::Normal(last)) = components.next()
768 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
769 && let Some(Utf8Component::Normal(parent)) = components.next()
770 && parent == "tests"
771 && let Ok(backend) = CodegenBackend::try_from(backend)
772 && backend != cx.config.default_codegen_backend
773 {
774 warning!(
776 "Ignoring tests in `{dir}` because they don't match the configured codegen \
777 backend (`{}`)",
778 cx.config.default_codegen_backend.as_str(),
779 );
780 return Ok(TestCollector::new());
781 }
782
783 if cx.config.mode == TestMode::RunMake {
785 let mut collector = TestCollector::new();
786 if dir.join("rmake.rs").exists() {
787 let paths = TestPaths {
788 file: dir.to_path_buf(),
789 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
790 };
791 make_test(cx, &mut collector, &paths);
792 return Ok(collector);
794 }
795 }
796
797 let build_dir = output_relative_path(&cx.config, relative_dir_path);
804 fs::create_dir_all(&build_dir).unwrap();
805
806 fs::read_dir(dir.as_std_path())?
811 .par_bridge()
812 .map(|file| {
813 let mut collector = TestCollector::new();
814 let file = file?;
815 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
816 let file_name = file_path.file_name().unwrap();
817
818 if is_test(file_name)
819 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
820 {
821 debug!(%file_path, "found test file");
823
824 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
826 collector.found_path_stems.insert(rel_test_path);
827
828 let paths =
829 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
830 make_test(cx, &mut collector, &paths);
831 } else if file_path.is_dir() {
832 let relative_file_path = relative_dir_path.join(file_name);
834 if file_name != "auxiliary" {
835 debug!(%file_path, "found directory");
836 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
837 }
838 } else {
839 debug!(%file_path, "found other file/directory");
840 }
841 Ok(collector)
842 })
843 .reduce(
844 || Ok(TestCollector::new()),
845 |a, b| {
846 let mut a = a?;
847 a.merge(b?);
848 Ok(a)
849 },
850 )
851}
852
853pub fn is_test(file_name: &str) -> bool {
855 if !file_name.ends_with(".rs") {
856 return false;
857 }
858
859 let invalid_prefixes = &[".", "#", "~"];
861 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
862}
863
864fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
867 let test_path = if cx.config.mode == TestMode::RunMake {
871 testpaths.file.join("rmake.rs")
872 } else {
873 testpaths.file.clone()
874 };
875
876 let early_props = EarlyProps::from_file(&cx.config, &test_path);
878
879 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
886 vec![None]
887 } else {
888 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
889 };
890
891 collector.tests.extend(revisions.into_iter().map(|revision| {
894 let file_contents =
896 fs::read_to_string(&test_path).expect("read test file to parse ignores");
897 let (test_name, filterable_path) =
898 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
899 let mut desc = make_test_description(
903 &cx.config,
904 &cx.cache,
905 test_name,
906 &test_path,
907 &filterable_path,
908 &file_contents,
909 revision,
910 &mut collector.poisoned,
911 );
912
913 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
916 desc.ignore = true;
917 desc.ignore_message = Some("up-to-date".into());
921 }
922
923 let config = Arc::clone(&cx.config);
924 let testpaths = testpaths.clone();
925 let revision = revision.map(str::to_owned);
926
927 CollectedTest { desc, config, testpaths, revision }
928 }));
929}
930
931fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
934 output_base_dir(config, testpaths, revision).join("stamp")
935}
936
937fn files_related_to_test(
942 config: &Config,
943 testpaths: &TestPaths,
944 props: &EarlyProps,
945 revision: Option<&str>,
946) -> Vec<Utf8PathBuf> {
947 let mut related = vec![];
948
949 if testpaths.file.is_dir() {
950 for entry in WalkDir::new(&testpaths.file) {
952 let path = entry.unwrap().into_path();
953 if path.is_file() {
954 related.push(Utf8PathBuf::try_from(path).unwrap());
955 }
956 }
957 } else {
958 related.push(testpaths.file.clone());
959 }
960
961 for aux in props.aux.all_aux_path_strings() {
962 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
964 related.push(path);
965 }
966
967 for extension in UI_EXTENSIONS {
969 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
970 related.push(path);
971 }
972
973 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
975
976 related
977}
978
979fn is_up_to_date(
985 cx: &TestCollectorCx,
986 testpaths: &TestPaths,
987 props: &EarlyProps,
988 revision: Option<&str>,
989) -> bool {
990 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
991 let contents = match fs::read_to_string(&stamp_file_path) {
993 Ok(f) => f,
994 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
995 Err(_) => return false,
997 };
998 let expected_hash = runtest::compute_stamp_hash(&cx.config);
999 if contents != expected_hash {
1000 return false;
1003 }
1004
1005 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1008 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1009 inputs_stamp.add_path(&path);
1010 }
1011
1012 inputs_stamp < Stamp::from_path(&stamp_file_path)
1015}
1016
1017#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1019struct Stamp {
1020 time: SystemTime,
1021}
1022
1023impl Stamp {
1024 fn from_path(path: &Utf8Path) -> Self {
1026 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1027 stamp.add_path(path);
1028 stamp
1029 }
1030
1031 fn add_path(&mut self, path: &Utf8Path) {
1034 let modified = fs::metadata(path.as_std_path())
1035 .and_then(|metadata| metadata.modified())
1036 .unwrap_or(SystemTime::UNIX_EPOCH);
1037 self.time = self.time.max(modified);
1038 }
1039
1040 fn add_dir(&mut self, path: &Utf8Path) {
1044 let path = path.as_std_path();
1045 for entry in WalkDir::new(path) {
1046 let entry = entry.unwrap();
1047 if entry.file_type().is_file() {
1048 let modified = entry
1049 .metadata()
1050 .ok()
1051 .and_then(|metadata| metadata.modified().ok())
1052 .unwrap_or(SystemTime::UNIX_EPOCH);
1053 self.time = self.time.max(modified);
1054 }
1055 }
1056 }
1057}
1058
1059fn make_test_name_and_filterable_path(
1061 config: &Config,
1062 testpaths: &TestPaths,
1063 revision: Option<&str>,
1064) -> (String, Utf8PathBuf) {
1065 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1067 let debugger = match config.debugger {
1068 Some(d) => format!("-{}", d),
1069 None => String::new(),
1070 };
1071 let mode_suffix = match config.compare_mode {
1072 Some(ref mode) => format!(" ({})", mode.to_str()),
1073 None => String::new(),
1074 };
1075
1076 let name = format!(
1077 "[{}{}{}] {}{}",
1078 config.mode,
1079 debugger,
1080 mode_suffix,
1081 path,
1082 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1083 );
1084
1085 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1089 filterable_path = filterable_path.components().skip(1).collect();
1091
1092 (name, filterable_path)
1093}
1094
1095fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1113 let mut collisions = Vec::new();
1114 for path in found_path_stems {
1115 for ancestor in path.ancestors().skip(1) {
1116 if found_path_stems.contains(ancestor) {
1117 collisions.push((path, ancestor));
1118 }
1119 }
1120 }
1121 if !collisions.is_empty() {
1122 collisions.sort();
1123 let collisions: String = collisions
1124 .into_iter()
1125 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1126 .collect();
1127 panic!(
1128 "{collisions}\n\
1129 Tests cannot have overlapping names. Make sure they use unique prefixes."
1130 );
1131 }
1132}
1133
1134pub fn early_config_check(config: &Config) {
1135 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1136 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1137 }
1138
1139 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1140 let actioned = if config.bless { "blessed" } else { "checked" };
1141 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1142 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1143 }
1144
1145 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1147 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1148 }
1149}