compiletest/
lib.rs

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