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 "default-codegen-backend",
216 "the codegen backend currently used",
217 "CODEGEN BACKEND NAME",
218 )
219 .optopt(
220 "",
221 "override-codegen-backend",
222 "the codegen backend to use instead of the default one",
223 "CODEGEN BACKEND [NAME | PATH]",
224 );
225
226 let (argv0, args_) = args.split_first().unwrap();
227 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
228 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
229 println!("{}", opts.usage(&message));
230 println!();
231 panic!()
232 }
233
234 let matches = &match opts.parse(args_) {
235 Ok(m) => m,
236 Err(f) => panic!("{:?}", f),
237 };
238
239 if matches.opt_present("h") || matches.opt_present("help") {
240 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
241 println!("{}", opts.usage(&message));
242 println!();
243 panic!()
244 }
245
246 fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
247 if path.is_relative() {
248 Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
249 } else {
250 path
251 }
252 }
253
254 fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
255 match m.opt_str(nm) {
256 Some(s) => Utf8PathBuf::from(&s),
257 None => panic!("no option (=path) found for {}", nm),
258 }
259 }
260
261 let target = opt_str2(matches.opt_str("target"));
262 let android_cross_path = opt_path(matches, "android-cross-path");
263 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
265 let (gdb, gdb_version) =
267 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
268 let lldb_version =
270 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
271 let color = match matches.opt_str("color").as_deref() {
272 Some("auto") | None => ColorConfig::AutoColor,
273 Some("always") => ColorConfig::AlwaysColor,
274 Some("never") => ColorConfig::NeverColor,
275 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
276 };
277 let llvm_version =
281 matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
282 || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
283 );
284
285 let default_codegen_backend = match matches.opt_str("default-codegen-backend").as_deref() {
286 Some(backend) => match CodegenBackend::try_from(backend) {
287 Ok(backend) => backend,
288 Err(error) => {
289 panic!("invalid value `{backend}` for `--defalt-codegen-backend`: {error}")
290 }
291 },
292 None => CodegenBackend::Llvm,
294 };
295 let override_codegen_backend = matches.opt_str("override-codegen-backend");
296
297 let run_ignored = matches.opt_present("ignored");
298 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
299 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
300 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
301 let has_html_tidy = if mode == TestMode::Rustdoc {
302 Command::new("tidy")
303 .arg("--version")
304 .stdout(Stdio::null())
305 .status()
306 .map_or(false, |status| status.success())
307 } else {
308 false
310 };
311 let has_enzyme = matches.opt_present("has-enzyme");
312 let filters = if mode == TestMode::RunMake {
313 matches
314 .free
315 .iter()
316 .map(|f| {
317 let path = Utf8Path::new(f);
318 let mut iter = path.iter().skip(1);
319
320 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
322 path.parent().unwrap().to_string()
323 } else {
324 f.to_string()
325 }
326 })
327 .collect::<Vec<_>>()
328 } else {
329 matches.free.clone()
330 };
331 let compare_mode = matches.opt_str("compare-mode").map(|s| {
332 s.parse().unwrap_or_else(|_| {
333 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
334 panic!(
335 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
336 variants.join(", ")
337 );
338 })
339 });
340 if matches.opt_present("nocapture") {
341 panic!("`--nocapture` is deprecated; please use `--no-capture`");
342 }
343
344 let stage = match matches.opt_str("stage") {
345 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
346 None => panic!("`--stage` is required"),
347 };
348
349 let src_root = opt_path(matches, "src-root");
350 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
351 assert!(
352 src_test_suite_root.starts_with(&src_root),
353 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
354 src_root,
355 src_test_suite_root
356 );
357
358 let build_root = opt_path(matches, "build-root");
359 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
360 assert!(build_test_suite_root.starts_with(&build_root));
361
362 Config {
363 bless: matches.opt_present("bless"),
364 fail_fast: matches.opt_present("fail-fast")
365 || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
366
367 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
368 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
369 rustc_path: opt_path(matches, "rustc-path"),
370 cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
371 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
372 query_rustc_path: matches.opt_str("query-rustc-path").map(Utf8PathBuf::from),
373 rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
374 coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
375 python: matches.opt_str("python").unwrap(),
376 jsondocck_path: matches.opt_str("jsondocck-path"),
377 jsondoclint_path: matches.opt_str("jsondoclint-path"),
378 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
379 llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
380 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
381
382 src_root,
383 src_test_suite_root,
384
385 build_root,
386 build_test_suite_root,
387
388 sysroot_base: opt_path(matches, "sysroot-base"),
389
390 stage,
391 stage_id: matches.opt_str("stage-id").unwrap(),
392
393 mode,
394 suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
395 debugger: matches.opt_str("debugger").map(|debugger| {
396 debugger
397 .parse::<Debugger>()
398 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
399 }),
400 run_ignored,
401 with_rustc_debug_assertions,
402 with_std_debug_assertions,
403 filters,
404 skip: matches.opt_strs("skip"),
405 filter_exact: matches.opt_present("exact"),
406 force_pass_mode: matches.opt_str("pass").map(|mode| {
407 mode.parse::<PassMode>()
408 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
409 }),
410 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
412 "auto" => None,
413 "always" => Some(true),
414 "never" => Some(false),
415 _ => panic!("unknown `--run` option `{}` given", mode),
416 }),
417 runner: matches.opt_str("runner"),
418 host_rustcflags: matches.opt_strs("host-rustcflags"),
419 target_rustcflags: matches.opt_strs("target-rustcflags"),
420 optimize_tests: matches.opt_present("optimize-tests"),
421 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
422 target,
423 host: opt_str2(matches.opt_str("host")),
424 cdb,
425 cdb_version,
426 gdb,
427 gdb_version,
428 lldb_version,
429 llvm_version,
430 system_llvm: matches.opt_present("system-llvm"),
431 android_cross_path,
432 adb_path: opt_str2(matches.opt_str("adb-path")),
433 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
434 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
435 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
436 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
437 lldb_python_dir: matches.opt_str("lldb-python-dir"),
438 verbose: matches.opt_present("verbose"),
439 format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
440 (true, true) => panic!("--quiet and --json are incompatible"),
441 (true, false) => OutputFormat::Terse,
442 (false, true) => OutputFormat::Json,
443 (false, false) => OutputFormat::Pretty,
444 },
445 only_modified: matches.opt_present("only-modified"),
446 color,
447 remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
448 compare_mode,
449 rustfix_coverage: matches.opt_present("rustfix-coverage"),
450 has_html_tidy,
451 has_enzyme,
452 channel: matches.opt_str("channel").unwrap(),
453 git_hash: matches.opt_present("git-hash"),
454 edition: matches.opt_str("edition"),
455
456 cc: matches.opt_str("cc").unwrap(),
457 cxx: matches.opt_str("cxx").unwrap(),
458 cflags: matches.opt_str("cflags").unwrap(),
459 cxxflags: matches.opt_str("cxxflags").unwrap(),
460 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
461 target_linker: matches.opt_str("target-linker"),
462 host_linker: matches.opt_str("host-linker"),
463 llvm_components: matches.opt_str("llvm-components").unwrap(),
464 nodejs: matches.opt_str("nodejs"),
465 npm: matches.opt_str("npm"),
466
467 force_rerun: matches.opt_present("force-rerun"),
468
469 target_cfgs: OnceLock::new(),
470 builtin_cfg_names: OnceLock::new(),
471 supported_crate_types: OnceLock::new(),
472
473 nocapture: matches.opt_present("no-capture"),
474
475 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
476 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
477
478 profiler_runtime: matches.opt_present("profiler-runtime"),
479
480 diff_command: matches.opt_str("compiletest-diff-tool"),
481
482 minicore_path: opt_path(matches, "minicore-path"),
483
484 default_codegen_backend,
485 override_codegen_backend,
486 }
487}
488
489pub fn log_config(config: &Config) {
490 let c = config;
491 logv(c, "configuration:".to_string());
492 logv(c, format!("compile_lib_path: {}", config.compile_lib_path));
493 logv(c, format!("run_lib_path: {}", config.run_lib_path));
494 logv(c, format!("rustc_path: {}", config.rustc_path));
495 logv(c, format!("cargo_path: {:?}", config.cargo_path));
496 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
497
498 logv(c, format!("src_root: {}", config.src_root));
499 logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root));
500
501 logv(c, format!("build_root: {}", config.build_root));
502 logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root));
503
504 logv(c, format!("sysroot_base: {}", config.sysroot_base));
505
506 logv(c, format!("stage: {}", config.stage));
507 logv(c, format!("stage_id: {}", config.stage_id));
508 logv(c, format!("mode: {}", config.mode));
509 logv(c, format!("run_ignored: {}", config.run_ignored));
510 logv(c, format!("filters: {:?}", config.filters));
511 logv(c, format!("skip: {:?}", config.skip));
512 logv(c, format!("filter_exact: {}", config.filter_exact));
513 logv(
514 c,
515 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
516 );
517 logv(c, format!("runner: {}", opt_str(&config.runner)));
518 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
519 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
520 logv(c, format!("target: {}", config.target));
521 logv(c, format!("host: {}", config.host));
522 logv(c, format!("android-cross-path: {}", config.android_cross_path));
523 logv(c, format!("adb_path: {}", config.adb_path));
524 logv(c, format!("adb_test_dir: {}", config.adb_test_dir));
525 logv(c, format!("adb_device_status: {}", config.adb_device_status));
526 logv(c, format!("ar: {}", config.ar));
527 logv(c, format!("target-linker: {:?}", config.target_linker));
528 logv(c, format!("host-linker: {:?}", config.host_linker));
529 logv(c, format!("verbose: {}", config.verbose));
530 logv(c, format!("format: {:?}", config.format));
531 logv(c, format!("minicore_path: {}", config.minicore_path));
532 logv(c, "\n".to_string());
533}
534
535pub fn opt_str(maybestr: &Option<String>) -> &str {
536 match *maybestr {
537 None => "(none)",
538 Some(ref s) => s,
539 }
540}
541
542pub fn opt_str2(maybestr: Option<String>) -> String {
543 match maybestr {
544 None => "(none)".to_owned(),
545 Some(s) => s,
546 }
547}
548
549pub fn run_tests(config: Arc<Config>) {
551 if config.rustfix_coverage {
555 let mut coverage_file_path = config.build_test_suite_root.clone();
556 coverage_file_path.push("rustfix_missing_coverage.txt");
557 if coverage_file_path.exists() {
558 if let Err(e) = fs::remove_file(&coverage_file_path) {
559 panic!("Could not delete {} due to {}", coverage_file_path, e)
560 }
561 }
562 }
563
564 unsafe {
568 raise_fd_limit::raise_fd_limit();
569 }
570 unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
575
576 unsafe { env::set_var("TARGET", &config.target) };
580
581 let mut configs = Vec::new();
582 if let TestMode::DebugInfo = config.mode {
583 if !config.target.contains("emscripten") {
585 match config.debugger {
586 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
587 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
588 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
589 None => {
594 configs.extend(debuggers::configure_cdb(&config));
595 configs.extend(debuggers::configure_gdb(&config));
596 configs.extend(debuggers::configure_lldb(&config));
597 }
598 }
599 }
600 } else {
601 configs.push(config.clone());
602 };
603
604 let mut tests = Vec::new();
607 for c in configs {
608 tests.extend(collect_and_make_tests(c));
609 }
610
611 tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
612
613 let res: io::Result<bool> = Ok(executor::run_tests(&config, tests));
620
621 match res {
623 Ok(true) => {}
624 Ok(false) => {
625 let mut msg = String::from("Some tests failed in compiletest");
633 write!(msg, " suite={}", config.suite).unwrap();
634
635 if let Some(compare_mode) = config.compare_mode.as_ref() {
636 write!(msg, " compare_mode={}", compare_mode).unwrap();
637 }
638
639 if let Some(pass_mode) = config.force_pass_mode.as_ref() {
640 write!(msg, " pass_mode={}", pass_mode).unwrap();
641 }
642
643 write!(msg, " mode={}", config.mode).unwrap();
644 write!(msg, " host={}", config.host).unwrap();
645 write!(msg, " target={}", config.target).unwrap();
646
647 println!("{msg}");
648
649 std::process::exit(1);
650 }
651 Err(e) => {
652 panic!("I/O failure during tests: {:?}", e);
659 }
660 }
661}
662
663struct TestCollectorCx {
665 config: Arc<Config>,
666 cache: DirectivesCache,
667 common_inputs_stamp: Stamp,
668 modified_tests: Vec<Utf8PathBuf>,
669}
670
671struct TestCollector {
673 tests: Vec<CollectedTest>,
674 found_path_stems: HashSet<Utf8PathBuf>,
675 poisoned: bool,
676}
677
678impl TestCollector {
679 fn new() -> Self {
680 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
681 }
682
683 fn merge(&mut self, mut other: Self) {
684 self.tests.append(&mut other.tests);
685 self.found_path_stems.extend(other.found_path_stems);
686 self.poisoned |= other.poisoned;
687 }
688}
689
690pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
696 debug!("making tests from {}", config.src_test_suite_root);
697 let common_inputs_stamp = common_inputs_stamp(&config);
698 let modified_tests =
699 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
700 fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
701 });
702 let cache = DirectivesCache::load(&config);
703
704 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
705 let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
706 .unwrap_or_else(|reason| {
707 panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
708 });
709
710 let TestCollector { tests, found_path_stems, poisoned } = collector;
711
712 if poisoned {
713 eprintln!();
714 panic!("there are errors in tests");
715 }
716
717 check_for_overlapping_test_paths(&found_path_stems);
718
719 tests
720}
721
722fn common_inputs_stamp(config: &Config) -> Stamp {
730 let src_root = &config.src_root;
731
732 let mut stamp = Stamp::from_path(&config.rustc_path);
733
734 let pretty_printer_files = [
736 "src/etc/rust_types.py",
737 "src/etc/gdb_load_rust_pretty_printers.py",
738 "src/etc/gdb_lookup.py",
739 "src/etc/gdb_providers.py",
740 "src/etc/lldb_batchmode.py",
741 "src/etc/lldb_lookup.py",
742 "src/etc/lldb_providers.py",
743 ];
744 for file in &pretty_printer_files {
745 let path = src_root.join(file);
746 stamp.add_path(&path);
747 }
748
749 stamp.add_dir(&src_root.join("src/etc/natvis"));
750
751 stamp.add_dir(&config.run_lib_path);
752
753 if let Some(ref rustdoc_path) = config.rustdoc_path {
754 stamp.add_path(&rustdoc_path);
755 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
756 }
757
758 if let Some(coverage_dump_path) = &config.coverage_dump_path {
761 stamp.add_path(coverage_dump_path)
762 }
763
764 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
765
766 stamp.add_dir(&src_root.join("src/tools/compiletest"));
768
769 stamp
770}
771
772fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
777 if !config.only_modified {
780 return Ok(vec![]);
781 }
782
783 let files = get_git_modified_files(
784 &config.git_config(),
785 Some(dir.as_std_path()),
786 &vec!["rs", "stderr", "fixed"],
787 )?;
788 let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
790
791 let all_paths = [&files[..], &untracked_files[..]].concat();
792 let full_paths = {
793 let mut full_paths: Vec<Utf8PathBuf> = all_paths
794 .into_iter()
795 .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
796 .filter_map(
797 |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
798 )
799 .collect();
800 full_paths.dedup();
801 full_paths.sort_unstable();
802 full_paths
803 };
804 Ok(full_paths)
805}
806
807fn collect_tests_from_dir(
810 cx: &TestCollectorCx,
811 dir: &Utf8Path,
812 relative_dir_path: &Utf8Path,
813) -> io::Result<TestCollector> {
814 if dir.join("compiletest-ignore-dir").exists() {
816 return Ok(TestCollector::new());
817 }
818
819 let mut components = dir.components().rev();
820 if let Some(Utf8Component::Normal(last)) = components.next()
821 && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
822 && let Some(Utf8Component::Normal(parent)) = components.next()
823 && parent == "tests"
824 && let Ok(backend) = CodegenBackend::try_from(backend)
825 && backend != cx.config.default_codegen_backend
826 {
827 warning!(
829 "Ignoring tests in `{dir}` because they don't match the configured codegen \
830 backend (`{}`)",
831 cx.config.default_codegen_backend.as_str(),
832 );
833 return Ok(TestCollector::new());
834 }
835
836 if cx.config.mode == TestMode::RunMake {
838 let mut collector = TestCollector::new();
839 if dir.join("rmake.rs").exists() {
840 let paths = TestPaths {
841 file: dir.to_path_buf(),
842 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
843 };
844 make_test(cx, &mut collector, &paths);
845 return Ok(collector);
847 }
848 }
849
850 let build_dir = output_relative_path(&cx.config, relative_dir_path);
857 fs::create_dir_all(&build_dir).unwrap();
858
859 fs::read_dir(dir.as_std_path())?
864 .par_bridge()
865 .map(|file| {
866 let mut collector = TestCollector::new();
867 let file = file?;
868 let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
869 let file_name = file_path.file_name().unwrap();
870
871 if is_test(file_name)
872 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
873 {
874 debug!(%file_path, "found test file");
876
877 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
879 collector.found_path_stems.insert(rel_test_path);
880
881 let paths =
882 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
883 make_test(cx, &mut collector, &paths);
884 } else if file_path.is_dir() {
885 let relative_file_path = relative_dir_path.join(file_name);
887 if file_name != "auxiliary" {
888 debug!(%file_path, "found directory");
889 collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
890 }
891 } else {
892 debug!(%file_path, "found other file/directory");
893 }
894 Ok(collector)
895 })
896 .reduce(
897 || Ok(TestCollector::new()),
898 |a, b| {
899 let mut a = a?;
900 a.merge(b?);
901 Ok(a)
902 },
903 )
904}
905
906pub fn is_test(file_name: &str) -> bool {
908 if !file_name.ends_with(".rs") {
909 return false;
910 }
911
912 let invalid_prefixes = &[".", "#", "~"];
914 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
915}
916
917fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
920 let test_path = if cx.config.mode == TestMode::RunMake {
924 testpaths.file.join("rmake.rs")
925 } else {
926 testpaths.file.clone()
927 };
928
929 let early_props = EarlyProps::from_file(&cx.config, &test_path);
931
932 let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
939 vec![None]
940 } else {
941 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
942 };
943
944 collector.tests.extend(revisions.into_iter().map(|revision| {
947 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
949 let test_name = make_test_name(&cx.config, testpaths, revision);
950 let mut desc = make_test_description(
954 &cx.config,
955 &cx.cache,
956 test_name,
957 &test_path,
958 src_file,
959 revision,
960 &mut collector.poisoned,
961 );
962
963 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
966 desc.ignore = true;
967 desc.ignore_message = Some("up-to-date".into());
969 }
970
971 let config = Arc::clone(&cx.config);
972 let testpaths = testpaths.clone();
973 let revision = revision.map(str::to_owned);
974
975 CollectedTest { desc, config, testpaths, revision }
976 }));
977}
978
979fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
982 output_base_dir(config, testpaths, revision).join("stamp")
983}
984
985fn files_related_to_test(
990 config: &Config,
991 testpaths: &TestPaths,
992 props: &EarlyProps,
993 revision: Option<&str>,
994) -> Vec<Utf8PathBuf> {
995 let mut related = vec![];
996
997 if testpaths.file.is_dir() {
998 for entry in WalkDir::new(&testpaths.file) {
1000 let path = entry.unwrap().into_path();
1001 if path.is_file() {
1002 related.push(Utf8PathBuf::try_from(path).unwrap());
1003 }
1004 }
1005 } else {
1006 related.push(testpaths.file.clone());
1007 }
1008
1009 for aux in props.aux.all_aux_path_strings() {
1010 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
1012 related.push(path);
1013 }
1014
1015 for extension in UI_EXTENSIONS {
1017 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1018 related.push(path);
1019 }
1020
1021 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1023
1024 related
1025}
1026
1027fn is_up_to_date(
1033 cx: &TestCollectorCx,
1034 testpaths: &TestPaths,
1035 props: &EarlyProps,
1036 revision: Option<&str>,
1037) -> bool {
1038 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1039 let contents = match fs::read_to_string(&stamp_file_path) {
1041 Ok(f) => f,
1042 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1043 Err(_) => return false,
1045 };
1046 let expected_hash = runtest::compute_stamp_hash(&cx.config);
1047 if contents != expected_hash {
1048 return false;
1051 }
1052
1053 let mut inputs_stamp = cx.common_inputs_stamp.clone();
1056 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1057 inputs_stamp.add_path(&path);
1058 }
1059
1060 inputs_stamp < Stamp::from_path(&stamp_file_path)
1063}
1064
1065#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1067struct Stamp {
1068 time: SystemTime,
1069}
1070
1071impl Stamp {
1072 fn from_path(path: &Utf8Path) -> Self {
1074 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1075 stamp.add_path(path);
1076 stamp
1077 }
1078
1079 fn add_path(&mut self, path: &Utf8Path) {
1082 let modified = fs::metadata(path.as_std_path())
1083 .and_then(|metadata| metadata.modified())
1084 .unwrap_or(SystemTime::UNIX_EPOCH);
1085 self.time = self.time.max(modified);
1086 }
1087
1088 fn add_dir(&mut self, path: &Utf8Path) {
1092 let path = path.as_std_path();
1093 for entry in WalkDir::new(path) {
1094 let entry = entry.unwrap();
1095 if entry.file_type().is_file() {
1096 let modified = entry
1097 .metadata()
1098 .ok()
1099 .and_then(|metadata| metadata.modified().ok())
1100 .unwrap_or(SystemTime::UNIX_EPOCH);
1101 self.time = self.time.max(modified);
1102 }
1103 }
1104 }
1105}
1106
1107fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1109 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1111 let debugger = match config.debugger {
1112 Some(d) => format!("-{}", d),
1113 None => String::new(),
1114 };
1115 let mode_suffix = match config.compare_mode {
1116 Some(ref mode) => format!(" ({})", mode.to_str()),
1117 None => String::new(),
1118 };
1119
1120 format!(
1121 "[{}{}{}] {}{}",
1122 config.mode,
1123 debugger,
1124 mode_suffix,
1125 path,
1126 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1127 )
1128}
1129
1130fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1148 let mut collisions = Vec::new();
1149 for path in found_path_stems {
1150 for ancestor in path.ancestors().skip(1) {
1151 if found_path_stems.contains(ancestor) {
1152 collisions.push((path, ancestor));
1153 }
1154 }
1155 }
1156 if !collisions.is_empty() {
1157 collisions.sort();
1158 let collisions: String = collisions
1159 .into_iter()
1160 .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1161 .collect();
1162 panic!(
1163 "{collisions}\n\
1164 Tests cannot have overlapping names. Make sure they use unique prefixes."
1165 );
1166 }
1167}
1168
1169pub fn early_config_check(config: &Config) {
1170 if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1171 warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1172 }
1173
1174 if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1175 let actioned = if config.bless { "blessed" } else { "checked" };
1176 warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1177 help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1178 }
1179
1180 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1182 warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1183 }
1184}