compiletest/
lib.rs

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