compiletest/
lib.rs

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