compiletest/
lib.rs

1#![crate_name = "compiletest"]
2// The `test` crate is the only unstable feature
3// allowed here, just to share similar code.
4#![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
46/// Creates the `Config` instance for this invocation of compiletest.
47///
48/// The config mostly reflects command-line arguments, but there might also be
49/// some code here that inspects environment variables or even runs executables
50/// (e.g. when discovering debugger versions).
51pub 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        // FIXME: Temporarily retained so we can point users to `--no-capture`
164        .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        // Avoid spawning an external command when we know html-tidy won't be used.
259        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                // We skip the test folder and check if the user passed `rmake.rs` or `Makefile`.
271                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
470/// Called by `main` after the config has been parsed.
471pub fn run_tests(config: Arc<Config>) {
472    // If we want to collect rustfix coverage information,
473    // we first make sure that the coverage file does not exist.
474    // It will be created later on.
475    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    // sadly osx needs some file descriptor limits raised for running tests in
486    // parallel (especially when we have lots and lots of child processes).
487    // For context, see #8904
488    unsafe {
489        raise_fd_limit::raise_fd_limit();
490    }
491    // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
492    // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
493    env::set_var("__COMPAT_LAYER", "RunAsInvoker");
494
495    // Let tests know which target they're running as
496    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        // Debugging emscripten code doesn't make sense today
503        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    // Discover all of the tests in the test suite directory, and build a libtest
520    // structure for each test (or each revision of a multi-revision test).
521    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    // Delegate to libtest to filter and run the big list of structures created
529    // during test discovery. When libtest decides to run a test, it will invoke
530    // the corresponding closure created by `make_test_closure`.
531    let res = test::run_tests_console(&opts, tests);
532
533    // Check the outcome reported by libtest.
534    match res {
535        Ok(true) => {}
536        Ok(false) => {
537            // We want to report that the tests failed, but we also want to give
538            // some indication of just what tests we were running. Especially on
539            // CI, where there can be cross-compiled tests for a lot of
540            // architectures, without this critical information it can be quite
541            // easy to miss which tests failed, and as such fail to reproduce
542            // the failure locally.
543
544            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            // We don't know if tests passed or not, but if there was an error
561            // during testing we don't want to just succeed (we may not have
562            // tested something), so fail.
563            //
564            // This should realistically "never" happen, so don't try to make
565            // this a pretty error message.
566            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
602/// Read-only context data used during test collection.
603struct TestCollectorCx {
604    config: Arc<Config>,
605    cache: HeadersCache,
606    common_inputs_stamp: Stamp,
607    modified_tests: Vec<PathBuf>,
608}
609
610/// Mutable state used during test collection.
611struct TestCollector {
612    tests: Vec<test::TestDescAndFn>,
613    found_path_stems: HashSet<PathBuf>,
614    poisoned: bool,
615}
616
617/// Creates libtest structures for every test/revision in the test suite directory.
618///
619/// This always inspects _all_ test files in the suite (e.g. all 17k+ ui tests),
620/// regardless of whether any filters/tests were specified on the command-line,
621/// because filtering is handled later by libtest.
622pub 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
650/// Returns the most recent last-modified timestamp from among the input files
651/// that are considered relevant to all tests (e.g. the compiler, std, and
652/// compiletest itself).
653///
654/// (Some of these inputs aren't actually relevant to _all_ tests, but they are
655/// common to some subset of tests, and are hopefully unlikely to be modified
656/// while working on other tests.)
657fn 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    // Relevant pretty printer files
663    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    // Re-run coverage tests if the `coverage-dump` tool was modified,
687    // because its output format might have changed.
688    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    // Compiletest itself.
695    stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/"));
696
697    stamp
698}
699
700/// Returns a list of modified/untracked test files that should be run when
701/// the `--only-modified` flag is in use.
702///
703/// (Might be inaccurate in some cases.)
704fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
705    // If `--only-modified` wasn't passed, the list of modified tests won't be
706    // used for anything, so avoid some work and just return an empty list.
707    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    // Add new test cases to the list, it will be convenient in daily development.
715    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
731/// Recursively scans a directory to find test files and create test structures
732/// that will be handed over to libtest.
733fn collect_tests_from_dir(
734    cx: &TestCollectorCx,
735    collector: &mut TestCollector,
736    dir: &Path,
737    relative_dir_path: &Path,
738) -> io::Result<()> {
739    // Ignore directories that contain a file named `compiletest-ignore-dir`.
740    if dir.join("compiletest-ignore-dir").exists() {
741        return Ok(());
742    }
743
744    // For run-make tests, a "test file" is actually a directory that contains
745    // an `rmake.rs` or `Makefile`"
746    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            // This directory is a test, so don't try to find other tests inside it.
760            return Ok(());
761        }
762    }
763
764    // If we find a test foo/bar.rs, we have to build the
765    // output directory `$build/foo` so we can write
766    // `$build/foo/bar` into it. We do this *now* in this
767    // sequential loop because otherwise, if we do it in the
768    // tests themselves, they race for the privilege of
769    // creating the directories and sometimes fail randomly.
770    let build_dir = output_relative_path(&cx.config, relative_dir_path);
771    fs::create_dir_all(&build_dir).unwrap();
772
773    // Add each `.rs` file as a test, and recurse further on any
774    // subdirectories we find, except for `auxiliary` directories.
775    // FIXME: this walks full tests tree, even if we have something to ignore
776    // use walkdir/ignore like in tidy?
777    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            // We found a test file, so create the corresponding libtest structures.
786            debug!("found test file: {:?}", file_path.display());
787
788            // Record the stem of the test file, to check for overlaps later.
789            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            // Recurse to find more tests in a subdirectory.
797            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
809/// Returns true if `file_name` looks like a proper test file name.
810pub 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    // `.`, `#`, and `~` are common temp-file prefixes.
818    let invalid_prefixes = &[".", "#", "~"];
819    !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
820}
821
822/// For a single test file, creates one or more test structures (one per revision)
823/// that can be handed over to libtest to run, possibly in parallel.
824fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
825    // For run-make tests, each "test file" is actually a _directory_ containing
826    // an `rmake.rs` or `Makefile`. But for the purposes of directive parsing,
827    // we want to look at that recipe file, not the directory itself.
828    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            // Parse directives in rmake.rs.
835            testpaths.file.join("rmake.rs")
836        } else {
837            // Parse directives in the Makefile.
838            testpaths.file.join("Makefile")
839        }
840    } else {
841        PathBuf::from(&testpaths.file)
842    };
843
844    // Scan the test file to discover its revisions, if any.
845    let early_props = EarlyProps::from_file(&cx.config, &test_path);
846
847    // Normally we create one libtest structure per revision, with two exceptions:
848    // - If a test doesn't use revisions, create a dummy revision (None) so that
849    //   the test can still run.
850    // - Incremental tests inherently can't run their revisions in parallel, so
851    //   we treat them like non-revisioned tests here. Incremental revisions are
852    //   handled internally by `runtest::run` instead.
853    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    // For each revision (or the sole dummy revision), create and append a
860    // `test::TestDescAndFn` that can be handed over to libtest.
861    collector.tests.extend(revisions.into_iter().map(|revision| {
862        // Create a test name and description to hand over to libtest.
863        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        // Create a libtest description for the test/revision.
866        // This is where `ignore-*`/`only-*`/`needs-*` directives are handled,
867        // because they need to set the libtest ignored flag.
868        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 a test's inputs haven't changed since the last time it ran,
879        // mark it as ignored so that libtest will skip it.
880        if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
881            desc.ignore = true;
882            // Keep this in sync with the "up-to-date" message detected by bootstrap.
883            desc.ignore_message = Some("up-to-date");
884        }
885
886        // Create the callback that will run this test/revision when libtest calls it.
887        let testfn = make_test_closure(Arc::clone(&cx.config), testpaths, revision);
888
889        test::TestDescAndFn { desc, testfn }
890    }));
891}
892
893/// The path of the `stamp` file that gets created or updated whenever a
894/// particular test completes successfully.
895fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
896    output_base_dir(config, testpaths, revision).join("stamp")
897}
898
899/// Returns a list of files that, if modified, would cause this test to no
900/// longer be up-to-date.
901///
902/// (Might be inaccurate in some cases.)
903fn 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        // run-make tests use their individual directory
913        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        // FIXME(Zalathar): Perform all `auxiliary` path resolution in one place.
925        let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
926        related.push(path);
927    }
928
929    // UI test files.
930    for extension in UI_EXTENSIONS {
931        let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
932        related.push(path);
933    }
934
935    // `minicore.rs` test auxiliary: we need to make sure tests get rerun if this changes.
936    //
937    // FIXME(jieyouxu): untangle these paths, we should provide both a path to root `tests/` or
938    // `tests/auxiliary/` and the test suite in question. `src_base` is also a terrible name.
939    related.push(config.src_base.parent().unwrap().join("auxiliary").join("minicore.rs"));
940
941    related
942}
943
944/// Checks whether a particular test/revision is "up-to-date", meaning that no
945/// relevant files/settings have changed since the last time the test succeeded.
946///
947/// (This is not very reliable in some circumstances, so the `--force-rerun`
948/// flag can be used to ignore up-to-date checking and always re-run tests.)
949fn 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    // Check the config hash inside the stamp file.
957    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        // The test hasn't succeeded yet, so it is not up-to-date.
961        Err(_) => return false,
962    };
963    let expected_hash = runtest::compute_stamp_hash(&cx.config);
964    if contents != expected_hash {
965        // Some part of compiletest configuration has changed since the test
966        // last succeeded, so it is not up-to-date.
967        return false;
968    }
969
970    // Check the timestamp of the stamp file against the last modified time
971    // of all files known to be relevant to the test.
972    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    // If no relevant files have been modified since the stamp file was last
978    // written, the test is up-to-date.
979    inputs_stamp < Stamp::from_path(&stamp_file_path)
980}
981
982/// The maximum of a set of file-modified timestamps.
983#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
984struct Stamp {
985    time: SystemTime,
986}
987
988impl Stamp {
989    /// Creates a timestamp holding the last-modified time of the specified file.
990    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    /// Updates this timestamp to the last-modified time of the specified file,
997    /// if it is later than the currently-stored timestamp.
998    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    /// Updates this timestamp to the most recent last-modified time of all files
1006    /// recursively contained in the given directory, if it is later than the
1007    /// currently-stored timestamp.
1008    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
1023/// Creates a name for this test/revision that can be handed over to libtest.
1024fn make_test_name(
1025    config: &Config,
1026    testpaths: &TestPaths,
1027    revision: Option<&str>,
1028) -> test::TestName {
1029    // Print the name of the file, relative to the repository root.
1030    // `src_base` looks like `/path/to/rust/tests/ui`
1031    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
1052/// Creates a callback for this test/revision that libtest will call when it
1053/// decides to actually run the underlying test.
1054fn 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    // This callback is the link between compiletest's test discovery code,
1063    // and the parts of compiletest that know how to run an individual test.
1064    test::DynTestFn(Box::new(move || {
1065        runtest::run(config, &testpaths, revision.as_deref());
1066        Ok(())
1067    }))
1068}
1069
1070/// Checks that test discovery didn't find any tests whose name stem is a prefix
1071/// of some other tests's name.
1072///
1073/// For example, suppose the test suite contains these two test files:
1074/// - `tests/rustdoc/primitive.rs`
1075/// - `tests/rustdoc/primitive/no_std.rs`
1076///
1077/// The test runner might put the output from those tests in these directories:
1078/// - `$build/test/rustdoc/primitive/`
1079/// - `$build/test/rustdoc/primitive/no_std/`
1080///
1081/// Because one output path is a subdirectory of the other, the two tests might
1082/// interfere with each other in unwanted ways, especially if the test runner
1083/// decides to delete test output directories to clean them between runs.
1084/// To avoid problems, we forbid test names from overlapping in this way.
1085///
1086/// See <https://github.com/rust-lang/rust/pull/109509> for more context.
1087fn 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}