compiletest/
lib.rs

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