Skip to main content

compiletest/
lib.rs

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