compiletest/
lib.rs

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