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