Skip to main content

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