compiletest/
lib.rs

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