1#![crate_name = "compiletest"]
2#![feature(internal_output_capture)]
7
8#[cfg(test)]
9mod tests;
10
11pub mod common;
12pub mod compute_diff;
13mod debuggers;
14pub mod diagnostics;
15pub mod directives;
16pub mod errors;
17mod executor;
18mod json;
19mod raise_fd_limit;
20mod read2;
21pub mod runtest;
22pub mod util;
23
24use core::panic;
25use std::collections::HashSet;
26use std::fmt::Write;
27use std::io::{self, ErrorKind};
28use std::process::{Command, Stdio};
29use std::sync::{Arc, OnceLock};
30use std::time::SystemTime;
31use std::{env, fs, vec};
32
33use build_helper::git::{get_git_modified_files, get_git_untracked_files};
34use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
35use getopts::Options;
36use rayon::iter::{ParallelBridge, ParallelIterator};
37use tracing::debug;
38use walkdir::WalkDir;
39
40use self::directives::{EarlyProps, make_test_description};
41use crate::common::{
42 CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
43 expected_output_path, output_base_dir, output_relative_path,
44};
45use crate::directives::DirectivesCache;
46use crate::executor::{CollectedTest, ColorConfig, OutputFormat};
47use crate::util::logv;
48
49pub fn parse_config(args: Vec<String>) -> Config {
55 let mut opts = Options::new();
56 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
57 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
58 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
59 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
60 .optopt(
61 "",
62 "stage0-rustc-path",
63 "path to rustc to use for compiling run-make recipes",
64 "PATH",
65 )
66 .optopt(
67 "",
68 "query-rustc-path",
69 "path to rustc to use for querying target information (defaults to `--rustc-path`)",
70 "PATH",
71 )
72 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
73 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
74 .reqopt("", "python", "path to python to use for doc tests", "PATH")
75 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
76 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
77 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
78 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
79 .reqopt("", "src-root", "directory containing sources", "PATH")
80 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
81 .reqopt("", "build-root", "path to root build directory", "PATH")
82 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
83 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
84 .reqopt("", "stage", "stage number under test", "N")
85 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
86 .reqopt(
87 "",
88 "mode",
89 "which sort of compile tests to run",
90 "pretty | debug-info | codegen | rustdoc \
91 | rustdoc-json | codegen-units | incremental | run-make | ui \
92 | rustdoc-js | mir-opt | assembly | crashes",
93 )
94 .reqopt(
95 "",
96 "suite",
97 "which suite of compile tests to run. used for nicer error reporting.",
98 "SUITE",
99 )
100 .optopt(
101 "",
102 "pass",
103 "force {check,build,run}-pass tests to this mode.",
104 "check | build | run",
105 )
106 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
107 .optflag("", "ignored", "run tests marked as ignored")
108 .optflag("", "has-enzyme", "run tests that require enzyme")
109 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
110 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
111 .optmulti(
112 "",
113 "skip",
114 "skip tests matching SUBSTRING. Can be passed multiple times",
115 "SUBSTRING",
116 )
117 .optflag("", "exact", "filters match exactly")
118 .optopt(
119 "",
120 "runner",
121 "supervisor program to run tests under \
122 (eg. emulator, valgrind)",
123 "PROGRAM",
124 )
125 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
126 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
127 .optflag(
128 "",
129 "rust-randomized-layout",
130 "set this when rustc/stdlib were compiled with randomized layouts",
131 )
132 .optflag("", "optimize-tests", "run tests with optimizations enabled")
133 .optflag("", "verbose", "run tests verbosely, showing all output")
134 .optflag(
135 "",
136 "bless",
137 "overwrite stderr/stdout files instead of complaining about a mismatch",
138 )
139 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
140 .optflag("", "quiet", "print one character per test instead of one line")
141 .optopt("", "color", "coloring: auto, always, never", "WHEN")
142 .optflag("", "json", "emit json output instead of plaintext output")
143 .optopt("", "target", "the target to build for", "TARGET")
144 .optopt("", "host", "the host to build for", "HOST")
145 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
146 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
147 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
148 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
149 .optflag("", "system-llvm", "is LLVM the system LLVM")
150 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
151 .optopt("", "adb-path", "path to the android debugger", "PATH")
152 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
153 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
154 .reqopt("", "cc", "path to a C compiler", "PATH")
155 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
156 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
157 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
158 .optopt("", "ar", "path to an archiver", "PATH")
159 .optopt("", "target-linker", "path to a linker for the target", "PATH")
160 .optopt("", "host-linker", "path to a linker for the host", "PATH")
161 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
162 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
163 .optopt("", "nodejs", "the name of nodejs", "PATH")
164 .optopt("", "npm", "the name of npm", "PATH")
165 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
166 .optopt(
167 "",
168 "compare-mode",
169 "mode describing what file the actual ui output will be compared to",
170 "COMPARE MODE",
171 )
172 .optflag(
173 "",
174 "rustfix-coverage",
175 "enable this to generate a Rustfix coverage file, which is saved in \
176 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
177 )
178 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
179 .optflag("", "only-modified", "only run tests that result been modified")
180 .optflag("", "nocapture", "")
182 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
183 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
184 .optflag("h", "help", "show this message")
185 .reqopt("", "channel", "current Rust channel", "CHANNEL")
186 .optflag(
187 "",
188 "git-hash",
189 "run tests which rely on commit version being compiled into the binaries",
190 )
191 .optopt("", "edition", "default Rust edition", "EDITION")
192 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
193 .reqopt(
194 "",
195 "git-merge-commit-email",
196 "email address used for finding merge commits",
197 "EMAIL",
198 )
199 .optopt(
200 "",
201 "compiletest-diff-tool",
202 "What custom diff tool to use for displaying compiletest tests.",
203 "COMMAND",
204 )
205 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
206 .optflag("N", "no-new-executor", "disables the new test executor, and uses libtest instead")
207 .optopt(
208 "",
209 "debugger",
210 "only test a specific debugger in debuginfo tests",
211 "gdb | lldb | cdb",
212 )
213 .optopt(
214 "",
215 "codegen-backend",
216 "the codegen backend currently used",
217 "CODEGEN BACKEND NAME",
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 codegen_backend = match matches.opt_str("codegen-backend").as_deref() {
280 Some(backend) => match CodegenBackend::try_from(backend) {
281 Ok(backend) => backend,
282 Err(error) => panic!("invalid value `{backend}` for `--codegen-backend`: {error}"),
283 },
284 None => CodegenBackend::Llvm,
286 };
287
288 let run_ignored = matches.opt_present("ignored");
289 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
290 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
291 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
292 let has_html_tidy = if mode == TestMode::Rustdoc {
293 Command::new("tidy")
294 .arg("--version")
295 .stdout(Stdio::null())
296 .status()
297 .map_or(false, |status| status.success())
298 } else {
299 false
301 };
302 let has_enzyme = matches.opt_present("has-enzyme");
303 let filters = if mode == TestMode::RunMake {
304 matches
305 .free
306 .iter()
307 .map(|f| {
308 let path = Utf8Path::new(f);
309 let mut iter = path.iter().skip(1);
310
311 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
313 path.parent().unwrap().to_string()
314 } else {
315 f.to_string()
316 }
317 })
318 .collect::<Vec<_>>()
319 } else {
320 matches.free.clone()
321 };
322 let compare_mode = matches.opt_str("compare-mode").map(|s| {
323 s.parse().unwrap_or_else(|_| {
324 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
325 panic!(
326 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
327 variants.join(", ")
328 );
329 })
330 });
331 if matches.opt_present("nocapture") {
332 panic!("`--nocapture` is deprecated; please use `--no-capture`");
333 }
334
335 let stage = match matches.opt_str("stage") {
336 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
337 None => panic!("`--stage` is required"),
338 };
339
340 let src_root = opt_path(matches, "src-root");
341 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
342 assert!(
343 src_test_suite_root.starts_with(&src_root),
344 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
345 src_root,
346 src_test_suite_root
347 );
348
349 let build_root = opt_path(matches, "build-root");
350 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
351 assert!(build_test_suite_root.starts_with(&build_root));
352
353 Config {
354 bless: matches.opt_present("bless"),
355 fail_fast: matches.opt_present("fail-fast")
356 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
357
358 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
359 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
360 rustc_path: opt_path(matches, "rustc-path"),
361 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
362 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
363 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
364 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
365 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
366 python: matches.opt_str("python").unwrap(),
367 jsondocck_path: matches.opt_str("jsondocck-path"),
368 jsondoclint_path: matches.opt_str("jsondoclint-path"),
369 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
370 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
371 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
372
373 src_root,
374 src_test_suite_root,
375
376 build_root,
377 build_test_suite_root,
378
379 sysroot_base: opt_path(matches, "sysroot-base"),
380
381 stage,
382 stage_id: matches.opt_str("stage-id").unwrap(),
383
384 mode,
385 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
386 debugger: matches.opt_str("debugger").map(|debugger| {
387 debugger
388 .parse::<Debugger>()
389 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
390 }),
391 run_ignored,
392 with_rustc_debug_assertions,
393 with_std_debug_assertions,
394 filters,
395 skip: matches.opt_strs("skip"),
396 filter_exact: matches.opt_present("exact"),
397 force_pass_mode: matches.opt_str("pass").map(|mode| {
398 mode.parse::<PassMode>()
399 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
400 }),
401 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
403 "auto" => None,
404 "always" => Some(true),
405 "never" => Some(false),
406 _ => panic!("unknown `--run` option `{}` given", mode),
407 }),
408 runner: matches.opt_str("runner"),
409 host_rustcflags: matches.opt_strs("host-rustcflags"),
410 target_rustcflags: matches.opt_strs("target-rustcflags"),
411 optimize_tests: matches.opt_present("optimize-tests"),
412 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
413 target,
414 host: opt_str2(matches.opt_str("host")),
415 cdb,
416 cdb_version,
417 gdb,
418 gdb_version,
419 lldb_version,
420 llvm_version,
421 system_llvm: matches.opt_present("system-llvm"),
422 android_cross_path,
423 adb_path: opt_str2(matches.opt_str("adb-path")),
424 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
425 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
426 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
427 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
428 lldb_python_dir: matches.opt_str("lldb-python-dir"),
429 verbose: matches.opt_present("verbose"),
430 format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
431 (true, true) => panic!("--quiet and --json are incompatible"),
432 (true, false) => OutputFormat::Terse,
433 (false, true) => OutputFormat::Json,
434 (false, false) => OutputFormat::Pretty,
435 },
436 only_modified: matches.opt_present("only-modified"),
437 color,
438 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
439 compare_mode,
440 rustfix_coverage: matches.opt_present("rustfix-coverage"),
441 has_html_tidy,
442 has_enzyme,
443 channel: matches.opt_str("channel").unwrap(),
444 git_hash: matches.opt_present("git-hash"),
445 edition: matches.opt_str("edition"),
446
447 cc: matches.opt_str("cc").unwrap(),
448 cxx: matches.opt_str("cxx").unwrap(),
449 cflags: matches.opt_str("cflags").unwrap(),
450 cxxflags: matches.opt_str("cxxflags").unwrap(),
451 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
452 target_linker: matches.opt_str("target-linker"),
453 host_linker: matches.opt_str("host-linker"),
454 llvm_components: matches.opt_str("llvm-components").unwrap(),
455 nodejs: matches.opt_str("nodejs"),
456 npm: matches.opt_str("npm"),
457
458 force_rerun: matches.opt_present("force-rerun"),
459
460 target_cfgs: OnceLock::new(),
461 builtin_cfg_names: OnceLock::new(),
462 supported_crate_types: OnceLock::new(),
463
464 nocapture: matches.opt_present("no-capture"),
465
466 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
467 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
468
469 profiler_runtime: matches.opt_present("profiler-runtime"),
470
471 diff_command: matches.opt_str("compiletest-diff-tool"),
472
473 minicore_path: opt_path(matches, "minicore-path"),
474
475 codegen_backend,
476 }
477}
478
479pub fn log_config(config: &Config) {
480 let c = config;
481 logv(c, "configuration:".to_string());
482 logv(c, format!("compile_lib_path: {}", config.compile_lib_path));
483 logv(c, format!("run_lib_path: {}", config.run_lib_path));
484 logv(c, format!("rustc_path: {}", config.rustc_path));
485 logv(c, format!("cargo_path: {:?}", config.cargo_path));
486 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
487
488 logv(c, format!("src_root: {}", config.src_root));
489 logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root));
490
491 logv(c, format!("build_root: {}", config.build_root));
492 logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root));
493
494 logv(c, format!("sysroot_base: {}", config.sysroot_base));
495
496 logv(c, format!("stage: {}", config.stage));
497 logv(c, format!("stage_id: {}", config.stage_id));
498 logv(c, format!("mode: {}", config.mode));
499 logv(c, format!("run_ignored: {}", config.run_ignored));
500 logv(c, format!("filters: {:?}", config.filters));
501 logv(c, format!("skip: {:?}", config.skip));
502 logv(c, format!("filter_exact: {}", config.filter_exact));
503 logv(
504 c,
505 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
506 );
507 logv(c, format!("runner: {}", opt_str(&config.runner)));
508 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
509 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
510 logv(c, format!("target: {}", config.target));
511 logv(c, format!("host: {}", config.host));
512 logv(c, format!("android-cross-path: {}", config.android_cross_path));
513 logv(c, format!("adb_path: {}", config.adb_path));
514 logv(c, format!("adb_test_dir: {}", config.adb_test_dir));
515 logv(c, format!("adb_device_status: {}", config.adb_device_status));
516 logv(c, format!("ar: {}", config.ar));
517 logv(c, format!("target-linker: {:?}", config.target_linker));
518 logv(c, format!("host-linker: {:?}", config.host_linker));
519 logv(c, format!("verbose: {}", config.verbose));
520 logv(c, format!("format: {:?}", config.format));
521 logv(c, format!("minicore_path: {}", config.minicore_path));
522 logv(c, "\n".to_string());
523}
524
525pub fn opt_str(maybestr: &Option<String>) -> &str {
526 match *maybestr {
527 None => "(none)",
528 Some(ref s) => s,
529 }
530}
531
532pub fn opt_str2(maybestr: Option<String>) -> String {
533 match maybestr {
534 None => "(none)".to_owned(),
535 Some(s) => s,
536 }
537}
538
539pub fn run_tests(config: Arc<Config>) {
541 if config.rustfix_coverage {
545 let mut coverage_file_path = config.build_test_suite_root.clone();
546 coverage_file_path.push("rustfix_missing_coverage.txt");
547 if coverage_file_path.exists() {
548 if let Err(e) = fs::remove_file(&coverage_file_path) {
549 panic!("Could not delete {} due to {}", coverage_file_path, e)
550 }
551 }
552 }
553
554 unsafe {
558 raise_fd_limit::raise_fd_limit();
559 }
560 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
565
566 unsafe { env::set_var("TARGET", &config.target) };
570
571 let mut configs = Vec::new();
572 if let TestMode::DebugInfo = config.mode {
573 if !config.target.contains("emscripten") {
575 match config.debugger {
576 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
577 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
578 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
579 None => {
584 configs.extend(debuggers::configure_cdb(&config));
585 configs.extend(debuggers::configure_gdb(&config));
586 configs.extend(debuggers::configure_lldb(&config));
587 }
588 }
589 }
590 } else {
591 configs.push(config.clone());
592 };
593
594 let mut tests = Vec::new();
597 for c in configs {
598 tests.extend(collect_and_make_tests(c));
599 }
600
601 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
602
603 let res: io::Result<bool> = Ok(executor::run_tests(&config, tests));
610
611 match res {
613 Ok(true) => {}
614 Ok(false) => {
615 let mut msg = String::from("Some tests failed in compiletest");
623 write!(msg, " suite={}", config.suite).unwrap();
624
625 if let Some(compare_mode) = config.compare_mode.as_ref() {
626 write!(msg, " compare_mode={}", compare_mode).unwrap();
627 }
628
629 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
630 write!(msg, " pass_mode={}", pass_mode).unwrap();
631 }
632
633 write!(msg, " mode={}", config.mode).unwrap();
634 write!(msg, " host={}", config.host).unwrap();
635 write!(msg, " target={}", config.target).unwrap();
636
637 println!("{msg}");
638
639 std::process::exit(1);
640 }
641 Err(e) => {
642 panic!("I/O failure during tests: {:?}", e);
649 }
650 }
651}
652
653struct TestCollectorCx {
655 config: Arc<Config>,
656 cache: DirectivesCache,
657 common_inputs_stamp: Stamp,
658 modified_tests: Vec<Utf8PathBuf>,
659}
660
661struct TestCollector {
663 tests: Vec<CollectedTest>,
664 found_path_stems: HashSet<Utf8PathBuf>,
665 poisoned: bool,
666}
667
668impl TestCollector {
669 fn new() -> Self {
670 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
671 }
672
673 fn merge(&mut self, mut other: Self) {
674 self.tests.append(&mut other.tests);
675 self.found_path_stems.extend(other.found_path_stems);
676 self.poisoned |= other.poisoned;
677 }
678}
679
680pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
686 debug!("making tests from {}", config.src_test_suite_root);
687 let common_inputs_stamp = common_inputs_stamp(&config);
688 let modified_tests =
689 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
690 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
691 });
692 let cache = DirectivesCache::load(&config);
693
694 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
695 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
696 .unwrap_or_else(|reason| {
697 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
698 });
699
700 let TestCollector { tests, found_path_stems, poisoned } = collector;
701
702 if poisoned {
703 eprintln!();
704 panic!("there are errors in tests");
705 }
706
707 check_for_overlapping_test_paths(&found_path_stems);
708
709 tests
710}
711
712fn common_inputs_stamp(config: &Config) -> Stamp {
720 let src_root = &config.src_root;
721
722 let mut stamp = Stamp::from_path(&config.rustc_path);
723
724 let pretty_printer_files = [
726 "src/etc/rust_types.py",
727 "src/etc/gdb_load_rust_pretty_printers.py",
728 "src/etc/gdb_lookup.py",
729 "src/etc/gdb_providers.py",
730 "src/etc/lldb_batchmode.py",
731 "src/etc/lldb_lookup.py",
732 "src/etc/lldb_providers.py",
733 ];
734 for file in &pretty_printer_files {
735 let path = src_root.join(file);
736 stamp.add_path(&path);
737 }
738
739 stamp.add_dir(&src_root.join("src/etc/natvis"));
740
741 stamp.add_dir(&config.run_lib_path);
742
743 if let Some(ref rustdoc_path) = config.rustdoc_path {
744 stamp.add_path(&rustdoc_path);
745 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
746 }
747
748 if let Some(coverage_dump_path) = &config.coverage_dump_path {
751 stamp.add_path(coverage_dump_path)
752 }
753
754 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
755
756 stamp.add_dir(&src_root.join("src/tools/compiletest"));
758
759 stamp
760}
761
762fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
767 if !config.only_modified {
770 return Ok(vec![]);
771 }
772
773 let files = get_git_modified_files(
774 &config.git_config(),
775 Some(dir.as_std_path()),
776 &vec!["rs", "stderr", "fixed"],
777 )?;
778 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
780
781 let all_paths = [&files[..], &untracked_files[..]].concat();
782 let full_paths = {
783 let mut full_paths: Vec<Utf8PathBuf> = all_paths
784 .into_iter()
785 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
786 .filter_map(
787 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
788 )
789 .collect();
790 full_paths.dedup();
791 full_paths.sort_unstable();
792 full_paths
793 };
794 Ok(full_paths)
795}
796
797fn collect_tests_from_dir(
800 cx: &TestCollectorCx,
801 dir: &Utf8Path,
802 relative_dir_path: &Utf8Path,
803) -> io::Result<TestCollector> {
804 if dir.join("compiletest-ignore-dir").exists() {
806 return Ok(TestCollector::new());
807 }
808
809 let mut components = dir.components().rev();
810 if let Some(Utf8Component::Normal(last)) = components.next()
811 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
812 && let Some(Utf8Component::Normal(parent)) = components.next()
813 && parent == "tests"
814 && let Ok(backend) = CodegenBackend::try_from(backend)
815 && backend != cx.config.codegen_backend
816 {
817 warning!(
819 "Ignoring tests in `{dir}` because they don't match the configured codegen \
820 backend (`{}`)",
821 cx.config.codegen_backend.as_str(),
822 );
823 return Ok(TestCollector::new());
824 }
825
826 if cx.config.mode == TestMode::RunMake {
828 let mut collector = TestCollector::new();
829 if dir.join("rmake.rs").exists() {
830 let paths = TestPaths {
831 file: dir.to_path_buf(),
832 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
833 };
834 make_test(cx, &mut collector, &paths);
835 return Ok(collector);
837 }
838 }
839
840 let build_dir = output_relative_path(&cx.config, relative_dir_path);
847 fs::create_dir_all(&build_dir).unwrap();
848
849 fs::read_dir(dir.as_std_path())?
854 .par_bridge()
855 .map(|file| {
856 let mut collector = TestCollector::new();
857 let file = file?;
858 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
859 let file_name = file_path.file_name().unwrap();
860
861 if is_test(file_name)
862 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
863 {
864 debug!(%file_path, "found test file");
866
867 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
869 collector.found_path_stems.insert(rel_test_path);
870
871 let paths =
872 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
873 make_test(cx, &mut collector, &paths);
874 } else if file_path.is_dir() {
875 let relative_file_path = relative_dir_path.join(file_name);
877 if file_name != "auxiliary" {
878 debug!(%file_path, "found directory");
879 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
880 }
881 } else {
882 debug!(%file_path, "found other file/directory");
883 }
884 Ok(collector)
885 })
886 .reduce(
887 || Ok(TestCollector::new()),
888 |a, b| {
889 let mut a = a?;
890 a.merge(b?);
891 Ok(a)
892 },
893 )
894}
895
896pub fn is_test(file_name: &str) -> bool {
898 if !file_name.ends_with(".rs") {
899 return false;
900 }
901
902 let invalid_prefixes = &[".", "#", "~"];
904 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
905}
906
907fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
910 let test_path = if cx.config.mode == TestMode::RunMake {
914 testpaths.file.join("rmake.rs")
915 } else {
916 testpaths.file.clone()
917 };
918
919 let early_props = EarlyProps::from_file(&cx.config, &test_path);
921
922 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
929 vec![None]
930 } else {
931 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
932 };
933
934 collector.tests.extend(revisions.into_iter().map(|revision| {
937 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
939 let test_name = make_test_name(&cx.config, testpaths, revision);
940 let mut desc = make_test_description(
944 &cx.config,
945 &cx.cache,
946 test_name,
947 &test_path,
948 src_file,
949 revision,
950 &mut collector.poisoned,
951 );
952
953 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
956 desc.ignore = true;
957 desc.ignore_message = Some("up-to-date".into());
959 }
960
961 let config = Arc::clone(&cx.config);
962 let testpaths = testpaths.clone();
963 let revision = revision.map(str::to_owned);
964
965 CollectedTest { desc, config, testpaths, revision }
966 }));
967}
968
969fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
972 output_base_dir(config, testpaths, revision).join("stamp")
973}
974
975fn files_related_to_test(
980 config: &Config,
981 testpaths: &TestPaths,
982 props: &EarlyProps,
983 revision: Option<&str>,
984) -> Vec<Utf8PathBuf> {
985 let mut related = vec![];
986
987 if testpaths.file.is_dir() {
988 for entry in WalkDir::new(&testpaths.file) {
990 let path = entry.unwrap().into_path();
991 if path.is_file() {
992 related.push(Utf8PathBuf::try_from(path).unwrap());
993 }
994 }
995 } else {
996 related.push(testpaths.file.clone());
997 }
998
999 for aux in props.aux.all_aux_path_strings() {
1000 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
1002 related.push(path);
1003 }
1004
1005 for extension in UI_EXTENSIONS {
1007 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1008 related.push(path);
1009 }
1010
1011 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1013
1014 related
1015}
1016
1017fn is_up_to_date(
1023 cx: &TestCollectorCx,
1024 testpaths: &TestPaths,
1025 props: &EarlyProps,
1026 revision: Option<&str>,
1027) -> bool {
1028 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1029 let contents = match fs::read_to_string(&stamp_file_path) {
1031 Ok(f) => f,
1032 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1033 Err(_) => return false,
1035 };
1036 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1037 if contents != expected_hash {
1038 return false;
1041 }
1042
1043 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1046 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1047 inputs_stamp.add_path(&path);
1048 }
1049
1050 inputs_stamp < Stamp::from_path(&stamp_file_path)
1053}
1054
1055#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1057struct Stamp {
1058 time: SystemTime,
1059}
1060
1061impl Stamp {
1062 fn from_path(path: &Utf8Path) -> Self {
1064 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1065 stamp.add_path(path);
1066 stamp
1067 }
1068
1069 fn add_path(&mut self, path: &Utf8Path) {
1072 let modified = fs::metadata(path.as_std_path())
1073 .and_then(|metadata| metadata.modified())
1074 .unwrap_or(SystemTime::UNIX_EPOCH);
1075 self.time = self.time.max(modified);
1076 }
1077
1078 fn add_dir(&mut self, path: &Utf8Path) {
1082 let path = path.as_std_path();
1083 for entry in WalkDir::new(path) {
1084 let entry = entry.unwrap();
1085 if entry.file_type().is_file() {
1086 let modified = entry
1087 .metadata()
1088 .ok()
1089 .and_then(|metadata| metadata.modified().ok())
1090 .unwrap_or(SystemTime::UNIX_EPOCH);
1091 self.time = self.time.max(modified);
1092 }
1093 }
1094 }
1095}
1096
1097fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1099 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1101 let debugger = match config.debugger {
1102 Some(d) => format!("-{}", d),
1103 None => String::new(),
1104 };
1105 let mode_suffix = match config.compare_mode {
1106 Some(ref mode) => format!(" ({})", mode.to_str()),
1107 None => String::new(),
1108 };
1109
1110 format!(
1111 "[{}{}{}] {}{}",
1112 config.mode,
1113 debugger,
1114 mode_suffix,
1115 path,
1116 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1117 )
1118}
1119
1120fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1138 let mut collisions = Vec::new();
1139 for path in found_path_stems {
1140 for ancestor in path.ancestors().skip(1) {
1141 if found_path_stems.contains(ancestor) {
1142 collisions.push((path, ancestor));
1143 }
1144 }
1145 }
1146 if !collisions.is_empty() {
1147 collisions.sort();
1148 let collisions: String = collisions
1149 .into_iter()
1150 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1151 .collect();
1152 panic!(
1153 "{collisions}\n\
1154 Tests cannot have overlapping names. Make sure they use unique prefixes."
1155 );
1156 }
1157}
1158
1159pub fn early_config_check(config: &Config) {
1160 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1161 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1162 }
1163
1164 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1165 let actioned = if config.bless { "blessed" } else { "checked" };
1166 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1167 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1168 }
1169
1170 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1172 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1173 }
1174}