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 host_compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
379 target_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").map(Utf8PathBuf::from),
388 jsondoclint_path: matches.opt_str("jsondoclint-path").map(Utf8PathBuf::from),
389 run_clang_based_tests_with: matches
390 .opt_str("run-clang-based-tests-with")
391 .map(Utf8PathBuf::from),
392 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
393 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
394
395 src_root,
396 src_test_suite_root,
397
398 build_root,
399 build_test_suite_root,
400
401 sysroot_base: opt_path(matches, "sysroot-base"),
402
403 stage,
404 stage_id: matches.opt_str("stage-id").unwrap(),
405
406 mode,
407 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
408 debugger: matches.opt_str("debugger").map(|debugger| {
409 debugger
410 .parse::<Debugger>()
411 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
412 }),
413 run_ignored,
414 with_rustc_debug_assertions,
415 with_std_debug_assertions,
416 filters,
417 skip: matches.opt_strs("skip"),
418 filter_exact: matches.opt_present("exact"),
419 force_pass_mode: matches.opt_str("pass").map(|mode| {
420 mode.parse::<PassMode>()
421 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
422 }),
423 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
425 "auto" => None,
426 "always" => Some(true),
427 "never" => Some(false),
428 _ => panic!("unknown `--run` option `{}` given", mode),
429 }),
430 runner: matches.opt_str("runner"),
431 host_rustcflags: matches.opt_strs("host-rustcflags"),
432 target_rustcflags: matches.opt_strs("target-rustcflags"),
433 optimize_tests: matches.opt_present("optimize-tests"),
434 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
435 target,
436 host: opt_str2(matches.opt_str("host")),
437 cdb,
438 cdb_version,
439 gdb,
440 gdb_version,
441 lldb,
442 lldb_version,
443 llvm_version,
444 system_llvm: matches.opt_present("system-llvm"),
445 android_cross_path,
446 adb_path: Utf8PathBuf::from(opt_str2(matches.opt_str("adb-path"))),
447 adb_test_dir: Utf8PathBuf::from(opt_str2(matches.opt_str("adb-test-dir"))),
448 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
449 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
450 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
451 verbose: matches.opt_present("verbose"),
452 only_modified: matches.opt_present("only-modified"),
453 color,
454 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
455 compare_mode,
456 rustfix_coverage: matches.opt_present("rustfix-coverage"),
457 has_html_tidy,
458 has_enzyme,
459 channel: matches.opt_str("channel").unwrap(),
460 git_hash: matches.opt_present("git-hash"),
461 edition: matches.opt_str("edition").as_deref().map(parse_edition),
462
463 cc: matches.opt_str("cc").unwrap(),
464 cxx: matches.opt_str("cxx").unwrap(),
465 cflags: matches.opt_str("cflags").unwrap(),
466 cxxflags: matches.opt_str("cxxflags").unwrap(),
467 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
468 target_linker: matches.opt_str("target-linker"),
469 host_linker: matches.opt_str("host-linker"),
470 llvm_components: matches.opt_str("llvm-components").unwrap(),
471 nodejs: matches.opt_str("nodejs").map(Utf8PathBuf::from),
472
473 force_rerun: matches.opt_present("force-rerun"),
474
475 target_cfgs: OnceLock::new(),
476 builtin_cfg_names: OnceLock::new(),
477 supported_crate_types: OnceLock::new(),
478
479 capture: !matches.opt_present("no-capture"),
480
481 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
482 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
483
484 profiler_runtime: matches.opt_present("profiler-runtime"),
485
486 diff_command: matches.opt_str("compiletest-diff-tool"),
487
488 minicore_path: opt_path(matches, "minicore-path"),
489
490 default_codegen_backend,
491 override_codegen_backend,
492 bypass_ignore_backends: matches.opt_present("bypass-ignore-backends"),
493 }
494}
495
496fn opt_str2(maybestr: Option<String>) -> String {
497 match maybestr {
498 None => "(none)".to_owned(),
499 Some(s) => s,
500 }
501}
502
503fn 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 let mut configs = Vec::new();
535 if let TestMode::DebugInfo = config.mode {
536 if !config.target.contains("emscripten") {
538 match config.debugger {
539 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
540 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
541 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
542 None => {
547 configs.extend(debuggers::configure_cdb(&config));
548 configs.extend(debuggers::configure_gdb(&config));
549 configs.extend(debuggers::configure_lldb(&config));
550 }
551 }
552 }
553 } else {
554 configs.push(config.clone());
555 };
556
557 let mut tests = Vec::new();
560 for c in configs {
561 tests.extend(collect_and_make_tests(c));
562 }
563
564 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
565
566 let ok = executor::run_tests(&config, tests);
570
571 if !ok {
573 let mut msg = String::from("Some tests failed in compiletest");
581 write!(msg, " suite={}", config.suite).unwrap();
582
583 if let Some(compare_mode) = config.compare_mode.as_ref() {
584 write!(msg, " compare_mode={}", compare_mode).unwrap();
585 }
586
587 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
588 write!(msg, " pass_mode={}", pass_mode).unwrap();
589 }
590
591 write!(msg, " mode={}", config.mode).unwrap();
592 write!(msg, " host={}", config.host).unwrap();
593 write!(msg, " target={}", config.target).unwrap();
594
595 println!("{msg}");
596
597 std::process::exit(1);
598 }
599}
600
601struct TestCollectorCx {
603 config: Arc<Config>,
604 cache: DirectivesCache,
605 common_inputs_stamp: Stamp,
606 modified_tests: Vec<Utf8PathBuf>,
607}
608
609struct TestCollector {
611 tests: Vec<CollectedTest>,
612 found_path_stems: HashSet<Utf8PathBuf>,
613 poisoned: bool,
614}
615
616impl TestCollector {
617 fn new() -> Self {
618 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
619 }
620
621 fn merge(&mut self, mut other: Self) {
622 self.tests.append(&mut other.tests);
623 self.found_path_stems.extend(other.found_path_stems);
624 self.poisoned |= other.poisoned;
625 }
626}
627
628fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
638 debug!("making tests from {}", config.src_test_suite_root);
639 let common_inputs_stamp = common_inputs_stamp(&config);
640 let modified_tests =
641 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
642 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
643 });
644 let cache = DirectivesCache::load(&config);
645
646 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
647 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
648 .unwrap_or_else(|reason| {
649 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
650 });
651
652 let TestCollector { tests, found_path_stems, poisoned } = collector;
653
654 if poisoned {
655 eprintln!();
656 panic!("there are errors in tests");
657 }
658
659 check_for_overlapping_test_paths(&found_path_stems);
660
661 tests
662}
663
664fn common_inputs_stamp(config: &Config) -> Stamp {
672 let src_root = &config.src_root;
673
674 let mut stamp = Stamp::from_path(&config.rustc_path);
675
676 let pretty_printer_files = [
678 "src/etc/rust_types.py",
679 "src/etc/gdb_load_rust_pretty_printers.py",
680 "src/etc/gdb_lookup.py",
681 "src/etc/gdb_providers.py",
682 "src/etc/lldb_batchmode.py",
683 "src/etc/lldb_lookup.py",
684 "src/etc/lldb_providers.py",
685 ];
686 for file in &pretty_printer_files {
687 let path = src_root.join(file);
688 stamp.add_path(&path);
689 }
690
691 stamp.add_dir(&src_root.join("src/etc/natvis"));
692
693 stamp.add_dir(&config.target_run_lib_path);
694
695 if let Some(ref rustdoc_path) = config.rustdoc_path {
696 stamp.add_path(&rustdoc_path);
697 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
698 }
699
700 if let Some(coverage_dump_path) = &config.coverage_dump_path {
703 stamp.add_path(coverage_dump_path)
704 }
705
706 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
707
708 stamp.add_dir(&src_root.join("src/tools/compiletest"));
710
711 stamp
712}
713
714fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
719 if !config.only_modified {
722 return Ok(vec![]);
723 }
724
725 let files = get_git_modified_files(
726 &config.git_config(),
727 Some(dir.as_std_path()),
728 &vec!["rs", "stderr", "fixed"],
729 )?;
730 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
732
733 let all_paths = [&files[..], &untracked_files[..]].concat();
734 let full_paths = {
735 let mut full_paths: Vec<Utf8PathBuf> = all_paths
736 .into_iter()
737 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
738 .filter_map(
739 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
740 )
741 .collect();
742 full_paths.dedup();
743 full_paths.sort_unstable();
744 full_paths
745 };
746 Ok(full_paths)
747}
748
749fn collect_tests_from_dir(
752 cx: &TestCollectorCx,
753 dir: &Utf8Path,
754 relative_dir_path: &Utf8Path,
755) -> io::Result<TestCollector> {
756 if dir.join("compiletest-ignore-dir").exists() {
758 return Ok(TestCollector::new());
759 }
760
761 let mut components = dir.components().rev();
762 if let Some(Utf8Component::Normal(last)) = components.next()
763 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
764 && let Some(Utf8Component::Normal(parent)) = components.next()
765 && parent == "tests"
766 && let Ok(backend) = CodegenBackend::try_from(backend)
767 && backend != cx.config.default_codegen_backend
768 {
769 warning!(
771 "Ignoring tests in `{dir}` because they don't match the configured codegen \
772 backend (`{}`)",
773 cx.config.default_codegen_backend.as_str(),
774 );
775 return Ok(TestCollector::new());
776 }
777
778 if cx.config.mode == TestMode::RunMake {
780 let mut collector = TestCollector::new();
781 if dir.join("rmake.rs").exists() {
782 let paths = TestPaths {
783 file: dir.to_path_buf(),
784 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
785 };
786 make_test(cx, &mut collector, &paths);
787 return Ok(collector);
789 }
790 }
791
792 let build_dir = output_relative_path(&cx.config, relative_dir_path);
799 fs::create_dir_all(&build_dir).unwrap();
800
801 fs::read_dir(dir.as_std_path())?
806 .par_bridge()
807 .map(|file| {
808 let mut collector = TestCollector::new();
809 let file = file?;
810 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
811 let file_name = file_path.file_name().unwrap();
812
813 if is_test(file_name)
814 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
815 {
816 debug!(%file_path, "found test file");
818
819 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
821 collector.found_path_stems.insert(rel_test_path);
822
823 let paths =
824 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
825 make_test(cx, &mut collector, &paths);
826 } else if file_path.is_dir() {
827 let relative_file_path = relative_dir_path.join(file_name);
829 if file_name != "auxiliary" {
830 debug!(%file_path, "found directory");
831 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
832 }
833 } else {
834 debug!(%file_path, "found other file/directory");
835 }
836 Ok(collector)
837 })
838 .reduce(
839 || Ok(TestCollector::new()),
840 |a, b| {
841 let mut a = a?;
842 a.merge(b?);
843 Ok(a)
844 },
845 )
846}
847
848fn is_test(file_name: &str) -> bool {
850 if !file_name.ends_with(".rs") {
851 return false;
852 }
853
854 let invalid_prefixes = &[".", "#", "~"];
856 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
857}
858
859fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
862 let test_path = if cx.config.mode == TestMode::RunMake {
866 testpaths.file.join("rmake.rs")
867 } else {
868 testpaths.file.clone()
869 };
870
871 let file_contents =
873 fs::read_to_string(&test_path).expect("reading test file for directives should succeed");
874 let file_directives = FileDirectives::from_file_contents(&test_path, &file_contents);
875
876 if let Err(message) = directives::do_early_directives_check(cx.config.mode, &file_directives) {
877 panic!("directives check failed:\n{message}");
880 }
881 let early_props = EarlyProps::from_file_directives(&cx.config, &file_directives);
882
883 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
890 vec![None]
891 } else {
892 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
893 };
894
895 collector.tests.extend(revisions.into_iter().map(|revision| {
898 let (test_name, filterable_path) =
900 make_test_name_and_filterable_path(&cx.config, testpaths, revision);
901
902 let mut aux_props = AuxProps::default();
905
906 let mut desc = make_test_description(
910 &cx.config,
911 &cx.cache,
912 test_name,
913 &test_path,
914 &filterable_path,
915 &file_directives,
916 revision,
917 &mut collector.poisoned,
918 &mut aux_props,
919 );
920
921 if !desc.ignore
924 && !cx.config.force_rerun
925 && is_up_to_date(cx, testpaths, &aux_props, revision)
926 {
927 desc.ignore = true;
928 desc.ignore_message = Some("up-to-date".into());
932 }
933
934 let config = Arc::clone(&cx.config);
935 let testpaths = testpaths.clone();
936 let revision = revision.map(str::to_owned);
937
938 CollectedTest { desc, config, testpaths, revision }
939 }));
940}
941
942fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
945 output_base_dir(config, testpaths, revision).join("stamp")
946}
947
948fn files_related_to_test(
953 config: &Config,
954 testpaths: &TestPaths,
955 aux_props: &AuxProps,
956 revision: Option<&str>,
957) -> Vec<Utf8PathBuf> {
958 let mut related = vec![];
959
960 if testpaths.file.is_dir() {
961 for entry in WalkDir::new(&testpaths.file) {
963 let path = entry.unwrap().into_path();
964 if path.is_file() {
965 related.push(Utf8PathBuf::try_from(path).unwrap());
966 }
967 }
968 } else {
969 related.push(testpaths.file.clone());
970 }
971
972 for aux in aux_props.all_aux_path_strings() {
973 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
978 related.push(path);
979 }
980
981 for extension in UI_EXTENSIONS {
983 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
984 related.push(path);
985 }
986
987 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
989
990 related
991}
992
993fn is_up_to_date(
999 cx: &TestCollectorCx,
1000 testpaths: &TestPaths,
1001 aux_props: &AuxProps,
1002 revision: Option<&str>,
1003) -> bool {
1004 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1005 let contents = match fs::read_to_string(&stamp_file_path) {
1007 Ok(f) => f,
1008 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1009 Err(_) => return false,
1011 };
1012 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1013 if contents != expected_hash {
1014 return false;
1017 }
1018
1019 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1022 for path in files_related_to_test(&cx.config, testpaths, aux_props, revision) {
1023 inputs_stamp.add_path(&path);
1024 }
1025
1026 inputs_stamp < Stamp::from_path(&stamp_file_path)
1029}
1030
1031#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1033struct Stamp {
1034 time: SystemTime,
1035}
1036
1037impl Stamp {
1038 fn from_path(path: &Utf8Path) -> Self {
1040 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1041 stamp.add_path(path);
1042 stamp
1043 }
1044
1045 fn add_path(&mut self, path: &Utf8Path) {
1048 let modified = fs::metadata(path.as_std_path())
1049 .and_then(|metadata| metadata.modified())
1050 .unwrap_or(SystemTime::UNIX_EPOCH);
1051 self.time = self.time.max(modified);
1052 }
1053
1054 fn add_dir(&mut self, path: &Utf8Path) {
1058 let path = path.as_std_path();
1059 for entry in WalkDir::new(path) {
1060 let entry = entry.unwrap();
1061 if entry.file_type().is_file() {
1062 let modified = entry
1063 .metadata()
1064 .ok()
1065 .and_then(|metadata| metadata.modified().ok())
1066 .unwrap_or(SystemTime::UNIX_EPOCH);
1067 self.time = self.time.max(modified);
1068 }
1069 }
1070 }
1071}
1072
1073fn make_test_name_and_filterable_path(
1075 config: &Config,
1076 testpaths: &TestPaths,
1077 revision: Option<&str>,
1078) -> (String, Utf8PathBuf) {
1079 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1081 let debugger = match config.debugger {
1082 Some(d) => format!("-{}", d),
1083 None => String::new(),
1084 };
1085 let mode_suffix = match config.compare_mode {
1086 Some(ref mode) => format!(" ({})", mode.to_str()),
1087 None => String::new(),
1088 };
1089
1090 let name = format!(
1091 "[{}{}{}] {}{}",
1092 config.mode,
1093 debugger,
1094 mode_suffix,
1095 path,
1096 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1097 );
1098
1099 let mut filterable_path = path.strip_prefix("tests").unwrap().to_owned();
1103 filterable_path = filterable_path.components().skip(1).collect();
1105
1106 (name, filterable_path)
1107}
1108
1109fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1127 let mut collisions = Vec::new();
1128 for path in found_path_stems {
1129 for ancestor in path.ancestors().skip(1) {
1130 if found_path_stems.contains(ancestor) {
1131 collisions.push((path, ancestor));
1132 }
1133 }
1134 }
1135 if !collisions.is_empty() {
1136 collisions.sort();
1137 let collisions: String = collisions
1138 .into_iter()
1139 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1140 .collect();
1141 panic!(
1142 "{collisions}\n\
1143 Tests cannot have overlapping names. Make sure they use unique prefixes."
1144 );
1145 }
1146}
1147
1148fn early_config_check(config: &Config) {
1149 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1150 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1151 }
1152
1153 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1154 let actioned = if config.bless { "blessed" } else { "checked" };
1155 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1156 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1157 }
1158
1159 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1161 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1162 }
1163}