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