compiletest/
lib.rs

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