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