compiletest/
lib.rs

1#![crate_name = "compiletest"]
2// Needed by the "new" test executor that does not depend on libtest.
3// FIXME(Zalathar): We should be able to get rid of `internal_output_capture`,
4// by having `runtest` manually capture all of its println-like output instead.
5// That would result in compiletest being written entirely in stable Rust!
6#![feature(internal_output_capture)]
7
8#[cfg(test)]
9mod tests;
10
11pub mod common;
12pub mod compute_diff;
13mod debuggers;
14pub mod diagnostics;
15pub mod directives;
16pub mod errors;
17mod executor;
18mod json;
19mod raise_fd_limit;
20mod read2;
21pub mod runtest;
22pub mod util;
23
24use core::panic;
25use std::collections::HashSet;
26use std::fmt::Write;
27use std::io::{self, ErrorKind};
28use std::process::{Command, Stdio};
29use std::sync::{Arc, OnceLock};
30use std::time::SystemTime;
31use std::{env, fs, vec};
32
33use build_helper::git::{get_git_modified_files, get_git_untracked_files};
34use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
35use getopts::Options;
36use rayon::iter::{ParallelBridge, ParallelIterator};
37use tracing::debug;
38use walkdir::WalkDir;
39
40use self::directives::{EarlyProps, make_test_description};
41use crate::common::{
42    CodegenBackend, CompareMode, Config, Debugger, PassMode, TestMode, TestPaths, UI_EXTENSIONS,
43    expected_output_path, output_base_dir, output_relative_path,
44};
45use crate::directives::DirectivesCache;
46use crate::executor::{CollectedTest, ColorConfig, OutputFormat};
47use crate::util::logv;
48
49/// Creates the `Config` instance for this invocation of compiletest.
50///
51/// The config mostly reflects command-line arguments, but there might also be
52/// some code here that inspects environment variables or even runs executables
53/// (e.g. when discovering debugger versions).
54pub fn parse_config(args: Vec<String>) -> Config {
55    let mut opts = Options::new();
56    opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH")
57        .reqopt("", "run-lib-path", "path to target shared libraries", "PATH")
58        .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH")
59        .optopt("", "cargo-path", "path to cargo to use for compiling", "PATH")
60        .optopt(
61            "",
62            "stage0-rustc-path",
63            "path to rustc to use for compiling run-make recipes",
64            "PATH",
65        )
66        .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH")
67        .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH")
68        .reqopt("", "python", "path to python to use for doc tests", "PATH")
69        .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
70        .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
71        .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
72        .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
73        .reqopt("", "src-root", "directory containing sources", "PATH")
74        .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
75        .reqopt("", "build-root", "path to root build directory", "PATH")
76        .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
77        .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
78        .reqopt("", "stage", "stage number under test", "N")
79        .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
80        .reqopt(
81            "",
82            "mode",
83            "which sort of compile tests to run",
84            "pretty | debug-info | codegen | rustdoc \
85            | rustdoc-json | codegen-units | incremental | run-make | ui \
86            | rustdoc-js | mir-opt | assembly | crashes",
87        )
88        .reqopt(
89            "",
90            "suite",
91            "which suite of compile tests to run. used for nicer error reporting.",
92            "SUITE",
93        )
94        .optopt(
95            "",
96            "pass",
97            "force {check,build,run}-pass tests to this mode.",
98            "check | build | run",
99        )
100        .optopt("", "run", "whether to execute run-* tests", "auto | always | never")
101        .optflag("", "ignored", "run tests marked as ignored")
102        .optflag("", "has-enzyme", "run tests that require enzyme")
103        .optflag("", "with-rustc-debug-assertions", "whether rustc was built with debug assertions")
104        .optflag("", "with-std-debug-assertions", "whether std was built with debug assertions")
105        .optmulti(
106            "",
107            "skip",
108            "skip tests matching SUBSTRING. Can be passed multiple times",
109            "SUBSTRING",
110        )
111        .optflag("", "exact", "filters match exactly")
112        .optopt(
113            "",
114            "runner",
115            "supervisor program to run tests under \
116             (eg. emulator, valgrind)",
117            "PROGRAM",
118        )
119        .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
120        .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS")
121        .optflag(
122            "",
123            "rust-randomized-layout",
124            "set this when rustc/stdlib were compiled with randomized layouts",
125        )
126        .optflag("", "optimize-tests", "run tests with optimizations enabled")
127        .optflag("", "verbose", "run tests verbosely, showing all output")
128        .optflag(
129            "",
130            "bless",
131            "overwrite stderr/stdout files instead of complaining about a mismatch",
132        )
133        .optflag("", "fail-fast", "stop as soon as possible after any test fails")
134        .optflag("", "quiet", "print one character per test instead of one line")
135        .optopt("", "color", "coloring: auto, always, never", "WHEN")
136        .optflag("", "json", "emit json output instead of plaintext output")
137        .optopt("", "target", "the target to build for", "TARGET")
138        .optopt("", "host", "the host to build for", "HOST")
139        .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH")
140        .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH")
141        .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING")
142        .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING")
143        .optflag("", "system-llvm", "is LLVM the system LLVM")
144        .optopt("", "android-cross-path", "Android NDK standalone path", "PATH")
145        .optopt("", "adb-path", "path to the android debugger", "PATH")
146        .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH")
147        .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH")
148        .reqopt("", "cc", "path to a C compiler", "PATH")
149        .reqopt("", "cxx", "path to a C++ compiler", "PATH")
150        .reqopt("", "cflags", "flags for the C compiler", "FLAGS")
151        .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS")
152        .optopt("", "ar", "path to an archiver", "PATH")
153        .optopt("", "target-linker", "path to a linker for the target", "PATH")
154        .optopt("", "host-linker", "path to a linker for the host", "PATH")
155        .reqopt("", "llvm-components", "list of LLVM components built in", "LIST")
156        .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
157        .optopt("", "nodejs", "the name of nodejs", "PATH")
158        .optopt("", "npm", "the name of npm", "PATH")
159        .optopt("", "remote-test-client", "path to the remote test client", "PATH")
160        .optopt(
161            "",
162            "compare-mode",
163            "mode describing what file the actual ui output will be compared to",
164            "COMPARE MODE",
165        )
166        .optflag(
167            "",
168            "rustfix-coverage",
169            "enable this to generate a Rustfix coverage file, which is saved in \
170            `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
171        )
172        .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
173        .optflag("", "only-modified", "only run tests that result been modified")
174        // FIXME: Temporarily retained so we can point users to `--no-capture`
175        .optflag("", "nocapture", "")
176        .optflag("", "no-capture", "don't capture stdout/stderr of tests")
177        .optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
178        .optflag("h", "help", "show this message")
179        .reqopt("", "channel", "current Rust channel", "CHANNEL")
180        .optflag(
181            "",
182            "git-hash",
183            "run tests which rely on commit version being compiled into the binaries",
184        )
185        .optopt("", "edition", "default Rust edition", "EDITION")
186        .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
187        .reqopt(
188            "",
189            "git-merge-commit-email",
190            "email address used for finding merge commits",
191            "EMAIL",
192        )
193        .optopt(
194            "",
195            "compiletest-diff-tool",
196            "What custom diff tool to use for displaying compiletest tests.",
197            "COMMAND",
198        )
199        .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
200        .optflag("N", "no-new-executor", "disables the new test executor, and uses libtest instead")
201        .optopt(
202            "",
203            "debugger",
204            "only test a specific debugger in debuginfo tests",
205            "gdb | lldb | cdb",
206        )
207        .optopt(
208            "",
209            "codegen-backend",
210            "the codegen backend currently used",
211            "CODEGEN BACKEND NAME",
212        );
213
214    let (argv0, args_) = args.split_first().unwrap();
215    if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
216        let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0);
217        println!("{}", opts.usage(&message));
218        println!();
219        panic!()
220    }
221
222    let matches = &match opts.parse(args_) {
223        Ok(m) => m,
224        Err(f) => panic!("{:?}", f),
225    };
226
227    if matches.opt_present("h") || matches.opt_present("help") {
228        let message = format!("Usage: {} [OPTIONS]  [TESTNAME...]", argv0);
229        println!("{}", opts.usage(&message));
230        println!();
231        panic!()
232    }
233
234    fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
235        if path.is_relative() {
236            Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
237        } else {
238            path
239        }
240    }
241
242    fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
243        match m.opt_str(nm) {
244            Some(s) => Utf8PathBuf::from(&s),
245            None => panic!("no option (=path) found for {}", nm),
246        }
247    }
248
249    let target = opt_str2(matches.opt_str("target"));
250    let android_cross_path = opt_path(matches, "android-cross-path");
251    // FIXME: `cdb_version` is *derived* from cdb, but it's *not* technically a config!
252    let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
253    // FIXME: `gdb_version` is *derived* from gdb, but it's *not* technically a config!
254    let (gdb, gdb_version) =
255        debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
256    // FIXME: `lldb_version` is *derived* from lldb, but it's *not* technically a config!
257    let lldb_version =
258        matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
259    let color = match matches.opt_str("color").as_deref() {
260        Some("auto") | None => ColorConfig::AutoColor,
261        Some("always") => ColorConfig::AlwaysColor,
262        Some("never") => ColorConfig::NeverColor,
263        Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x),
264    };
265    // FIXME: this is very questionable, we really should be obtaining LLVM version info from
266    // `bootstrap`, and not trying to be figuring out that in `compiletest` by running the
267    // `FileCheck` binary.
268    let llvm_version =
269        matches.opt_str("llvm-version").as_deref().map(directives::extract_llvm_version).or_else(
270            || directives::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?),
271        );
272
273    let codegen_backend = match matches.opt_str("codegen-backend").as_deref() {
274        Some(backend) => match CodegenBackend::try_from(backend) {
275            Ok(backend) => backend,
276            Err(error) => panic!("invalid value `{backend}` for `--codegen-backend`: {error}"),
277        },
278        // By default, it's always llvm.
279        None => CodegenBackend::Llvm,
280    };
281
282    let run_ignored = matches.opt_present("ignored");
283    let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions");
284    let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions");
285    let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode");
286    let has_html_tidy = if mode == TestMode::Rustdoc {
287        Command::new("tidy")
288            .arg("--version")
289            .stdout(Stdio::null())
290            .status()
291            .map_or(false, |status| status.success())
292    } else {
293        // Avoid spawning an external command when we know html-tidy won't be used.
294        false
295    };
296    let has_enzyme = matches.opt_present("has-enzyme");
297    let filters = if mode == TestMode::RunMake {
298        matches
299            .free
300            .iter()
301            .map(|f| {
302                let path = Utf8Path::new(f);
303                let mut iter = path.iter().skip(1);
304
305                // We skip the test folder and check if the user passed `rmake.rs`.
306                if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
307                    path.parent().unwrap().to_string()
308                } else {
309                    f.to_string()
310                }
311            })
312            .collect::<Vec<_>>()
313    } else {
314        matches.free.clone()
315    };
316    let compare_mode = matches.opt_str("compare-mode").map(|s| {
317        s.parse().unwrap_or_else(|_| {
318            let variants: Vec<_> = CompareMode::STR_VARIANTS.iter().copied().collect();
319            panic!(
320                "`{s}` is not a valid value for `--compare-mode`, it should be one of: {}",
321                variants.join(", ")
322            );
323        })
324    });
325    if matches.opt_present("nocapture") {
326        panic!("`--nocapture` is deprecated; please use `--no-capture`");
327    }
328
329    let stage = match matches.opt_str("stage") {
330        Some(stage) => stage.parse::<u32>().expect("expected `--stage` to be an unsigned integer"),
331        None => panic!("`--stage` is required"),
332    };
333
334    let src_root = opt_path(matches, "src-root");
335    let src_test_suite_root = opt_path(matches, "src-test-suite-root");
336    assert!(
337        src_test_suite_root.starts_with(&src_root),
338        "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
339        src_root,
340        src_test_suite_root
341    );
342
343    let build_root = opt_path(matches, "build-root");
344    let build_test_suite_root = opt_path(matches, "build-test-suite-root");
345    assert!(build_test_suite_root.starts_with(&build_root));
346
347    Config {
348        bless: matches.opt_present("bless"),
349        fail_fast: matches.opt_present("fail-fast")
350            || env::var_os("RUSTC_TEST_FAIL_FAST").is_some(),
351
352        compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
353        run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
354        rustc_path: opt_path(matches, "rustc-path"),
355        cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
356        stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
357        rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
358        coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
359        python: matches.opt_str("python").unwrap(),
360        jsondocck_path: matches.opt_str("jsondocck-path"),
361        jsondoclint_path: matches.opt_str("jsondoclint-path"),
362        run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
363        llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
364        llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
365
366        src_root,
367        src_test_suite_root,
368
369        build_root,
370        build_test_suite_root,
371
372        sysroot_base: opt_path(matches, "sysroot-base"),
373
374        stage,
375        stage_id: matches.opt_str("stage-id").unwrap(),
376
377        mode,
378        suite: matches.opt_str("suite").unwrap().parse().expect("invalid suite"),
379        debugger: matches.opt_str("debugger").map(|debugger| {
380            debugger
381                .parse::<Debugger>()
382                .unwrap_or_else(|_| panic!("unknown `--debugger` option `{debugger}` given"))
383        }),
384        run_ignored,
385        with_rustc_debug_assertions,
386        with_std_debug_assertions,
387        filters,
388        skip: matches.opt_strs("skip"),
389        filter_exact: matches.opt_present("exact"),
390        force_pass_mode: matches.opt_str("pass").map(|mode| {
391            mode.parse::<PassMode>()
392                .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode))
393        }),
394        // FIXME: this run scheme is... confusing.
395        run: matches.opt_str("run").and_then(|mode| match mode.as_str() {
396            "auto" => None,
397            "always" => Some(true),
398            "never" => Some(false),
399            _ => panic!("unknown `--run` option `{}` given", mode),
400        }),
401        runner: matches.opt_str("runner"),
402        host_rustcflags: matches.opt_strs("host-rustcflags"),
403        target_rustcflags: matches.opt_strs("target-rustcflags"),
404        optimize_tests: matches.opt_present("optimize-tests"),
405        rust_randomized_layout: matches.opt_present("rust-randomized-layout"),
406        target,
407        host: opt_str2(matches.opt_str("host")),
408        cdb,
409        cdb_version,
410        gdb,
411        gdb_version,
412        lldb_version,
413        llvm_version,
414        system_llvm: matches.opt_present("system-llvm"),
415        android_cross_path,
416        adb_path: opt_str2(matches.opt_str("adb-path")),
417        adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
418        adb_device_status: opt_str2(matches.opt_str("target")).contains("android")
419            && "(none)" != opt_str2(matches.opt_str("adb-test-dir"))
420            && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(),
421        lldb_python_dir: matches.opt_str("lldb-python-dir"),
422        verbose: matches.opt_present("verbose"),
423        format: match (matches.opt_present("quiet"), matches.opt_present("json")) {
424            (true, true) => panic!("--quiet and --json are incompatible"),
425            (true, false) => OutputFormat::Terse,
426            (false, true) => OutputFormat::Json,
427            (false, false) => OutputFormat::Pretty,
428        },
429        only_modified: matches.opt_present("only-modified"),
430        color,
431        remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
432        compare_mode,
433        rustfix_coverage: matches.opt_present("rustfix-coverage"),
434        has_html_tidy,
435        has_enzyme,
436        channel: matches.opt_str("channel").unwrap(),
437        git_hash: matches.opt_present("git-hash"),
438        edition: matches.opt_str("edition"),
439
440        cc: matches.opt_str("cc").unwrap(),
441        cxx: matches.opt_str("cxx").unwrap(),
442        cflags: matches.opt_str("cflags").unwrap(),
443        cxxflags: matches.opt_str("cxxflags").unwrap(),
444        ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")),
445        target_linker: matches.opt_str("target-linker"),
446        host_linker: matches.opt_str("host-linker"),
447        llvm_components: matches.opt_str("llvm-components").unwrap(),
448        nodejs: matches.opt_str("nodejs"),
449        npm: matches.opt_str("npm"),
450
451        force_rerun: matches.opt_present("force-rerun"),
452
453        target_cfgs: OnceLock::new(),
454        builtin_cfg_names: OnceLock::new(),
455        supported_crate_types: OnceLock::new(),
456
457        nocapture: matches.opt_present("no-capture"),
458
459        nightly_branch: matches.opt_str("nightly-branch").unwrap(),
460        git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
461
462        profiler_runtime: matches.opt_present("profiler-runtime"),
463
464        diff_command: matches.opt_str("compiletest-diff-tool"),
465
466        minicore_path: opt_path(matches, "minicore-path"),
467
468        codegen_backend,
469    }
470}
471
472pub fn log_config(config: &Config) {
473    let c = config;
474    logv(c, "configuration:".to_string());
475    logv(c, format!("compile_lib_path: {}", config.compile_lib_path));
476    logv(c, format!("run_lib_path: {}", config.run_lib_path));
477    logv(c, format!("rustc_path: {}", config.rustc_path));
478    logv(c, format!("cargo_path: {:?}", config.cargo_path));
479    logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
480
481    logv(c, format!("src_root: {}", config.src_root));
482    logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root));
483
484    logv(c, format!("build_root: {}", config.build_root));
485    logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root));
486
487    logv(c, format!("sysroot_base: {}", config.sysroot_base));
488
489    logv(c, format!("stage: {}", config.stage));
490    logv(c, format!("stage_id: {}", config.stage_id));
491    logv(c, format!("mode: {}", config.mode));
492    logv(c, format!("run_ignored: {}", config.run_ignored));
493    logv(c, format!("filters: {:?}", config.filters));
494    logv(c, format!("skip: {:?}", config.skip));
495    logv(c, format!("filter_exact: {}", config.filter_exact));
496    logv(
497        c,
498        format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),),
499    );
500    logv(c, format!("runner: {}", opt_str(&config.runner)));
501    logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags));
502    logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
503    logv(c, format!("target: {}", config.target));
504    logv(c, format!("host: {}", config.host));
505    logv(c, format!("android-cross-path: {}", config.android_cross_path));
506    logv(c, format!("adb_path: {}", config.adb_path));
507    logv(c, format!("adb_test_dir: {}", config.adb_test_dir));
508    logv(c, format!("adb_device_status: {}", config.adb_device_status));
509    logv(c, format!("ar: {}", config.ar));
510    logv(c, format!("target-linker: {:?}", config.target_linker));
511    logv(c, format!("host-linker: {:?}", config.host_linker));
512    logv(c, format!("verbose: {}", config.verbose));
513    logv(c, format!("format: {:?}", config.format));
514    logv(c, format!("minicore_path: {}", config.minicore_path));
515    logv(c, "\n".to_string());
516}
517
518pub fn opt_str(maybestr: &Option<String>) -> &str {
519    match *maybestr {
520        None => "(none)",
521        Some(ref s) => s,
522    }
523}
524
525pub fn opt_str2(maybestr: Option<String>) -> String {
526    match maybestr {
527        None => "(none)".to_owned(),
528        Some(s) => s,
529    }
530}
531
532/// Called by `main` after the config has been parsed.
533pub fn run_tests(config: Arc<Config>) {
534    // If we want to collect rustfix coverage information,
535    // we first make sure that the coverage file does not exist.
536    // It will be created later on.
537    if config.rustfix_coverage {
538        let mut coverage_file_path = config.build_test_suite_root.clone();
539        coverage_file_path.push("rustfix_missing_coverage.txt");
540        if coverage_file_path.exists() {
541            if let Err(e) = fs::remove_file(&coverage_file_path) {
542                panic!("Could not delete {} due to {}", coverage_file_path, e)
543            }
544        }
545    }
546
547    // sadly osx needs some file descriptor limits raised for running tests in
548    // parallel (especially when we have lots and lots of child processes).
549    // For context, see #8904
550    unsafe {
551        raise_fd_limit::raise_fd_limit();
552    }
553    // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
554    // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
555    //
556    // SAFETY: at this point we're still single-threaded.
557    unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
558
559    // Let tests know which target they're running as.
560    //
561    // SAFETY: at this point we're still single-threaded.
562    unsafe { env::set_var("TARGET", &config.target) };
563
564    let mut configs = Vec::new();
565    if let TestMode::DebugInfo = config.mode {
566        // Debugging emscripten code doesn't make sense today
567        if !config.target.contains("emscripten") {
568            match config.debugger {
569                Some(Debugger::Cdb) => configs.extend(debuggers::configure_cdb(&config)),
570                Some(Debugger::Gdb) => configs.extend(debuggers::configure_gdb(&config)),
571                Some(Debugger::Lldb) => configs.extend(debuggers::configure_lldb(&config)),
572                // FIXME: the *implicit* debugger discovery makes it really difficult to control
573                // which {`cdb`, `gdb`, `lldb`} are used. These should **not** be implicitly
574                // discovered by `compiletest`; these should be explicit `bootstrap` configuration
575                // options that are passed to `compiletest`!
576                None => {
577                    configs.extend(debuggers::configure_cdb(&config));
578                    configs.extend(debuggers::configure_gdb(&config));
579                    configs.extend(debuggers::configure_lldb(&config));
580                }
581            }
582        }
583    } else {
584        configs.push(config.clone());
585    };
586
587    // Discover all of the tests in the test suite directory, and build a libtest
588    // structure for each test (or each revision of a multi-revision test).
589    let mut tests = Vec::new();
590    for c in configs {
591        tests.extend(collect_and_make_tests(c));
592    }
593
594    tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
595
596    // Delegate to the executor to filter and run the big list of test structures
597    // created during test discovery. When the executor decides to run a test,
598    // it will return control to the rest of compiletest by calling `runtest::run`.
599    // FIXME(Zalathar): Once we're confident that we won't need to revert the
600    // removal of the libtest-based executor, remove this Result and other
601    // remnants of the old executor.
602    let res: io::Result<bool> = Ok(executor::run_tests(&config, tests));
603
604    // Check the outcome reported by libtest.
605    match res {
606        Ok(true) => {}
607        Ok(false) => {
608            // We want to report that the tests failed, but we also want to give
609            // some indication of just what tests we were running. Especially on
610            // CI, where there can be cross-compiled tests for a lot of
611            // architectures, without this critical information it can be quite
612            // easy to miss which tests failed, and as such fail to reproduce
613            // the failure locally.
614
615            let mut msg = String::from("Some tests failed in compiletest");
616            write!(msg, " suite={}", config.suite).unwrap();
617
618            if let Some(compare_mode) = config.compare_mode.as_ref() {
619                write!(msg, " compare_mode={}", compare_mode).unwrap();
620            }
621
622            if let Some(pass_mode) = config.force_pass_mode.as_ref() {
623                write!(msg, " pass_mode={}", pass_mode).unwrap();
624            }
625
626            write!(msg, " mode={}", config.mode).unwrap();
627            write!(msg, " host={}", config.host).unwrap();
628            write!(msg, " target={}", config.target).unwrap();
629
630            println!("{msg}");
631
632            std::process::exit(1);
633        }
634        Err(e) => {
635            // We don't know if tests passed or not, but if there was an error
636            // during testing we don't want to just succeed (we may not have
637            // tested something), so fail.
638            //
639            // This should realistically "never" happen, so don't try to make
640            // this a pretty error message.
641            panic!("I/O failure during tests: {:?}", e);
642        }
643    }
644}
645
646/// Read-only context data used during test collection.
647struct TestCollectorCx {
648    config: Arc<Config>,
649    cache: DirectivesCache,
650    common_inputs_stamp: Stamp,
651    modified_tests: Vec<Utf8PathBuf>,
652}
653
654/// Mutable state used during test collection.
655struct TestCollector {
656    tests: Vec<CollectedTest>,
657    found_path_stems: HashSet<Utf8PathBuf>,
658    poisoned: bool,
659}
660
661impl TestCollector {
662    fn new() -> Self {
663        TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }
664    }
665
666    fn merge(&mut self, mut other: Self) {
667        self.tests.append(&mut other.tests);
668        self.found_path_stems.extend(other.found_path_stems);
669        self.poisoned |= other.poisoned;
670    }
671}
672
673/// Creates test structures for every test/revision in the test suite directory.
674///
675/// This always inspects _all_ test files in the suite (e.g. all 17k+ ui tests),
676/// regardless of whether any filters/tests were specified on the command-line,
677/// because filtering is handled later by libtest.
678pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
679    debug!("making tests from {}", config.src_test_suite_root);
680    let common_inputs_stamp = common_inputs_stamp(&config);
681    let modified_tests =
682        modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
683            fatal!("modified_tests: {}: {err}", config.src_test_suite_root);
684        });
685    let cache = DirectivesCache::load(&config);
686
687    let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests };
688    let collector = collect_tests_from_dir(&cx, &cx.config.src_test_suite_root, Utf8Path::new(""))
689        .unwrap_or_else(|reason| {
690            panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
691        });
692
693    let TestCollector { tests, found_path_stems, poisoned } = collector;
694
695    if poisoned {
696        eprintln!();
697        panic!("there are errors in tests");
698    }
699
700    check_for_overlapping_test_paths(&found_path_stems);
701
702    tests
703}
704
705/// Returns the most recent last-modified timestamp from among the input files
706/// that are considered relevant to all tests (e.g. the compiler, std, and
707/// compiletest itself).
708///
709/// (Some of these inputs aren't actually relevant to _all_ tests, but they are
710/// common to some subset of tests, and are hopefully unlikely to be modified
711/// while working on other tests.)
712fn common_inputs_stamp(config: &Config) -> Stamp {
713    let src_root = &config.src_root;
714
715    let mut stamp = Stamp::from_path(&config.rustc_path);
716
717    // Relevant pretty printer files
718    let pretty_printer_files = [
719        "src/etc/rust_types.py",
720        "src/etc/gdb_load_rust_pretty_printers.py",
721        "src/etc/gdb_lookup.py",
722        "src/etc/gdb_providers.py",
723        "src/etc/lldb_batchmode.py",
724        "src/etc/lldb_lookup.py",
725        "src/etc/lldb_providers.py",
726    ];
727    for file in &pretty_printer_files {
728        let path = src_root.join(file);
729        stamp.add_path(&path);
730    }
731
732    stamp.add_dir(&src_root.join("src/etc/natvis"));
733
734    stamp.add_dir(&config.run_lib_path);
735
736    if let Some(ref rustdoc_path) = config.rustdoc_path {
737        stamp.add_path(&rustdoc_path);
738        stamp.add_path(&src_root.join("src/etc/htmldocck.py"));
739    }
740
741    // Re-run coverage tests if the `coverage-dump` tool was modified,
742    // because its output format might have changed.
743    if let Some(coverage_dump_path) = &config.coverage_dump_path {
744        stamp.add_path(coverage_dump_path)
745    }
746
747    stamp.add_dir(&src_root.join("src/tools/run-make-support"));
748
749    // Compiletest itself.
750    stamp.add_dir(&src_root.join("src/tools/compiletest"));
751
752    stamp
753}
754
755/// Returns a list of modified/untracked test files that should be run when
756/// the `--only-modified` flag is in use.
757///
758/// (Might be inaccurate in some cases.)
759fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
760    // If `--only-modified` wasn't passed, the list of modified tests won't be
761    // used for anything, so avoid some work and just return an empty list.
762    if !config.only_modified {
763        return Ok(vec![]);
764    }
765
766    let files = get_git_modified_files(
767        &config.git_config(),
768        Some(dir.as_std_path()),
769        &vec!["rs", "stderr", "fixed"],
770    )?;
771    // Add new test cases to the list, it will be convenient in daily development.
772    let untracked_files = get_git_untracked_files(Some(dir.as_std_path()))?.unwrap_or(vec![]);
773
774    let all_paths = [&files[..], &untracked_files[..]].concat();
775    let full_paths = {
776        let mut full_paths: Vec<Utf8PathBuf> = all_paths
777            .into_iter()
778            .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
779            .filter_map(
780                |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
781            )
782            .collect();
783        full_paths.dedup();
784        full_paths.sort_unstable();
785        full_paths
786    };
787    Ok(full_paths)
788}
789
790/// Recursively scans a directory to find test files and create test structures
791/// that will be handed over to libtest.
792fn collect_tests_from_dir(
793    cx: &TestCollectorCx,
794    dir: &Utf8Path,
795    relative_dir_path: &Utf8Path,
796) -> io::Result<TestCollector> {
797    // Ignore directories that contain a file named `compiletest-ignore-dir`.
798    if dir.join("compiletest-ignore-dir").exists() {
799        return Ok(TestCollector::new());
800    }
801
802    let mut components = dir.components().rev();
803    if let Some(Utf8Component::Normal(last)) = components.next()
804        && let Some(("assembly" | "codegen", backend)) = last.split_once('-')
805        && let Some(Utf8Component::Normal(parent)) = components.next()
806        && parent == "tests"
807        && let Ok(backend) = CodegenBackend::try_from(backend)
808        && backend != cx.config.codegen_backend
809    {
810        // We ignore asm tests which don't match the current codegen backend.
811        warning!(
812            "Ignoring tests in `{dir}` because they don't match the configured codegen \
813             backend (`{}`)",
814            cx.config.codegen_backend.as_str(),
815        );
816        return Ok(TestCollector::new());
817    }
818
819    // For run-make tests, a "test file" is actually a directory that contains an `rmake.rs`.
820    if cx.config.mode == TestMode::RunMake {
821        let mut collector = TestCollector::new();
822        if dir.join("rmake.rs").exists() {
823            let paths = TestPaths {
824                file: dir.to_path_buf(),
825                relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
826            };
827            make_test(cx, &mut collector, &paths);
828            // This directory is a test, so don't try to find other tests inside it.
829            return Ok(collector);
830        }
831    }
832
833    // If we find a test foo/bar.rs, we have to build the
834    // output directory `$build/foo` so we can write
835    // `$build/foo/bar` into it. We do this *now* in this
836    // sequential loop because otherwise, if we do it in the
837    // tests themselves, they race for the privilege of
838    // creating the directories and sometimes fail randomly.
839    let build_dir = output_relative_path(&cx.config, relative_dir_path);
840    fs::create_dir_all(&build_dir).unwrap();
841
842    // Add each `.rs` file as a test, and recurse further on any
843    // subdirectories we find, except for `auxiliary` directories.
844    // FIXME: this walks full tests tree, even if we have something to ignore
845    // use walkdir/ignore like in tidy?
846    fs::read_dir(dir.as_std_path())?
847        .par_bridge()
848        .map(|file| {
849            let mut collector = TestCollector::new();
850            let file = file?;
851            let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
852            let file_name = file_path.file_name().unwrap();
853
854            if is_test(file_name)
855                && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
856            {
857                // We found a test file, so create the corresponding libtest structures.
858                debug!(%file_path, "found test file");
859
860                // Record the stem of the test file, to check for overlaps later.
861                let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
862                collector.found_path_stems.insert(rel_test_path);
863
864                let paths =
865                    TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
866                make_test(cx, &mut collector, &paths);
867            } else if file_path.is_dir() {
868                // Recurse to find more tests in a subdirectory.
869                let relative_file_path = relative_dir_path.join(file_name);
870                if file_name != "auxiliary" {
871                    debug!(%file_path, "found directory");
872                    collector.merge(collect_tests_from_dir(cx, &file_path, &relative_file_path)?);
873                }
874            } else {
875                debug!(%file_path, "found other file/directory");
876            }
877            Ok(collector)
878        })
879        .reduce(
880            || Ok(TestCollector::new()),
881            |a, b| {
882                let mut a = a?;
883                a.merge(b?);
884                Ok(a)
885            },
886        )
887}
888
889/// Returns true if `file_name` looks like a proper test file name.
890pub fn is_test(file_name: &str) -> bool {
891    if !file_name.ends_with(".rs") {
892        return false;
893    }
894
895    // `.`, `#`, and `~` are common temp-file prefixes.
896    let invalid_prefixes = &[".", "#", "~"];
897    !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
898}
899
900/// For a single test file, creates one or more test structures (one per revision) that can be
901/// handed over to libtest to run, possibly in parallel.
902fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
903    // For run-make tests, each "test file" is actually a _directory_ containing an `rmake.rs`. But
904    // for the purposes of directive parsing, we want to look at that recipe file, not the directory
905    // itself.
906    let test_path = if cx.config.mode == TestMode::RunMake {
907        testpaths.file.join("rmake.rs")
908    } else {
909        testpaths.file.clone()
910    };
911
912    // Scan the test file to discover its revisions, if any.
913    let early_props = EarlyProps::from_file(&cx.config, &test_path);
914
915    // Normally we create one libtest structure per revision, with two exceptions:
916    // - If a test doesn't use revisions, create a dummy revision (None) so that
917    //   the test can still run.
918    // - Incremental tests inherently can't run their revisions in parallel, so
919    //   we treat them like non-revisioned tests here. Incremental revisions are
920    //   handled internally by `runtest::run` instead.
921    let revisions = if early_props.revisions.is_empty() || cx.config.mode == TestMode::Incremental {
922        vec![None]
923    } else {
924        early_props.revisions.iter().map(|r| Some(r.as_str())).collect()
925    };
926
927    // For each revision (or the sole dummy revision), create and append a
928    // `CollectedTest` that can be handed over to the test executor.
929    collector.tests.extend(revisions.into_iter().map(|revision| {
930        // Create a test name and description to hand over to libtest.
931        let src_file = fs::File::open(&test_path).expect("open test file to parse ignores");
932        let test_name = make_test_name(&cx.config, testpaths, revision);
933        // Create a libtest description for the test/revision.
934        // This is where `ignore-*`/`only-*`/`needs-*` directives are handled,
935        // because they need to set the libtest ignored flag.
936        let mut desc = make_test_description(
937            &cx.config,
938            &cx.cache,
939            test_name,
940            &test_path,
941            src_file,
942            revision,
943            &mut collector.poisoned,
944        );
945
946        // If a test's inputs haven't changed since the last time it ran,
947        // mark it as ignored so that libtest will skip it.
948        if !cx.config.force_rerun && is_up_to_date(cx, testpaths, &early_props, revision) {
949            desc.ignore = true;
950            // Keep this in sync with the "up-to-date" message detected by bootstrap.
951            desc.ignore_message = Some("up-to-date".into());
952        }
953
954        let config = Arc::clone(&cx.config);
955        let testpaths = testpaths.clone();
956        let revision = revision.map(str::to_owned);
957
958        CollectedTest { desc, config, testpaths, revision }
959    }));
960}
961
962/// The path of the `stamp` file that gets created or updated whenever a
963/// particular test completes successfully.
964fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
965    output_base_dir(config, testpaths, revision).join("stamp")
966}
967
968/// Returns a list of files that, if modified, would cause this test to no
969/// longer be up-to-date.
970///
971/// (Might be inaccurate in some cases.)
972fn files_related_to_test(
973    config: &Config,
974    testpaths: &TestPaths,
975    props: &EarlyProps,
976    revision: Option<&str>,
977) -> Vec<Utf8PathBuf> {
978    let mut related = vec![];
979
980    if testpaths.file.is_dir() {
981        // run-make tests use their individual directory
982        for entry in WalkDir::new(&testpaths.file) {
983            let path = entry.unwrap().into_path();
984            if path.is_file() {
985                related.push(Utf8PathBuf::try_from(path).unwrap());
986            }
987        }
988    } else {
989        related.push(testpaths.file.clone());
990    }
991
992    for aux in props.aux.all_aux_path_strings() {
993        // FIXME(Zalathar): Perform all `auxiliary` path resolution in one place.
994        let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
995        related.push(path);
996    }
997
998    // UI test files.
999    for extension in UI_EXTENSIONS {
1000        let path = expected_output_path(testpaths, revision, &config.compare_mode, extension);
1001        related.push(path);
1002    }
1003
1004    // `minicore.rs` test auxiliary: we need to make sure tests get rerun if this changes.
1005    related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs"));
1006
1007    related
1008}
1009
1010/// Checks whether a particular test/revision is "up-to-date", meaning that no
1011/// relevant files/settings have changed since the last time the test succeeded.
1012///
1013/// (This is not very reliable in some circumstances, so the `--force-rerun`
1014/// flag can be used to ignore up-to-date checking and always re-run tests.)
1015fn is_up_to_date(
1016    cx: &TestCollectorCx,
1017    testpaths: &TestPaths,
1018    props: &EarlyProps,
1019    revision: Option<&str>,
1020) -> bool {
1021    let stamp_file_path = stamp_file_path(&cx.config, testpaths, revision);
1022    // Check the config hash inside the stamp file.
1023    let contents = match fs::read_to_string(&stamp_file_path) {
1024        Ok(f) => f,
1025        Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"),
1026        // The test hasn't succeeded yet, so it is not up-to-date.
1027        Err(_) => return false,
1028    };
1029    let expected_hash = runtest::compute_stamp_hash(&cx.config);
1030    if contents != expected_hash {
1031        // Some part of compiletest configuration has changed since the test
1032        // last succeeded, so it is not up-to-date.
1033        return false;
1034    }
1035
1036    // Check the timestamp of the stamp file against the last modified time
1037    // of all files known to be relevant to the test.
1038    let mut inputs_stamp = cx.common_inputs_stamp.clone();
1039    for path in files_related_to_test(&cx.config, testpaths, props, revision) {
1040        inputs_stamp.add_path(&path);
1041    }
1042
1043    // If no relevant files have been modified since the stamp file was last
1044    // written, the test is up-to-date.
1045    inputs_stamp < Stamp::from_path(&stamp_file_path)
1046}
1047
1048/// The maximum of a set of file-modified timestamps.
1049#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1050struct Stamp {
1051    time: SystemTime,
1052}
1053
1054impl Stamp {
1055    /// Creates a timestamp holding the last-modified time of the specified file.
1056    fn from_path(path: &Utf8Path) -> Self {
1057        let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
1058        stamp.add_path(path);
1059        stamp
1060    }
1061
1062    /// Updates this timestamp to the last-modified time of the specified file,
1063    /// if it is later than the currently-stored timestamp.
1064    fn add_path(&mut self, path: &Utf8Path) {
1065        let modified = fs::metadata(path.as_std_path())
1066            .and_then(|metadata| metadata.modified())
1067            .unwrap_or(SystemTime::UNIX_EPOCH);
1068        self.time = self.time.max(modified);
1069    }
1070
1071    /// Updates this timestamp to the most recent last-modified time of all files
1072    /// recursively contained in the given directory, if it is later than the
1073    /// currently-stored timestamp.
1074    fn add_dir(&mut self, path: &Utf8Path) {
1075        let path = path.as_std_path();
1076        for entry in WalkDir::new(path) {
1077            let entry = entry.unwrap();
1078            if entry.file_type().is_file() {
1079                let modified = entry
1080                    .metadata()
1081                    .ok()
1082                    .and_then(|metadata| metadata.modified().ok())
1083                    .unwrap_or(SystemTime::UNIX_EPOCH);
1084                self.time = self.time.max(modified);
1085            }
1086        }
1087    }
1088}
1089
1090/// Creates a name for this test/revision that can be handed over to libtest.
1091fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> String {
1092    // Print the name of the file, relative to the sources root.
1093    let path = testpaths.file.strip_prefix(&config.src_root).unwrap();
1094    let debugger = match config.debugger {
1095        Some(d) => format!("-{}", d),
1096        None => String::new(),
1097    };
1098    let mode_suffix = match config.compare_mode {
1099        Some(ref mode) => format!(" ({})", mode.to_str()),
1100        None => String::new(),
1101    };
1102
1103    format!(
1104        "[{}{}{}] {}{}",
1105        config.mode,
1106        debugger,
1107        mode_suffix,
1108        path,
1109        revision.map_or("".to_string(), |rev| format!("#{}", rev))
1110    )
1111}
1112
1113/// Checks that test discovery didn't find any tests whose name stem is a prefix
1114/// of some other tests's name.
1115///
1116/// For example, suppose the test suite contains these two test files:
1117/// - `tests/rustdoc/primitive.rs`
1118/// - `tests/rustdoc/primitive/no_std.rs`
1119///
1120/// The test runner might put the output from those tests in these directories:
1121/// - `$build/test/rustdoc/primitive/`
1122/// - `$build/test/rustdoc/primitive/no_std/`
1123///
1124/// Because one output path is a subdirectory of the other, the two tests might
1125/// interfere with each other in unwanted ways, especially if the test runner
1126/// decides to delete test output directories to clean them between runs.
1127/// To avoid problems, we forbid test names from overlapping in this way.
1128///
1129/// See <https://github.com/rust-lang/rust/pull/109509> for more context.
1130fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
1131    let mut collisions = Vec::new();
1132    for path in found_path_stems {
1133        for ancestor in path.ancestors().skip(1) {
1134            if found_path_stems.contains(ancestor) {
1135                collisions.push((path, ancestor));
1136            }
1137        }
1138    }
1139    if !collisions.is_empty() {
1140        collisions.sort();
1141        let collisions: String = collisions
1142            .into_iter()
1143            .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
1144            .collect();
1145        panic!(
1146            "{collisions}\n\
1147            Tests cannot have overlapping names. Make sure they use unique prefixes."
1148        );
1149    }
1150}
1151
1152pub fn early_config_check(config: &Config) {
1153    if !config.has_html_tidy && config.mode == TestMode::Rustdoc {
1154        warning!("`tidy` (html-tidy.org) is not installed; diffs will not be generated");
1155    }
1156
1157    if !config.profiler_runtime && config.mode == TestMode::CoverageRun {
1158        let actioned = if config.bless { "blessed" } else { "checked" };
1159        warning!("profiler runtime is not available, so `.coverage` files won't be {actioned}");
1160        help!("try setting `profiler = true` in the `[build]` section of `bootstrap.toml`");
1161    }
1162
1163    // `RUST_TEST_NOCAPTURE` is a libtest env var, but we don't callout to libtest.
1164    if env::var("RUST_TEST_NOCAPTURE").is_ok() {
1165        warning!("`RUST_TEST_NOCAPTURE` is not supported; use the `--no-capture` flag instead");
1166    }
1167}