1#![crate_name = "compiletest"]
2#![feature(test)]
5
6extern crate test;
7
8#[cfg(test)]
9mod tests;
10
11pub mod common;
12pub mod compute_diff;
13mod debuggers;
14pub mod errors;
15pub mod header;
16mod json;
17mod raise_fd_limit;
18mod read2;
19pub mod runtest;
20pub mod util;
21
22use core::panic;
23use std::collections::HashSet;
24use std::ffi::OsString;
25use std::io::{self, ErrorKind};
26use std::path::{Path, PathBuf};
27use std::process::{Command, Stdio};
28use std::sync::{Arc, OnceLock};
29use std::time::SystemTime;
30use std::{env, fs, vec};
31
32use build_helper::git::{get_git_modified_files, get_git_untracked_files};
33use getopts::Options;
34use test::ColorConfig;
35use tracing::*;
36use walkdir::WalkDir;
37
38use self::header::{EarlyProps, make_test_description};
39use crate::common::{
40 CompareMode, Config, Debugger, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path,
41 output_base_dir, output_relative_path,
42};
43use crate::header::HeadersCache;
44use crate::util::logv;
45
46pub fn parse_config(args: Vec<String>) -> Config {
52 let mut opts = Options::new();
53 opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
54 .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
55 .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
56 .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
57 .optopt(
58 "",
59 "stage0-rustc-path",
60 "path to rustc to use for compiling run-make recipes",
61 "PATH",
62 )
63 .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
64 .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
65 .reqopt("", "python", "path to python to use for doc tests", "PATH")
66 .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
67 .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
68 .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
69 .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
70 .reqopt("", "src-root", "directory containing sources", "PATH")
71 .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
72 .reqopt("", "build-root", "path to root build directory", "PATH")
73 .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
74 .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
75 .reqopt("", "stage", "stage number under test", "N")
76 .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
77 .reqopt(
78 "",
79 "mode",
80 "which sort of compile tests to run",
81 "pretty | debug-info | codegen | rustdoc \
82 | rustdoc-json | codegen-units | incremental | run-make | ui \
83 | rustdoc-js | mir-opt | assembly | crashes",
84 )
85 .reqopt(
86 "",
87 "suite",
88 "which suite of compile tests to run. used for nicer error reporting.",
89 "SUITE",
90 )
91 .optopt(
92 "",
93 "pass",
94 "force {check,build,run}-pass tests to this mode.",
95 "check | build | run",
96 )
97 .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
98 .optflag("", "ignored", "run tests marked as ignored")
99 .optflag("", "has-enzyme", "run tests that require enzyme")
100 .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
101 .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
102 .optmulti(
103 "",
104 "skip",
105 "skip tests matching SUBSTRING. Can be passed multiple times",
106 "SUBSTRING",
107 )
108 .optflag("", "exact", "filters match exactly")
109 .optopt(
110 "",
111 "runner",
112 "supervisor program to run tests under \
113 (eg. emulator, valgrind)",
114 "PROGRAM",
115 )
116 .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
117 .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
118 .optflag(
119 "",
120 "rust-randomized-layout",
121 "set this when rustc/stdlib were compiled with randomized layouts",
122 )
123 .optflag("", "optimize-tests", "run tests with optimizations enabled")
124 .optflag("", "verbose", "run tests verbosely, showing all output")
125 .optflag(
126 "",
127 "bless",
128 "overwrite stderr/stdout files instead of complaining about a mismatch",
129 )
130 .optflag("", "quiet", "print one character per test instead of one line")
131 .optopt("", "color", "coloring: auto, always, never", "WHEN")
132 .optflag("", "json", "emit json output instead of plaintext output")
133 .optopt("", "logfile", "file to log test execution to", "FILE")
134 .optopt("", "target", "the target to build for", "TARGET")
135 .optopt("", "host", "the host to build for", "HOST")
136 .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
137 .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
138 .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
139 .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
140 .optflag("", "system-llvm", "is LLVM the system LLVM")
141 .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
142 .optopt("", "adb-path", "path to the android debugger", "PATH")
143 .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
144 .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
145 .reqopt("", "cc", "path to a C compiler", "PATH")
146 .reqopt("", "cxx", "path to a C++ compiler", "PATH")
147 .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
148 .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
149 .optopt("", "ar", "path to an archiver", "PATH")
150 .optopt("", "target-linker", "path to a linker for the target", "PATH")
151 .optopt("", "host-linker", "path to a linker for the host", "PATH")
152 .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
153 .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
154 .optopt("", "nodejs", "the name of nodejs", "PATH")
155 .optopt("", "npm", "the name of npm", "PATH")
156 .optopt("", "remote-test-client", "path to the remote test client", "PATH")
157 .optopt(
158 "",
159 "compare-mode",
160 "mode describing what file the actual ui output will be compared to",
161 "COMPARE MODE",
162 )
163 .optflag(
164 "",
165 "rustfix-coverage",
166 "enable this to generate a Rustfix coverage file, which is saved in \
167 `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
168 )
169 .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
170 .optflag("", "only-modified", "only run tests that result been modified")
171 .optflag("", "nocapture", "")
173 .optflag("", "no-capture", "don't capture stdout/stderr of tests")
174 .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
175 .optflag("h", "help", "show this message")
176 .reqopt("", "channel", "current Rust channel", "CHANNEL")
177 .optflag(
178 "",
179 "git-hash",
180 "run tests which rely on commit version being compiled into the binaries",
181 )
182 .optopt("", "edition", "default Rust edition", "EDITION")
183 .reqopt("", "git-repository", "name of the git repository", "ORG/REPO")
184 .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
185 .reqopt(
186 "",
187 "git-merge-commit-email",
188 "email address used for finding merge commits",
189 "EMAIL",
190 )
191 .optopt(
192 "",
193 "compiletest-diff-tool",
194 "What custom diff tool to use for displaying compiletest tests.",
195 "COMMAND",
196 )
197 .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
198 .optopt(
199 "",
200 "debugger",
201 "only test a specific debugger in debuginfo tests",
202 "gdb | lldb | cdb",
203 );
204
205 let (argv0, args_) = args.split_first().unwrap();
206 if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
207 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
208 println!("{}", opts.usage(&message));
209 println!();
210 panic!()
211 }
212
213 let matches = &match opts.parse(args_) {
214 Ok(m) => m,
215 Err(f) => panic!("{:?}", f),
216 };
217
218 if matches.opt_present("h") || matches.opt_present("help") {
219 let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
220 println!("{}", opts.usage(&message));
221 println!();
222 panic!()
223 }
224
225 fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
226 match m.opt_str(nm) {
227 Some(s) => PathBuf::from(&s),
228 None => panic!("no option (=path) found for {}", nm),
229 }
230 }
231
232 fn make_absolute(path: PathBuf) -> PathBuf {
233 if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
234 }
235
236 let target = opt_str2(matches.opt_str("target"));
237 let android_cross_path = opt_path(matches, "android-cross-path");
238 let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
239 let (gdb, gdb_version) =
240 debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
241 let lldb_version =
242 matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
243 let color = match matches.opt_str("color").as_deref() {
244 Some("auto") | None => ColorConfig::AutoColor,
245 Some("always") => ColorConfig::AlwaysColor,
246 Some("never") => ColorConfig::NeverColor,
247 Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
248 };
249 let llvm_version =
250 matches.opt_str("llvm-version").as_deref().map(header::extract_llvm_version).or_else(
251 || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
252 );
253
254 let run_ignored = matches.opt_present("ignored");
255 let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
256 let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
257 let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
258 let has_html_tidy = if mode == Mode::Rustdoc {
259 Command::new("tidy")
260 .arg("--version")
261 .stdout(Stdio::null())
262 .status()
263 .map_or(false, |status| status.success())
264 } else {
265 false
267 };
268 let has_enzyme = matches.opt_present("has-enzyme");
269 let filters = if mode == Mode::RunMake {
270 matches
271 .free
272 .iter()
273 .map(|f| {
274 let path = Path::new(f);
275 let mut iter = path.iter().skip(1);
276
277 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
279 path.parent().unwrap().to_str().unwrap().to_string()
280 } else {
281 f.to_string()
282 }
283 })
284 .collect::<Vec<_>>()
285 } else {
286 matches.free.clone()
287 };
288 let compare_mode = matches.opt_str("compare-mode").map(|s| {
289 s.parse().unwrap_or_else(|_| {
290 let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
291 panic!(
292 "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
293 variants.join(", ")
294 );
295 })
296 });
297 if matches.opt_present("nocapture") {
298 panic!("`--nocapture` is deprecated; please use `--no-capture`");
299 }
300
301 let stage = match matches.opt_str("stage") {
302 Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
303 None => panic!("`--stage` is required"),
304 };
305
306 let src_root = opt_path(matches, "src-root");
307 let src_test_suite_root = opt_path(matches, "src-test-suite-root");
308 assert!(
309 src_test_suite_root.starts_with(&src_root),
310 "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
311 src_root.display(),
312 src_test_suite_root.display()
313 );
314
315 let build_root = opt_path(matches, "build-root");
316 let build_test_suite_root = opt_path(matches, "build-test-suite-root");
317 assert!(build_test_suite_root.starts_with(&build_root));
318
319 Config {
320 bless: matches.opt_present("bless"),
321 compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
322 run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
323 rustc_path: opt_path(matches, "rustc-path"),
324 cargo_path: matches.opt_str("cargo-path").map(PathBuf::from),
325 stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(PathBuf::from),
326 rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
327 coverage_dump_path: matches.opt_str("coverage-dump-path").map(PathBuf::from),
328 python: matches.opt_str("python").unwrap(),
329 jsondocck_path: matches.opt_str("jsondocck-path"),
330 jsondoclint_path: matches.opt_str("jsondoclint-path"),
331 run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
332 llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
333 llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
334
335 src_root,
336 src_test_suite_root,
337
338 build_root,
339 build_test_suite_root,
340
341 sysroot_base: opt_path(matches, "sysroot-base"),
342
343 stage,
344 stage_id: matches.opt_str("stage-id").unwrap(),
345
346 mode,
347 suite: matches.opt_str("suite").unwrap(),
348 debugger: matches.opt_str("debugger").map(|debugger| {
349 debugger
350 .parse::<Debugger>()
351 .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
352 }),
353 run_ignored,
354 with_rustc_debug_assertions,
355 with_std_debug_assertions,
356 filters,
357 skip: matches.opt_strs("skip"),
358 filter_exact: matches.opt_present("exact"),
359 force_pass_mode: matches.opt_str("pass").map(|mode| {
360 mode.parse::<PassMode>()
361 .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
362 }),
363 run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
364 "auto" => None,
365 "always" => Some(true),
366 "never" => Some(false),
367 _ => panic!("unknown `--run` option `{}` given", mode),
368 }),
369 logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
370 runner: matches.opt_str("runner"),
371 host_rustcflags: matches.opt_strs("host-rustcflags"),
372 target_rustcflags: matches.opt_strs("target-rustcflags"),
373 optimize_tests: matches.opt_present("optimize-tests"),
374 rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
375 target,
376 host: opt_str2(matches.opt_str("host")),
377 cdb,
378 cdb_version,
379 gdb,
380 gdb_version,
381 lldb_version,
382 llvm_version,
383 system_llvm: matches.opt_present("system-llvm"),
384 android_cross_path,
385 adb_path: opt_str2(matches.opt_str("adb-path")),
386 adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
387 adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
388 && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
389 && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
390 lldb_python_dir: matches.opt_str("lldb-python-dir"),
391 verbose: matches.opt_present("verbose"),
392 format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
393 (true, true) => panic!("--quiet and --json are incompatible"),
394 (true, false) => test::OutputFormat::Terse,
395 (false, true) => test::OutputFormat::Json,
396 (false, false) => test::OutputFormat::Pretty,
397 },
398 only_modified: matches.opt_present("only-modified"),
399 color,
400 remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
401 compare_mode,
402 rustfix_coverage: matches.opt_present("rustfix-coverage"),
403 has_html_tidy,
404 has_enzyme,
405 channel: matches.opt_str("channel").unwrap(),
406 git_hash: matches.opt_present("git-hash"),
407 edition: matches.opt_str("edition"),
408
409 cc: matches.opt_str("cc").unwrap(),
410 cxx: matches.opt_str("cxx").unwrap(),
411 cflags: matches.opt_str("cflags").unwrap(),
412 cxxflags: matches.opt_str("cxxflags").unwrap(),
413 ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
414 target_linker: matches.opt_str("target-linker"),
415 host_linker: matches.opt_str("host-linker"),
416 llvm_components: matches.opt_str("llvm-components").unwrap(),
417 nodejs: matches.opt_str("nodejs"),
418 npm: matches.opt_str("npm"),
419
420 force_rerun: matches.opt_present("force-rerun"),
421
422 target_cfgs: OnceLock::new(),
423 builtin_cfg_names: OnceLock::new(),
424
425 nocapture: matches.opt_present("no-capture"),
426
427 git_repository: matches.opt_str("git-repository").unwrap(),
428 nightly_branch: matches.opt_str("nightly-branch").unwrap(),
429 git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
430
431 profiler_runtime: matches.opt_present("profiler-runtime"),
432
433 diff_command: matches.opt_str("compiletest-diff-tool"),
434
435 minicore_path: opt_path(matches, "minicore-path"),
436 }
437}
438
439pub fn log_config(config: &Config) {
440 let c = config;
441 logv(c, "configuration:".to_string());
442 logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
443 logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
444 logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
445 logv(c, format!("cargo_path: {:?}", config.cargo_path));
446 logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
447
448 logv(c, format!("src_root: {}", config.src_root.display()));
449 logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root.display()));
450
451 logv(c, format!("build_root: {}", config.build_root.display()));
452 logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root.display()));
453
454 logv(c, format!("sysroot_base: {}", config.sysroot_base.display()));
455
456 logv(c, format!("stage: {}", config.stage));
457 logv(c, format!("stage_id: {}", config.stage_id));
458 logv(c, format!("mode: {}", config.mode));
459 logv(c, format!("run_ignored: {}", config.run_ignored));
460 logv(c, format!("filters: {:?}", config.filters));
461 logv(c, format!("skip: {:?}", config.skip));
462 logv(c, format!("filter_exact: {}", config.filter_exact));
463 logv(
464 c,
465 format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
466 );
467 logv(c, format!("runner: {}", opt_str(&config.runner)));
468 logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
469 logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
470 logv(c, format!("target: {}", config.target));
471 logv(c, format!("host: {}", config.host));
472 logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
473 logv(c, format!("adb_path: {:?}", config.adb_path));
474 logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
475 logv(c, format!("adb_device_status: {}", config.adb_device_status));
476 logv(c, format!("ar: {}", config.ar));
477 logv(c, format!("target-linker: {:?}", config.target_linker));
478 logv(c, format!("host-linker: {:?}", config.host_linker));
479 logv(c, format!("verbose: {}", config.verbose));
480 logv(c, format!("format: {:?}", config.format));
481 logv(c, format!("minicore_path: {:?}", config.minicore_path.display()));
482 logv(c, "\n".to_string());
483}
484
485pub fn opt_str(maybestr: &Option<String>) -> &str {
486 match *maybestr {
487 None => "(none)",
488 Some(ref s) => s,
489 }
490}
491
492pub fn opt_str2(maybestr: Option<String>) -> String {
493 match maybestr {
494 None => "(none)".to_owned(),
495 Some(s) => s,
496 }
497}
498
499pub fn run_tests(config: Arc<Config>) {
501 if config.rustfix_coverage {
505 let mut coverage_file_path = config.build_test_suite_root.clone();
506 coverage_file_path.push("rustfix_missing_coverage.txt");
507 if coverage_file_path.exists() {
508 if let Err(e) = fs::remove_file(&coverage_file_path) {
509 panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
510 }
511 }
512 }
513
514 unsafe {
518 raise_fd_limit::raise_fd_limit();
519 }
520 env::set_var("__COMPAT_LAYER", "RunAsInvoker");
523
524 env::set_var("TARGET", &config.target);
526
527 let opts = test_opts(&config);
528
529 let mut configs = Vec::new();
530 if let Mode::DebugInfo = config.mode {
531 if !config.target.contains("emscripten") {
533 match config.debugger {
534 Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
535 Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
536 Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
537 None => {
538 configs.extend(debuggers::configure_cdb(&config));
539 configs.extend(debuggers::configure_gdb(&config));
540 configs.extend(debuggers::configure_lldb(&config));
541 }
542 }
543 }
544 } else {
545 configs.push(config.clone());
546 };
547
548 let mut tests = Vec::new();
551 for c in configs {
552 tests.extend(collect_and_make_tests(c));
553 }
554
555 tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
556
557 let res = test::run_tests_console(&opts, tests);
561
562 match res {
564 Ok(true) => {}
565 Ok(false) => {
566 println!(
574 "Some tests failed in compiletest suite={}{} mode={} host={} target={}",
575 config.suite,
576 config
577 .compare_mode
578 .as_ref()
579 .map(|c| format!(" compare_mode={:?}", c))
580 .unwrap_or_default(),
581 config.mode,
582 config.host,
583 config.target
584 );
585
586 std::process::exit(1);
587 }
588 Err(e) => {
589 panic!("I/O failure during tests: {:?}", e);
596 }
597 }
598}
599
600pub fn test_opts(config: &Config) -> test::TestOpts {
601 if env::var("RUST_TEST_NOCAPTURE").is_ok() {
602 eprintln!(
603 "WARNING: RUST_TEST_NOCAPTURE is no longer used. \
604 Use the `--nocapture` flag instead."
605 );
606 }
607
608 test::TestOpts {
609 exclude_should_panic: false,
610 filters: config.filters.clone(),
611 filter_exact: config.filter_exact,
612 run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
613 format: config.format,
614 logfile: config.logfile.clone(),
615 run_tests: true,
616 bench_benchmarks: true,
617 nocapture: config.nocapture,
618 color: config.color,
619 shuffle: false,
620 shuffle_seed: None,
621 test_threads: None,
622 skip: config.skip.clone(),
623 list: false,
624 options: test::Options::new(),
625 time_options: None,
626 force_run_in_process: false,
627 fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
628 }
629}
630
631struct TestCollectorCx {
633 config: Arc<Config>,
634 cache: HeadersCache,
635 common_inputs_stamp: Stamp,
636 modified_tests: Vec<PathBuf>,
637}
638
639struct TestCollector {
641 tests: Vec<test::TestDescAndFn>,
642 found_path_stems: HashSet<PathBuf>,
643 poisoned: bool,
644}
645
646pub fn collect_and_make_tests(config: Arc<Config>) -> Vec<test::TestDescAndFn> {
652 debug!("making tests from {}", config.src_test_suite_root.display());
653 let common_inputs_stamp = common_inputs_stamp(&config);
654 let modified_tests =
655 modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
656 panic!(
657 "modified_tests got error from dir: {}, error: {}",
658 config.src_test_suite_root.display(),
659 err
660 )
661 });
662 let cache = HeadersCache::load(&config);
663
664 let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
665 let mut collector =
666 TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false };
667
668 collect_tests_from_dir(&cx, &mut collector, &cx.config.src_test_suite_root, Path::new(""))
669 .unwrap_or_else(|reason| {
670 panic!(
671 "Could not read tests from {}: {reason}",
672 cx.config.src_test_suite_root.display()
673 )
674 });
675
676 let TestCollector { tests, found_path_stems, poisoned } = collector;
677
678 if poisoned {
679 eprintln!();
680 panic!("there are errors in tests");
681 }
682
683 check_for_overlapping_test_paths(&found_path_stems);
684
685 tests
686}
687
688fn common_inputs_stamp(config: &Config) -> Stamp {
696 let src_root = &config.src_root;
697
698 let mut stamp = Stamp::from_path(&config.rustc_path);
699
700 let pretty_printer_files = [
702 "src/etc/rust_types.py",
703 "src/etc/gdb_load_rust_pretty_printers.py",
704 "src/etc/gdb_lookup.py",
705 "src/etc/gdb_providers.py",
706 "src/etc/lldb_batchmode.py",
707 "src/etc/lldb_lookup.py",
708 "src/etc/lldb_providers.py",
709 ];
710 for file in &pretty_printer_files {
711 let path = src_root.join(file);
712 stamp.add_path(&path);
713 }
714
715 stamp.add_dir(&src_root.join("src/etc/natvis"));
716
717 stamp.add_dir(&config.run_lib_path);
718
719 if let Some(ref rustdoc_path) = config.rustdoc_path {
720 stamp.add_path(&rustdoc_path);
721 stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
722 }
723
724 if let Some(coverage_dump_path) = &config.coverage_dump_path {
727 stamp.add_path(coverage_dump_path)
728 }
729
730 stamp.add_dir(&src_root.join("src/tools/run-make-support"));
731
732 stamp.add_dir(&src_root.join("src/tools/compiletest"));
734
735 stamp
736}
737
738fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
743 if !config.only_modified {
746 return Ok(vec![]);
747 }
748
749 let files =
750 get_git_modified_files(&config.git_config(), Some(dir), &vec!["rs", "stderr", "fixed"])?;
751 let untracked_files = get_git_untracked_files(&config.git_config(), None)?.unwrap_or(vec![]);
753
754 let all_paths = [&files[..], &untracked_files[..]].concat();
755 let full_paths = {
756 let mut full_paths: Vec<PathBuf> = all_paths
757 .into_iter()
758 .map(|f| PathBuf::from(f).with_extension("").with_extension("rs"))
759 .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None })
760 .collect();
761 full_paths.dedup();
762 full_paths.sort_unstable();
763 full_paths
764 };
765 Ok(full_paths)
766}
767
768fn collect_tests_from_dir(
771 cx: &TestCollectorCx,
772 collector: &mut TestCollector,
773 dir: &Path,
774 relative_dir_path: &Path,
775) -> io::Result<()> {
776 if dir.join("compiletest-ignore-dir").exists() {
778 return Ok(());
779 }
780
781 if cx.config.mode == Mode::RunMake {
783 if dir.join("rmake.rs").exists() {
784 let paths = TestPaths {
785 file: dir.to_path_buf(),
786 relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
787 };
788 make_test(cx, collector, &paths);
789 return Ok(());
791 }
792 }
793
794 let build_dir = output_relative_path(&cx.config, relative_dir_path);
801 fs::create_dir_all(&build_dir).unwrap();
802
803 for file in fs::read_dir(dir)? {
808 let file = file?;
809 let file_path = file.path();
810 let file_name = file.file_name();
811
812 if is_test(&file_name)
813 && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
814 {
815 debug!("found test file: {:?}", file_path.display());
817
818 let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
820 collector.found_path_stems.insert(rel_test_path);
821
822 let paths =
823 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
824 make_test(cx, collector, &paths);
825 } else if file_path.is_dir() {
826 let relative_file_path = relative_dir_path.join(file.file_name());
828 if &file_name != "auxiliary" {
829 debug!("found directory: {:?}", file_path.display());
830 collect_tests_from_dir(cx, collector, &file_path, &relative_file_path)?;
831 }
832 } else {
833 debug!("found other file/directory: {:?}", file_path.display());
834 }
835 }
836 Ok(())
837}
838
839pub fn is_test(file_name: &OsString) -> bool {
841 let file_name = file_name.to_str().unwrap();
842
843 if !file_name.ends_with(".rs") {
844 return false;
845 }
846
847 let invalid_prefixes = &[".", "#", "~"];
849 !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
850}
851
852fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
855 let test_path = if cx.config.mode == Mode::RunMake {
859 testpaths.file.join("rmake.rs")
860 } else {
861 PathBuf::from(&testpaths.file)
862 };
863
864 let early_props = EarlyProps::from_file(&cx.config, &test_path);
866
867 let revisions = if early_props.revisions.is_empty() || cx.config.mode == Mode::Incremental {
874 vec![None]
875 } else {
876 early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
877 };
878
879 collector.tests.extend(revisions.into_iter().map(|revision| {
882 let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
884 let test_name = make_test_name(&cx.config, testpaths, revision);
885 let mut desc = make_test_description(
889 &cx.config,
890 &cx.cache,
891 test_name,
892 &test_path,
893 src_file,
894 revision,
895 &mut collector.poisoned,
896 );
897
898 if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
901 desc.ignore = true;
902 desc.ignore_message = Some("up-to-date");
904 }
905
906 let testfn = make_test_closure(Arc::clone(&cx.config), testpaths, revision);
908
909 test::TestDescAndFn { desc, testfn }
910 }));
911}
912
913fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
916 output_base_dir(config, testpaths, revision).join("stamp")
917}
918
919fn files_related_to_test(
924 config: &Config,
925 testpaths: &TestPaths,
926 props: &EarlyProps,
927 revision: Option<&str>,
928) -> Vec<PathBuf> {
929 let mut related = vec![];
930
931 if testpaths.file.is_dir() {
932 for entry in WalkDir::new(&testpaths.file) {
934 let path = entry.unwrap().into_path();
935 if path.is_file() {
936 related.push(path);
937 }
938 }
939 } else {
940 related.push(testpaths.file.clone());
941 }
942
943 for aux in props.aux.all_aux_path_strings() {
944 let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
946 related.push(path);
947 }
948
949 for extension in UI_EXTENSIONS {
951 let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
952 related.push(path);
953 }
954
955 related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
957
958 related
959}
960
961fn is_up_to_date(
967 cx: &TestCollectorCx,
968 testpaths: &TestPaths,
969 props: &EarlyProps,
970 revision: Option<&str>,
971) -> bool {
972 let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
973 let contents = match fs::read_to_string(&stamp_file_path) {
975 Ok(f) => f,
976 Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
977 Err(_) => return false,
979 };
980 let expected_hash = runtest::compute_stamp_hash(&cx.config);
981 if contents != expected_hash {
982 return false;
985 }
986
987 let mut inputs_stamp = cx.common_inputs_stamp.clone();
990 for path in files_related_to_test(&cx.config, testpaths, props, revision) {
991 inputs_stamp.add_path(&path);
992 }
993
994 inputs_stamp < Stamp::from_path(&stamp_file_path)
997}
998
999#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1001struct Stamp {
1002 time: SystemTime,
1003}
1004
1005impl Stamp {
1006 fn from_path(path: &Path) -> Self {
1008 let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1009 stamp.add_path(path);
1010 stamp
1011 }
1012
1013 fn add_path(&mut self, path: &Path) {
1016 let modified = fs::metadata(path)
1017 .and_then(|metadata| metadata.modified())
1018 .unwrap_or(SystemTime::UNIX_EPOCH);
1019 self.time = self.time.max(modified);
1020 }
1021
1022 fn add_dir(&mut self, path: &Path) {
1026 for entry in WalkDir::new(path) {
1027 let entry = entry.unwrap();
1028 if entry.file_type().is_file() {
1029 let modified = entry
1030 .metadata()
1031 .ok()
1032 .and_then(|metadata| metadata.modified().ok())
1033 .unwrap_or(SystemTime::UNIX_EPOCH);
1034 self.time = self.time.max(modified);
1035 }
1036 }
1037 }
1038}
1039
1040fn make_test_name(
1042 config: &Config,
1043 testpaths: &TestPaths,
1044 revision: Option<&str>,
1045) -> test::TestName {
1046 let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1048 let debugger = match config.debugger {
1049 Some(d) => format!("-{}", d),
1050 None => String::new(),
1051 };
1052 let mode_suffix = match config.compare_mode {
1053 Some(ref mode) => format!(" ({})", mode.to_str()),
1054 None => String::new(),
1055 };
1056
1057 test::DynTestName(format!(
1058 "[{}{}{}] {}{}",
1059 config.mode,
1060 debugger,
1061 mode_suffix,
1062 path.display(),
1063 revision.map_or("".to_string(), |rev| format!("#{}", rev))
1064 ))
1065}
1066
1067fn make_test_closure(
1070 config: Arc<Config>,
1071 testpaths: &TestPaths,
1072 revision: Option<&str>,
1073) -> test::TestFn {
1074 let testpaths = testpaths.clone();
1075 let revision = revision.map(str::to_owned);
1076
1077 test::DynTestFn(Box::new(move || {
1080 runtest::run(config, &testpaths, revision.as_deref());
1081 Ok(())
1082 }))
1083}
1084
1085fn check_for_overlapping_test_paths(found_path_stems: &HashSet<PathBuf>) {
1103 let mut collisions = Vec::new();
1104 for path in found_path_stems {
1105 for ancestor in path.ancestors().skip(1) {
1106 if found_path_stems.contains(ancestor) {
1107 collisions.push((path, ancestor));
1108 }
1109 }
1110 }
1111 if !collisions.is_empty() {
1112 collisions.sort();
1113 let collisions: String = collisions
1114 .into_iter()
1115 .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n"))
1116 .collect();
1117 panic!(
1118 "{collisions}\n\
1119 Tests cannot have overlapping names. Make sure they use unique prefixes."
1120 );
1121 }
1122}