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