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("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
67 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
68 .reqopt("", "python", "path to python to use for doc tests", "PATH")
69 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
70 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
71 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
72 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
73 .reqopt("", "src-root", "directory containing sources", "PATH")
74 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
75 .reqopt("", "build-root", "path to root build directory", "PATH")
76 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
77 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
78 .reqopt("", "stage", "stage number under test", "N")
79 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
80 .reqopt(
81 "",
82 "mode",
83 "which sort of compile tests to run",
84 "pretty | debug-info | codegen | rustdoc \
85 | rustdoc-json | codegen-units | incremental | run-make | ui \
86 | rustdoc-js | mir-opt | assembly | crashes",
87 )
88 .reqopt(
89 "",
90 "suite",
91 "which suite of compile tests to run. used for nicer error reporting.",
92 "SUITE",
93 )
94 .optopt(
95 "",
96 "pass",
97 "force {check,build,run}-pass tests to this mode.",
98 "check | build | run",
99 )
100 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
101 .optflag("", "ignored", "run tests marked as ignored")
102 .optflag("", "has-enzyme", "run tests that require enzyme")
103 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
104 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
105 .optmulti(
106 "",
107 "skip",
108 "skip tests matching SUBSTRING. Can be passed multiple times",
109 "SUBSTRING",
110 )
111 .optflag("", "exact", "filters match exactly")
112 .optopt(
113 "",
114 "runner",
115 "supervisor program to run tests under \
116 (eg. emulator, valgrind)",
117 "PROGRAM",
118 )
119 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
120 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
121 .optflag(
122 "",
123 "rust-randomized-layout",
124 "set this when rustc/stdlib were compiled with randomized layouts",
125 )
126 .optflag("", "optimize-tests", "run tests with optimizations enabled")
127 .optflag("", "verbose", "run tests verbosely, showing all output")
128 .optflag(
129 "",
130 "bless",
131 "overwrite stderr/stdout files instead of complaining about a mismatch",
132 )
133 .optflag("", "fail-fast", "stop as soon as possible after any test fails")
134 .optflag("", "quiet", "print one character per test instead of one line")
135 .optopt("", "color", "coloring: auto, always, never", "WHEN")
136 .optflag("", "json", "emit json output instead of plaintext output")
137 .optopt("", "target", "the target to build for", "TARGET")
138 .optopt("", "host", "the host to build for", "HOST")
139 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
140 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
141 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
142 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
143 .optflag("", "system-llvm", "is LLVM the system LLVM")
144 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
145 .optopt("", "adb-path", "path to the android debugger", "PATH")
146 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
147 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
148 .reqopt("", "cc", "path to a C compiler", "PATH")
149 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
150 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
151 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
152 .optopt("", "ar", "path to an archiver", "PATH")
153 .optopt("", "target-linker", "path to a linker for the target", "PATH")
154 .optopt("", "host-linker", "path to a linker for the host", "PATH")
155 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
156 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
157 .optopt("", "nodejs", "the name of nodejs", "PATH")
158 .optopt("", "npm", "the name of npm", "PATH")
159 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
160 .optopt(
161 "",
162 "compare-mode",
163 "mode describing what file the actual ui output will be compared to",
164 "COMPARE MODE",
165 )
166 .optflag(
167 "",
168 "rustfix-coverage",
169 "enable this to generate a Rustfix coverage file, which is saved in \
170 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
171 )
172 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
173 .optflag("", "only-modified", "only run tests that result been modified")
174 .optflag("", "nocapture", "")
176 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
177 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
178 .optflag("h", "help", "show this message")
179 .reqopt("", "channel", "current Rust channel", "CHANNEL")
180 .optflag(
181 "",
182 "git-hash",
183 "run tests which rely on commit version being compiled into the binaries",
184 )
185 .optopt("", "edition", "default Rust edition", "EDITION")
186 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
187 .reqopt(
188 "",
189 "git-merge-commit-email",
190 "email address used for finding merge commits",
191 "EMAIL",
192 )
193 .optopt(
194 "",
195 "compiletest-diff-tool",
196 "What custom diff tool to use for displaying compiletest tests.",
197 "COMMAND",
198 )
199 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
200 .optflag("N", "no-new-executor", "disables the new test executor, and uses libtest instead")
201 .optopt(
202 "",
203 "debugger",
204 "only test a specific debugger in debuginfo tests",
205 "gdb | lldb | cdb",
206 )
207 .optopt(
208 "",
209 "codegen-backend",
210 "the codegen backend currently used",
211 "CODEGEN BACKEND NAME",
212 );
213
214 let (argv0, args_) = args.split_first().unwrap();
215 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
216 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
217 println!("{}", opts.usage(&message));
218 println!();
219 panic!()
220 }
221
222 let matches = &match opts.parse(args_) {
223 Ok(m) => m,
224 Err(f) => panic!("{:?}", f),
225 };
226
227 if matches.opt_present("h") || matches.opt_present("help") {
228 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
229 println!("{}", opts.usage(&message));
230 println!();
231 panic!()
232 }
233
234 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
235 if path.is_relative() {
236 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
237 } else {
238 path
239 }
240 }
241
242 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
243 match m.opt_str(nm) {
244 Some(s) => Utf8PathBuf::from(&s),
245 None => panic!("no option (=path) found for {}", nm),
246 }
247 }
248
249 let target = opt_str2(matches.opt_str("target"));
250 let android_cross_path = opt_path(matches, "android-cross-path");
251 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
253 let (gdb, gdb_version) =
255 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
256 let lldb_version =
258 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
259 let color = match matches.opt_str("color").as_deref() {
260 Some("auto") | None => ColorConfig::AutoColor,
261 Some("always") => ColorConfig::AlwaysColor,
262 Some("never") => ColorConfig::NeverColor,
263 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
264 };
265 let llvm_version =
269 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
270 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
271 );
272
273 let codegen_backend = match matches.opt_str("codegen-backend").as_deref() {
274 Some(backend) => match CodegenBackend::try_from(backend) {
275 Ok(backend) => backend,
276 Err(error) => panic!("invalid value `{backend}` for `--codegen-backend`: {error}"),
277 },
278 None => CodegenBackend::Llvm,
280 };
281
282 let run_ignored = matches.opt_present("ignored");
283 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
284 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
285 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
286 let has_html_tidy = if mode == TestMode::Rustdoc {
287 Command::new("tidy")
288 .arg("--version")
289 .stdout(Stdio::null())
290 .status()
291 .map_or(false, |status| status.success())
292 } else {
293 false
295 };
296 let has_enzyme = matches.opt_present("has-enzyme");
297 let filters = if mode == TestMode::RunMake {
298 matches
299 .free
300 .iter()
301 .map(|f| {
302 let path = Utf8Path::new(f);
303 let mut iter = path.iter().skip(1);
304
305 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
307 path.parent().unwrap().to_string()
308 } else {
309 f.to_string()
310 }
311 })
312 .collect::<Vec<_>>()
313 } else {
314 matches.free.clone()
315 };
316 let compare_mode = matches.opt_str("compare-mode").map(|s| {
317 s.parse().unwrap_or_else(|_| {
318 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
319 panic!(
320 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
321 variants.join(", ")
322 );
323 })
324 });
325 if matches.opt_present("nocapture") {
326 panic!("`--nocapture` is deprecated; please use `--no-capture`");
327 }
328
329 let stage = match matches.opt_str("stage") {
330 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
331 None => panic!("`--stage` is required"),
332 };
333
334 let src_root = opt_path(matches, "src-root");
335 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
336 assert!(
337 src_test_suite_root.starts_with(&src_root),
338 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
339 src_root,
340 src_test_suite_root
341 );
342
343 let build_root = opt_path(matches, "build-root");
344 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
345 assert!(build_test_suite_root.starts_with(&build_root));
346
347 Config {
348 bless: matches.opt_present("bless"),
349 fail_fast: matches.opt_present("fail-fast")
350 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
351
352 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
353 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
354 rustc_path: opt_path(matches, "rustc-path"),
355 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
356 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
357 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
358 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
359 python: matches.opt_str("python").unwrap(),
360 jsondocck_path: matches.opt_str("jsondocck-path"),
361 jsondoclint_path: matches.opt_str("jsondoclint-path"),
362 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
363 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
364 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
365
366 src_root,
367 src_test_suite_root,
368
369 build_root,
370 build_test_suite_root,
371
372 sysroot_base: opt_path(matches, "sysroot-base"),
373
374 stage,
375 stage_id: matches.opt_str("stage-id").unwrap(),
376
377 mode,
378 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
379 debugger: matches.opt_str("debugger").map(|debugger| {
380 debugger
381 .parse::<Debugger>()
382 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
383 }),
384 run_ignored,
385 with_rustc_debug_assertions,
386 with_std_debug_assertions,
387 filters,
388 skip: matches.opt_strs("skip"),
389 filter_exact: matches.opt_present("exact"),
390 force_pass_mode: matches.opt_str("pass").map(|mode| {
391 mode.parse::<PassMode>()
392 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
393 }),
394 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
396 "auto" => None,
397 "always" => Some(true),
398 "never" => Some(false),
399 _ => panic!("unknown `--run` option `{}` given", mode),
400 }),
401 runner: matches.opt_str("runner"),
402 host_rustcflags: matches.opt_strs("host-rustcflags"),
403 target_rustcflags: matches.opt_strs("target-rustcflags"),
404 optimize_tests: matches.opt_present("optimize-tests"),
405 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
406 target,
407 host: opt_str2(matches.opt_str("host")),
408 cdb,
409 cdb_version,
410 gdb,
411 gdb_version,
412 lldb_version,
413 llvm_version,
414 system_llvm: matches.opt_present("system-llvm"),
415 android_cross_path,
416 adb_path: opt_str2(matches.opt_str("adb-path")),
417 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
418 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
419 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
420 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
421 lldb_python_dir: matches.opt_str("lldb-python-dir"),
422 verbose: matches.opt_present("verbose"),
423 format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
424 (true, true) => panic!("--quiet and --json are incompatible"),
425 (true, false) => OutputFormat::Terse,
426 (false, true) => OutputFormat::Json,
427 (false, false) => OutputFormat::Pretty,
428 },
429 only_modified: matches.opt_present("only-modified"),
430 color,
431 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
432 compare_mode,
433 rustfix_coverage: matches.opt_present("rustfix-coverage"),
434 has_html_tidy,
435 has_enzyme,
436 channel: matches.opt_str("channel").unwrap(),
437 git_hash: matches.opt_present("git-hash"),
438 edition: matches.opt_str("edition"),
439
440 cc: matches.opt_str("cc").unwrap(),
441 cxx: matches.opt_str("cxx").unwrap(),
442 cflags: matches.opt_str("cflags").unwrap(),
443 cxxflags: matches.opt_str("cxxflags").unwrap(),
444 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
445 target_linker: matches.opt_str("target-linker"),
446 host_linker: matches.opt_str("host-linker"),
447 llvm_components: matches.opt_str("llvm-components").unwrap(),
448 nodejs: matches.opt_str("nodejs"),
449 npm: matches.opt_str("npm"),
450
451 force_rerun: matches.opt_present("force-rerun"),
452
453 target_cfgs: OnceLock::new(),
454 builtin_cfg_names: OnceLock::new(),
455 supported_crate_types: OnceLock::new(),
456
457 nocapture: matches.opt_present("no-capture"),
458
459 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
460 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
461
462 profiler_runtime: matches.opt_present("profiler-runtime"),
463
464 diff_command: matches.opt_str("compiletest-diff-tool"),
465
466 minicore_path: opt_path(matches, "minicore-path"),
467
468 codegen_backend,
469 }
470}
471
472pub fn log_config(config: &Config) {
473 let c = config;
474 logv(c, "configuration:".to_string());
475 logv(c, format!("compile_lib_path: {}", config.compile_lib_path));
476 logv(c, format!("run_lib_path: {}", config.run_lib_path));
477 logv(c, format!("rustc_path: {}", config.rustc_path));
478 logv(c, format!("cargo_path: {:?}", config.cargo_path));
479 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
480
481 logv(c, format!("src_root: {}", config.src_root));
482 logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root));
483
484 logv(c, format!("build_root: {}", config.build_root));
485 logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root));
486
487 logv(c, format!("sysroot_base: {}", config.sysroot_base));
488
489 logv(c, format!("stage: {}", config.stage));
490 logv(c, format!("stage_id: {}", config.stage_id));
491 logv(c, format!("mode: {}", config.mode));
492 logv(c, format!("run_ignored: {}", config.run_ignored));
493 logv(c, format!("filters: {:?}", config.filters));
494 logv(c, format!("skip: {:?}", config.skip));
495 logv(c, format!("filter_exact: {}", config.filter_exact));
496 logv(
497 c,
498 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
499 );
500 logv(c, format!("runner: {}", opt_str(&config.runner)));
501 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
502 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
503 logv(c, format!("target: {}", config.target));
504 logv(c, format!("host: {}", config.host));
505 logv(c, format!("android-cross-path: {}", config.android_cross_path));
506 logv(c, format!("adb_path: {}", config.adb_path));
507 logv(c, format!("adb_test_dir: {}", config.adb_test_dir));
508 logv(c, format!("adb_device_status: {}", config.adb_device_status));
509 logv(c, format!("ar: {}", config.ar));
510 logv(c, format!("target-linker: {:?}", config.target_linker));
511 logv(c, format!("host-linker: {:?}", config.host_linker));
512 logv(c, format!("verbose: {}", config.verbose));
513 logv(c, format!("format: {:?}", config.format));
514 logv(c, format!("minicore_path: {}", config.minicore_path));
515 logv(c, "\n".to_string());
516}
517
518pub fn opt_str(maybestr: &Option<String>) -> &str {
519 match *maybestr {
520 None => "(none)",
521 Some(ref s) => s,
522 }
523}
524
525pub fn opt_str2(maybestr: Option<String>) -> String {
526 match maybestr {
527 None => "(none)".to_owned(),
528 Some(s) => s,
529 }
530}
531
532pub fn run_tests(config: Arc<Config>) {
534 if config.rustfix_coverage {
538 let mut coverage_file_path = config.build_test_suite_root.clone();
539 coverage_file_path.push("rustfix_missing_coverage.txt");
540 if coverage_file_path.exists() {
541 if let Err(e) = fs::remove_file(&coverage_file_path) {
542 panic!("Could not delete {} due to {}", coverage_file_path, e)
543 }
544 }
545 }
546
547 unsafe {
551 raise_fd_limit::raise_fd_limit();
552 }
553 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
558
559 unsafe { env::set_var("TARGET", &config.target) };
563
564 let mut configs = Vec::new();
565 if let TestMode::DebugInfo = config.mode {
566 if !config.target.contains("emscripten") {
568 match config.debugger {
569 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
570 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
571 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
572 None => {
577 configs.extend(debuggers::configure_cdb(&config));
578 configs.extend(debuggers::configure_gdb(&config));
579 configs.extend(debuggers::configure_lldb(&config));
580 }
581 }
582 }
583 } else {
584 configs.push(config.clone());
585 };
586
587 let mut tests = Vec::new();
590 for c in configs {
591 tests.extend(collect_and_make_tests(c));
592 }
593
594 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
595
596 let res: io::Result<bool> = Ok(executor::run_tests(&config, tests));
603
604 match res {
606 Ok(true) => {}
607 Ok(false) => {
608 let mut msg = String::from("Some tests failed in compiletest");
616 write!(msg, " suite={}", config.suite).unwrap();
617
618 if let Some(compare_mode) = config.compare_mode.as_ref() {
619 write!(msg, " compare_mode={}", compare_mode).unwrap();
620 }
621
622 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
623 write!(msg, " pass_mode={}", pass_mode).unwrap();
624 }
625
626 write!(msg, " mode={}", config.mode).unwrap();
627 write!(msg, " host={}", config.host).unwrap();
628 write!(msg, " target={}", config.target).unwrap();
629
630 println!("{msg}");
631
632 std::process::exit(1);
633 }
634 Err(e) => {
635 panic!("I/O failure during tests: {:?}", e);
642 }
643 }
644}
645
646struct TestCollectorCx {
648 config: Arc<Config>,
649 cache: DirectivesCache,
650 common_inputs_stamp: Stamp,
651 modified_tests: Vec<Utf8PathBuf>,
652}
653
654struct TestCollector {
656 tests: Vec<CollectedTest>,
657 found_path_stems: HashSet<Utf8PathBuf>,
658 poisoned: bool,
659}
660
661impl TestCollector {
662 fn new() -> Self {
663 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
664 }
665
666 fn merge(&mut self, mut other: Self) {
667 self.tests.append(&mut other.tests);
668 self.found_path_stems.extend(other.found_path_stems);
669 self.poisoned |= other.poisoned;
670 }
671}
672
673pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
679 debug!("making tests from {}", config.src_test_suite_root);
680 let common_inputs_stamp = common_inputs_stamp(&config);
681 let modified_tests =
682 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
683 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
684 });
685 let cache = DirectivesCache::load(&config);
686
687 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
688 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
689 .unwrap_or_else(|reason| {
690 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
691 });
692
693 let TestCollector { tests, found_path_stems, poisoned } = collector;
694
695 if poisoned {
696 eprintln!();
697 panic!("there are errors in tests");
698 }
699
700 check_for_overlapping_test_paths(&found_path_stems);
701
702 tests
703}
704
705fn common_inputs_stamp(config: &Config) -> Stamp {
713 let src_root = &config.src_root;
714
715 let mut stamp = Stamp::from_path(&config.rustc_path);
716
717 let pretty_printer_files = [
719 "src/etc/rust_types.py",
720 "src/etc/gdb_load_rust_pretty_printers.py",
721 "src/etc/gdb_lookup.py",
722 "src/etc/gdb_providers.py",
723 "src/etc/lldb_batchmode.py",
724 "src/etc/lldb_lookup.py",
725 "src/etc/lldb_providers.py",
726 ];
727 for file in &pretty_printer_files {
728 let path = src_root.join(file);
729 stamp.add_path(&path);
730 }
731
732 stamp.add_dir(&src_root.join("src/etc/natvis"));
733
734 stamp.add_dir(&config.run_lib_path);
735
736 if let Some(ref rustdoc_path) = config.rustdoc_path {
737 stamp.add_path(&rustdoc_path);
738 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
739 }
740
741 if let Some(coverage_dump_path) = &config.coverage_dump_path {
744 stamp.add_path(coverage_dump_path)
745 }
746
747 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
748
749 stamp.add_dir(&src_root.join("src/tools/compiletest"));
751
752 stamp
753}
754
755fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
760 if !config.only_modified {
763 return Ok(vec![]);
764 }
765
766 let files = get_git_modified_files(
767 &config.git_config(),
768 Some(dir.as_std_path()),
769 &vec!["rs", "stderr", "fixed"],
770 )?;
771 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
773
774 let all_paths = [&files[..], &untracked_files[..]].concat();
775 let full_paths = {
776 let mut full_paths: Vec<Utf8PathBuf> = all_paths
777 .into_iter()
778 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
779 .filter_map(
780 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
781 )
782 .collect();
783 full_paths.dedup();
784 full_paths.sort_unstable();
785 full_paths
786 };
787 Ok(full_paths)
788}
789
790fn collect_tests_from_dir(
793 cx: &TestCollectorCx,
794 dir: &Utf8Path,
795 relative_dir_path: &Utf8Path,
796) -> io::Result<TestCollector> {
797 if dir.join("compiletest-ignore-dir").exists() {
799 return Ok(TestCollector::new());
800 }
801
802 let mut components = dir.components().rev();
803 if let Some(Utf8Component::Normal(last)) = components.next()
804 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
805 && let Some(Utf8Component::Normal(parent)) = components.next()
806 && parent == "tests"
807 && let Ok(backend) = CodegenBackend::try_from(backend)
808 && backend != cx.config.codegen_backend
809 {
810 warning!(
812 "Ignoring tests in `{dir}` because they don't match the configured codegen \
813 backend (`{}`)",
814 cx.config.codegen_backend.as_str(),
815 );
816 return Ok(TestCollector::new());
817 }
818
819 if cx.config.mode == TestMode::RunMake {
821 let mut collector = TestCollector::new();
822 if dir.join("rmake.rs").exists() {
823 let paths = TestPaths {
824 file: dir.to_path_buf(),
825 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
826 };
827 make_test(cx, &mut collector, &paths);
828 return Ok(collector);
830 }
831 }
832
833 let build_dir = output_relative_path(&cx.config, relative_dir_path);
840 fs::create_dir_all(&build_dir).unwrap();
841
842 fs::read_dir(dir.as_std_path())?
847 .par_bridge()
848 .map(|file| {
849 let mut collector = TestCollector::new();
850 let file = file?;
851 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
852 let file_name = file_path.file_name().unwrap();
853
854 if is_test(file_name)
855 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
856 {
857 debug!(%file_path, "found test file");
859
860 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
862 collector.found_path_stems.insert(rel_test_path);
863
864 let paths =
865 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
866 make_test(cx, &mut collector, &paths);
867 } else if file_path.is_dir() {
868 let relative_file_path = relative_dir_path.join(file_name);
870 if file_name != "auxiliary" {
871 debug!(%file_path, "found directory");
872 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
873 }
874 } else {
875 debug!(%file_path, "found other file/directory");
876 }
877 Ok(collector)
878 })
879 .reduce(
880 || Ok(TestCollector::new()),
881 |a, b| {
882 let mut a = a?;
883 a.merge(b?);
884 Ok(a)
885 },
886 )
887}
888
889pub fn is_test(file_name: &str) -> bool {
891 if !file_name.ends_with(".rs") {
892 return false;
893 }
894
895 let invalid_prefixes = &[".", "#", "~"];
897 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
898}
899
900fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
903 let test_path = if cx.config.mode == TestMode::RunMake {
907 testpaths.file.join("rmake.rs")
908 } else {
909 testpaths.file.clone()
910 };
911
912 let early_props = EarlyProps::from_file(&cx.config, &test_path);
914
915 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
922 vec![None]
923 } else {
924 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
925 };
926
927 collector.tests.extend(revisions.into_iter().map(|revision| {
930 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
932 let test_name = make_test_name(&cx.config, testpaths, revision);
933 let mut desc = make_test_description(
937 &cx.config,
938 &cx.cache,
939 test_name,
940 &test_path,
941 src_file,
942 revision,
943 &mut collector.poisoned,
944 );
945
946 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
949 desc.ignore = true;
950 desc.ignore_message = Some("up-to-date".into());
952 }
953
954 let config = Arc::clone(&cx.config);
955 let testpaths = testpaths.clone();
956 let revision = revision.map(str::to_owned);
957
958 CollectedTest { desc, config, testpaths, revision }
959 }));
960}
961
962fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
965 output_base_dir(config, testpaths, revision).join("stamp")
966}
967
968fn files_related_to_test(
973 config: &Config,
974 testpaths: &TestPaths,
975 props: &EarlyProps,
976 revision: Option<&str>,
977) -> Vec<Utf8PathBuf> {
978 let mut related = vec![];
979
980 if testpaths.file.is_dir() {
981 for entry in WalkDir::new(&testpaths.file) {
983 let path = entry.unwrap().into_path();
984 if path.is_file() {
985 related.push(Utf8PathBuf::try_from(path).unwrap());
986 }
987 }
988 } else {
989 related.push(testpaths.file.clone());
990 }
991
992 for aux in props.aux.all_aux_path_strings() {
993 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
995 related.push(path);
996 }
997
998 for extension in UI_EXTENSIONS {
1000 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1001 related.push(path);
1002 }
1003
1004 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1006
1007 related
1008}
1009
1010fn is_up_to_date(
1016 cx: &TestCollectorCx,
1017 testpaths: &TestPaths,
1018 props: &EarlyProps,
1019 revision: Option<&str>,
1020) -> bool {
1021 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1022 let contents = match fs::read_to_string(&stamp_file_path) {
1024 Ok(f) => f,
1025 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1026 Err(_) => return false,
1028 };
1029 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1030 if contents != expected_hash {
1031 return false;
1034 }
1035
1036 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1039 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1040 inputs_stamp.add_path(&path);
1041 }
1042
1043 inputs_stamp < Stamp::from_path(&stamp_file_path)
1046}
1047
1048#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1050struct Stamp {
1051 time: SystemTime,
1052}
1053
1054impl Stamp {
1055 fn from_path(path: &Utf8Path) -> Self {
1057 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1058 stamp.add_path(path);
1059 stamp
1060 }
1061
1062 fn add_path(&mut self, path: &Utf8Path) {
1065 let modified = fs::metadata(path.as_std_path())
1066 .and_then(|metadata| metadata.modified())
1067 .unwrap_or(SystemTime::UNIX_EPOCH);
1068 self.time = self.time.max(modified);
1069 }
1070
1071 fn add_dir(&mut self, path: &Utf8Path) {
1075 let path = path.as_std_path();
1076 for entry in WalkDir::new(path) {
1077 let entry = entry.unwrap();
1078 if entry.file_type().is_file() {
1079 let modified = entry
1080 .metadata()
1081 .ok()
1082 .and_then(|metadata| metadata.modified().ok())
1083 .unwrap_or(SystemTime::UNIX_EPOCH);
1084 self.time = self.time.max(modified);
1085 }
1086 }
1087 }
1088}
1089
1090fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1092 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1094 let debugger = match config.debugger {
1095 Some(d) => format!("-{}", d),
1096 None => String::new(),
1097 };
1098 let mode_suffix = match config.compare_mode {
1099 Some(ref mode) => format!(" ({})", mode.to_str()),
1100 None => String::new(),
1101 };
1102
1103 format!(
1104 "[{}{}{}] {}{}",
1105 config.mode,
1106 debugger,
1107 mode_suffix,
1108 path,
1109 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1110 )
1111}
1112
1113fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1131 let mut collisions = Vec::new();
1132 for path in found_path_stems {
1133 for ancestor in path.ancestors().skip(1) {
1134 if found_path_stems.contains(ancestor) {
1135 collisions.push((path, ancestor));
1136 }
1137 }
1138 }
1139 if !collisions.is_empty() {
1140 collisions.sort();
1141 let collisions: String = collisions
1142 .into_iter()
1143 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1144 .collect();
1145 panic!(
1146 "{collisions}\n\
1147 Tests cannot have overlapping names. Make sure they use unique prefixes."
1148 );
1149 }
1150}
1151
1152pub fn early_config_check(config: &Config) {
1153 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1154 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1155 }
1156
1157 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1158 let actioned = if config.bless { "blessed" } else { "checked" };
1159 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1160 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1161 }
1162
1163 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1165 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1166 }
1167}