compiletest/
directives.rs

1use std::collections::HashSet;
2use std::process::Command;
3use std::{env, fs};
4
5use camino::{Utf8Path, Utf8PathBuf};
6use semver::Version;
7use tracing::*;
8
9use crate::common::{CodegenBackend, Config, Debugger, FailMode, PassMode, RunFailMode, TestMode};
10use crate::debuggers::{extract_cdb_version, extract_gdb_version};
11pub(crate) use crate::directives::auxiliary::AuxProps;
12use crate::directives::auxiliary::parse_and_update_aux;
13use crate::directives::directive_names::{
14    KNOWN_DIRECTIVE_NAMES_SET, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES,
15};
16pub(crate) use crate::directives::file::FileDirectives;
17use crate::directives::handlers::DIRECTIVE_HANDLERS_MAP;
18use crate::directives::line::{DirectiveLine, line_directive};
19use crate::directives::needs::CachedNeedsConditions;
20use crate::edition::{Edition, parse_edition};
21use crate::errors::ErrorKind;
22use crate::executor::{CollectedTestDesc, ShouldFail};
23use crate::util::static_regex;
24use crate::{fatal, help};
25
26mod auxiliary;
27mod cfg;
28mod directive_names;
29mod file;
30mod handlers;
31mod line;
32mod needs;
33#[cfg(test)]
34mod tests;
35
36pub struct DirectivesCache {
37    needs: CachedNeedsConditions,
38}
39
40impl DirectivesCache {
41    pub fn load(config: &Config) -> Self {
42        Self { needs: CachedNeedsConditions::load(config) }
43    }
44}
45
46/// Properties which must be known very early, before actually running
47/// the test.
48#[derive(Default)]
49pub(crate) struct EarlyProps {
50    pub(crate) revisions: Vec<String>,
51}
52
53impl EarlyProps {
54    pub(crate) fn from_file_directives(
55        config: &Config,
56        file_directives: &FileDirectives<'_>,
57    ) -> Self {
58        let mut props = EarlyProps::default();
59
60        iter_directives(
61            config.mode,
62            file_directives,
63            // (dummy comment to force args into vertical layout)
64            &mut |ln: &DirectiveLine<'_>| {
65                config.parse_and_update_revisions(ln, &mut props.revisions);
66            },
67        );
68
69        props
70    }
71}
72
73#[derive(Clone, Debug)]
74pub(crate) struct TestProps {
75    // Lines that should be expected, in order, on standard out
76    pub error_patterns: Vec<String>,
77    // Regexes that should be expected, in order, on standard out
78    pub regex_error_patterns: Vec<String>,
79    /// Edition selected by an `//@ edition` directive, if any.
80    ///
81    /// Automatically added to `compile_flags` during directive processing.
82    pub edition: Option<Edition>,
83    // Extra flags to pass to the compiler
84    pub compile_flags: Vec<String>,
85    // Extra flags to pass when the compiled code is run (such as --bench)
86    pub run_flags: Vec<String>,
87    /// Extra flags to pass to rustdoc but not the compiler.
88    pub doc_flags: Vec<String>,
89    // If present, the name of a file that this test should match when
90    // pretty-printed
91    pub pp_exact: Option<Utf8PathBuf>,
92    /// Auxiliary crates that should be built and made available to this test.
93    pub(crate) aux: AuxProps,
94    // Environment settings to use for compiling
95    pub rustc_env: Vec<(String, String)>,
96    // Environment variables to unset prior to compiling.
97    // Variables are unset before applying 'rustc_env'.
98    pub unset_rustc_env: Vec<String>,
99    // Environment settings to use during execution
100    pub exec_env: Vec<(String, String)>,
101    // Environment variables to unset prior to execution.
102    // Variables are unset before applying 'exec_env'
103    pub unset_exec_env: Vec<String>,
104    // Build documentation for all specified aux-builds as well
105    pub build_aux_docs: bool,
106    /// Build the documentation for each crate in a unique output directory.
107    /// Uses `<root output directory>/docs/<test name>/doc`.
108    pub unique_doc_out_dir: bool,
109    // Flag to force a crate to be built with the host architecture
110    pub force_host: bool,
111    // Check stdout for error-pattern output as well as stderr
112    pub check_stdout: bool,
113    // Check stdout & stderr for output of run-pass test
114    pub check_run_results: bool,
115    // For UI tests, allows compiler to generate arbitrary output to stdout
116    pub dont_check_compiler_stdout: bool,
117    // For UI tests, allows compiler to generate arbitrary output to stderr
118    pub dont_check_compiler_stderr: bool,
119    // Don't force a --crate-type=dylib flag on the command line
120    //
121    // Set this for example if you have an auxiliary test file that contains
122    // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures
123    // that the aux file is compiled as a `proc-macro` and not as a `dylib`.
124    pub no_prefer_dynamic: bool,
125    // Which pretty mode are we testing with, default to 'normal'
126    pub pretty_mode: String,
127    // Only compare pretty output and don't try compiling
128    pub pretty_compare_only: bool,
129    // Patterns which must not appear in the output of a cfail test.
130    pub forbid_output: Vec<String>,
131    // Revisions to test for incremental compilation.
132    pub revisions: Vec<String>,
133    // Directory (if any) to use for incremental compilation.  This is
134    // not set by end-users; rather it is set by the incremental
135    // testing harness and used when generating compilation
136    // arguments. (In particular, it propagates to the aux-builds.)
137    pub incremental_dir: Option<Utf8PathBuf>,
138    // If `true`, this test will use incremental compilation.
139    //
140    // This can be set manually with the `incremental` directive, or implicitly
141    // by being a part of an incremental mode test. Using the `incremental`
142    // directive should be avoided if possible; using an incremental mode test is
143    // preferred. Incremental mode tests support multiple passes, which can
144    // verify that the incremental cache can be loaded properly after being
145    // created. Just setting the directive will only verify the behavior with
146    // creating an incremental cache, but doesn't check that it is created
147    // correctly.
148    //
149    // Compiletest will create the incremental directory, and ensure it is
150    // empty before the test starts. Incremental mode tests will reuse the
151    // incremental directory between passes in the same test.
152    pub incremental: bool,
153    // If `true`, this test is a known bug.
154    //
155    // When set, some requirements are relaxed. Currently, this only means no
156    // error annotations are needed, but this may be updated in the future to
157    // include other relaxations.
158    pub known_bug: bool,
159    // How far should the test proceed while still passing.
160    pass_mode: Option<PassMode>,
161    // Ignore `--pass` overrides from the command line for this test.
162    ignore_pass: bool,
163    // How far this test should proceed to start failing.
164    pub fail_mode: Option<FailMode>,
165    // rustdoc will test the output of the `--test` option
166    pub check_test_line_numbers_match: bool,
167    // customized normalization rules
168    pub normalize_stdout: Vec<(String, String)>,
169    pub normalize_stderr: Vec<(String, String)>,
170    pub failure_status: Option<i32>,
171    // For UI tests, allows compiler to exit with arbitrary failure status
172    pub dont_check_failure_status: bool,
173    // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the
174    // resulting Rust code.
175    pub run_rustfix: bool,
176    // If true, `rustfix` will only apply `MachineApplicable` suggestions.
177    pub rustfix_only_machine_applicable: bool,
178    pub assembly_output: Option<String>,
179    // If true, the test is expected to ICE
180    pub should_ice: bool,
181    // If true, the stderr is expected to be different across bit-widths.
182    pub stderr_per_bitwidth: bool,
183    // The MIR opt to unit test, if any
184    pub mir_unit_test: Option<String>,
185    // Whether to tell `rustc` to remap the "src base" directory to a fake
186    // directory.
187    pub remap_src_base: bool,
188    /// Extra flags to pass to `llvm-cov` when producing coverage reports.
189    /// Only used by the "coverage-run" test mode.
190    pub llvm_cov_flags: Vec<String>,
191    /// Extra flags to pass to LLVM's `filecheck` tool, in tests that use it.
192    pub filecheck_flags: Vec<String>,
193    /// Don't automatically insert any `--check-cfg` args
194    pub no_auto_check_cfg: bool,
195    /// Build and use `minicore` as `core` stub for `no_core` tests in cross-compilation scenarios
196    /// that don't otherwise want/need `-Z build-std`.
197    pub add_minicore: bool,
198    /// Add these flags to the build of `minicore`.
199    pub minicore_compile_flags: Vec<String>,
200    /// Whether line annotatins are required for the given error kind.
201    pub dont_require_annotations: HashSet<ErrorKind>,
202    /// Whether pretty printers should be disabled in gdb.
203    pub disable_gdb_pretty_printers: bool,
204    /// Compare the output by lines, rather than as a single string.
205    pub compare_output_by_lines: bool,
206}
207
208mod directives {
209    pub const ERROR_PATTERN: &'static str = "error-pattern";
210    pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
211    pub const COMPILE_FLAGS: &'static str = "compile-flags";
212    pub const RUN_FLAGS: &'static str = "run-flags";
213    pub const DOC_FLAGS: &'static str = "doc-flags";
214    pub const SHOULD_ICE: &'static str = "should-ice";
215    pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
216    pub const UNIQUE_DOC_OUT_DIR: &'static str = "unique-doc-out-dir";
217    pub const FORCE_HOST: &'static str = "force-host";
218    pub const CHECK_STDOUT: &'static str = "check-stdout";
219    pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
220    pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout";
221    pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr";
222    pub const DONT_REQUIRE_ANNOTATIONS: &'static str = "dont-require-annotations";
223    pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic";
224    pub const PRETTY_MODE: &'static str = "pretty-mode";
225    pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only";
226    pub const AUX_BIN: &'static str = "aux-bin";
227    pub const AUX_BUILD: &'static str = "aux-build";
228    pub const AUX_CRATE: &'static str = "aux-crate";
229    pub const PROC_MACRO: &'static str = "proc-macro";
230    pub const AUX_CODEGEN_BACKEND: &'static str = "aux-codegen-backend";
231    pub const EXEC_ENV: &'static str = "exec-env";
232    pub const RUSTC_ENV: &'static str = "rustc-env";
233    pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env";
234    pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env";
235    pub const FORBID_OUTPUT: &'static str = "forbid-output";
236    pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match";
237    pub const IGNORE_PASS: &'static str = "ignore-pass";
238    pub const FAILURE_STATUS: &'static str = "failure-status";
239    pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status";
240    pub const RUN_RUSTFIX: &'static str = "run-rustfix";
241    pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable";
242    pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output";
243    pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth";
244    pub const INCREMENTAL: &'static str = "incremental";
245    pub const KNOWN_BUG: &'static str = "known-bug";
246    pub const TEST_MIR_PASS: &'static str = "test-mir-pass";
247    pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
248    pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags";
249    pub const FILECHECK_FLAGS: &'static str = "filecheck-flags";
250    pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg";
251    pub const ADD_MINICORE: &'static str = "add-minicore";
252    pub const MINICORE_COMPILE_FLAGS: &'static str = "minicore-compile-flags";
253    pub const DISABLE_GDB_PRETTY_PRINTERS: &'static str = "disable-gdb-pretty-printers";
254    pub const COMPARE_OUTPUT_BY_LINES: &'static str = "compare-output-by-lines";
255}
256
257impl TestProps {
258    pub fn new() -> Self {
259        TestProps {
260            error_patterns: vec![],
261            regex_error_patterns: vec![],
262            edition: None,
263            compile_flags: vec![],
264            run_flags: vec![],
265            doc_flags: vec![],
266            pp_exact: None,
267            aux: Default::default(),
268            revisions: vec![],
269            rustc_env: vec![
270                ("RUSTC_ICE".to_string(), "0".to_string()),
271                ("RUST_BACKTRACE".to_string(), "short".to_string()),
272            ],
273            unset_rustc_env: vec![("RUSTC_LOG_COLOR".to_string())],
274            exec_env: vec![],
275            unset_exec_env: vec![],
276            build_aux_docs: false,
277            unique_doc_out_dir: false,
278            force_host: false,
279            check_stdout: false,
280            check_run_results: false,
281            dont_check_compiler_stdout: false,
282            dont_check_compiler_stderr: false,
283            no_prefer_dynamic: false,
284            pretty_mode: "normal".to_string(),
285            pretty_compare_only: false,
286            forbid_output: vec![],
287            incremental_dir: None,
288            incremental: false,
289            known_bug: false,
290            pass_mode: None,
291            fail_mode: None,
292            ignore_pass: false,
293            check_test_line_numbers_match: false,
294            normalize_stdout: vec![],
295            normalize_stderr: vec![],
296            failure_status: None,
297            dont_check_failure_status: false,
298            run_rustfix: false,
299            rustfix_only_machine_applicable: false,
300            assembly_output: None,
301            should_ice: false,
302            stderr_per_bitwidth: false,
303            mir_unit_test: None,
304            remap_src_base: false,
305            llvm_cov_flags: vec![],
306            filecheck_flags: vec![],
307            no_auto_check_cfg: false,
308            add_minicore: false,
309            minicore_compile_flags: vec![],
310            dont_require_annotations: Default::default(),
311            disable_gdb_pretty_printers: false,
312            compare_output_by_lines: false,
313        }
314    }
315
316    pub fn from_aux_file(
317        &self,
318        testfile: &Utf8Path,
319        revision: Option<&str>,
320        config: &Config,
321    ) -> Self {
322        let mut props = TestProps::new();
323
324        // copy over select properties to the aux build:
325        props.incremental_dir = self.incremental_dir.clone();
326        props.ignore_pass = true;
327        props.load_from(testfile, revision, config);
328
329        props
330    }
331
332    pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
333        let mut props = TestProps::new();
334        props.load_from(testfile, revision, config);
335        props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
336
337        match (props.pass_mode, props.fail_mode) {
338            (None, None) if config.mode == TestMode::Ui => props.fail_mode = Some(FailMode::Check),
339            (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"),
340            _ => {}
341        }
342
343        props
344    }
345
346    /// Loads properties from `testfile` into `props`. If a property is
347    /// tied to a particular revision `foo` (indicated by writing
348    /// `//@[foo]`), then the property is ignored unless `test_revision` is
349    /// `Some("foo")`.
350    fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
351        if !testfile.is_dir() {
352            let file_contents = fs::read_to_string(testfile).unwrap();
353            let file_directives = FileDirectives::from_file_contents(testfile, &file_contents);
354
355            iter_directives(
356                config.mode,
357                &file_directives,
358                // (dummy comment to force args into vertical layout)
359                &mut |ln: &DirectiveLine<'_>| {
360                    if !ln.applies_to_test_revision(test_revision) {
361                        return;
362                    }
363
364                    if let Some(handler) = DIRECTIVE_HANDLERS_MAP.get(ln.name) {
365                        handler.handle(config, ln, self);
366                    }
367                },
368            );
369        }
370
371        if self.should_ice {
372            self.failure_status = Some(101);
373        }
374
375        if config.mode == TestMode::Incremental {
376            self.incremental = true;
377        }
378
379        if config.mode == TestMode::Crashes {
380            // we don't want to pollute anything with backtrace-files
381            // also turn off backtraces in order to save some execution
382            // time on the tests; we only need to know IF it crashes
383            self.rustc_env = vec![
384                ("RUST_BACKTRACE".to_string(), "0".to_string()),
385                ("RUSTC_ICE".to_string(), "0".to_string()),
386            ];
387        }
388
389        for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
390            if let Ok(val) = env::var(key) {
391                if !self.exec_env.iter().any(|&(ref x, _)| x == key) {
392                    self.exec_env.push(((*key).to_owned(), val))
393                }
394            }
395        }
396
397        if let Some(edition) = self.edition.or(config.edition) {
398            // The edition is added at the start, since flags from //@compile-flags must be passed
399            // to rustc last.
400            self.compile_flags.insert(0, format!("--edition={edition}"));
401        }
402    }
403
404    fn update_fail_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
405        let check_ui = |mode: &str| {
406            // Mode::Crashes may need build-fail in order to trigger llvm errors or stack overflows
407            if config.mode != TestMode::Ui && config.mode != TestMode::Crashes {
408                panic!("`{}-fail` directive is only supported in UI tests", mode);
409            }
410        };
411        let fail_mode = if config.parse_name_directive(ln, "check-fail") {
412            check_ui("check");
413            Some(FailMode::Check)
414        } else if config.parse_name_directive(ln, "build-fail") {
415            check_ui("build");
416            Some(FailMode::Build)
417        } else if config.parse_name_directive(ln, "run-fail") {
418            check_ui("run");
419            Some(FailMode::Run(RunFailMode::Fail))
420        } else if config.parse_name_directive(ln, "run-crash") {
421            check_ui("run");
422            Some(FailMode::Run(RunFailMode::Crash))
423        } else if config.parse_name_directive(ln, "run-fail-or-crash") {
424            check_ui("run");
425            Some(FailMode::Run(RunFailMode::FailOrCrash))
426        } else {
427            None
428        };
429        match (self.fail_mode, fail_mode) {
430            (None, Some(_)) => self.fail_mode = fail_mode,
431            (Some(_), Some(_)) => panic!("multiple `*-fail` directives in a single test"),
432            (_, None) => {}
433        }
434    }
435
436    fn update_pass_mode(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
437        let check_no_run = |s| match (config.mode, s) {
438            (TestMode::Ui, _) => (),
439            (TestMode::Crashes, _) => (),
440            (TestMode::Codegen, "build-pass") => (),
441            (TestMode::Incremental, _) => {
442                // FIXME(Zalathar): This only detects forbidden directives that are
443                // declared _after_ the incompatible `//@ revisions:` directive(s).
444                if self.revisions.iter().any(|r| !r.starts_with("cfail")) {
445                    panic!("`{s}` directive is only supported in `cfail` incremental tests")
446                }
447            }
448            (mode, _) => panic!("`{s}` directive is not supported in `{mode}` tests"),
449        };
450        let pass_mode = if config.parse_name_directive(ln, "check-pass") {
451            check_no_run("check-pass");
452            Some(PassMode::Check)
453        } else if config.parse_name_directive(ln, "build-pass") {
454            check_no_run("build-pass");
455            Some(PassMode::Build)
456        } else if config.parse_name_directive(ln, "run-pass") {
457            check_no_run("run-pass");
458            Some(PassMode::Run)
459        } else {
460            None
461        };
462        match (self.pass_mode, pass_mode) {
463            (None, Some(_)) => self.pass_mode = pass_mode,
464            (Some(_), Some(_)) => panic!("multiple `*-pass` directives in a single test"),
465            (_, None) => {}
466        }
467    }
468
469    pub fn pass_mode(&self, config: &Config) -> Option<PassMode> {
470        if !self.ignore_pass && self.fail_mode.is_none() {
471            if let mode @ Some(_) = config.force_pass_mode {
472                return mode;
473            }
474        }
475        self.pass_mode
476    }
477
478    // does not consider CLI override for pass mode
479    pub fn local_pass_mode(&self) -> Option<PassMode> {
480        self.pass_mode
481    }
482
483    fn update_add_minicore(&mut self, ln: &DirectiveLine<'_>, config: &Config) {
484        let add_minicore = config.parse_name_directive(ln, directives::ADD_MINICORE);
485        if add_minicore {
486            if !matches!(config.mode, TestMode::Ui | TestMode::Codegen | TestMode::Assembly) {
487                panic!(
488                    "`add-minicore` is currently only supported for ui, codegen and assembly test modes"
489                );
490            }
491
492            // FIXME(jieyouxu): this check is currently order-dependent, but we should probably
493            // collect all directives in one go then perform a validation pass after that.
494            if self.local_pass_mode().is_some_and(|pm| pm == PassMode::Run) {
495                // `minicore` can only be used with non-run modes, because it's `core` prelude stubs
496                // and can't run.
497                panic!("`add-minicore` cannot be used to run the test binary");
498            }
499
500            self.add_minicore = add_minicore;
501        }
502    }
503}
504
505pub(crate) fn do_early_directives_check(
506    mode: TestMode,
507    file_directives: &FileDirectives<'_>,
508) -> Result<(), String> {
509    let testfile = file_directives.path;
510
511    for directive_line @ DirectiveLine { line_number, .. } in &file_directives.lines {
512        let CheckDirectiveResult { is_known_directive, trailing_directive } =
513            check_directive(directive_line, mode);
514
515        if !is_known_directive {
516            return Err(format!(
517                "ERROR: unknown compiletest directive `{directive}` at {testfile}:{line_number}",
518                directive = directive_line.display(),
519            ));
520        }
521
522        if let Some(trailing_directive) = &trailing_directive {
523            return Err(format!(
524                "ERROR: detected trailing compiletest directive `{trailing_directive}` at {testfile}:{line_number}\n\
525                HELP: put the directive on its own line: `//@ {trailing_directive}`"
526            ));
527        }
528    }
529
530    Ok(())
531}
532
533pub(crate) struct CheckDirectiveResult<'ln> {
534    is_known_directive: bool,
535    trailing_directive: Option<&'ln str>,
536}
537
538fn check_directive<'a>(
539    directive_ln: &DirectiveLine<'a>,
540    mode: TestMode,
541) -> CheckDirectiveResult<'a> {
542    let &DirectiveLine { name: directive_name, .. } = directive_ln;
543
544    let is_known_directive = KNOWN_DIRECTIVE_NAMES_SET.contains(&directive_name)
545        || match mode {
546            TestMode::Rustdoc => KNOWN_HTMLDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
547            TestMode::RustdocJson => KNOWN_JSONDOCCK_DIRECTIVE_NAMES.contains(&directive_name),
548            _ => false,
549        };
550
551    // If it looks like the user tried to put two directives on the same line
552    // (e.g. `//@ only-linux only-x86_64`), signal an error, because the
553    // second "directive" would actually be ignored with no effect.
554    let trailing_directive = directive_ln
555        .remark_after_space()
556        .map(|remark| remark.trim_start().split(' ').next().unwrap())
557        .filter(|token| KNOWN_DIRECTIVE_NAMES_SET.contains(token));
558
559    // FIXME(Zalathar): Consider emitting specialized error/help messages for
560    // bogus directive names that are similar to real ones, e.g.:
561    // - *`compiler-flags` => `compile-flags`
562    // - *`compile-fail` => `check-fail` or `build-fail`
563
564    CheckDirectiveResult { is_known_directive, trailing_directive }
565}
566
567fn iter_directives(
568    mode: TestMode,
569    file_directives: &FileDirectives<'_>,
570    it: &mut dyn FnMut(&DirectiveLine<'_>),
571) {
572    let testfile = file_directives.path;
573
574    // Coverage tests in coverage-run mode always have these extra directives, without needing to
575    // specify them manually in every test file.
576    //
577    // FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
578    if mode == TestMode::CoverageRun {
579        let extra_directives: &[&str] = &[
580            "//@ needs-profiler-runtime",
581            // FIXME(pietroalbini): this test currently does not work on cross-compiled targets
582            // because remote-test is not capable of sending back the *.profraw files generated by
583            // the LLVM instrumentation.
584            "//@ ignore-cross-compile",
585        ];
586        // Process the extra implied directives, with a dummy line number of 0.
587        for directive_str in extra_directives {
588            let directive_line = line_directive(testfile, 0, directive_str)
589                .unwrap_or_else(|| panic!("bad extra-directive line: {directive_str:?}"));
590            it(&directive_line);
591        }
592    }
593
594    for directive_line in &file_directives.lines {
595        it(directive_line);
596    }
597}
598
599impl Config {
600    fn parse_and_update_revisions(&self, line: &DirectiveLine<'_>, existing: &mut Vec<String>) {
601        const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
602            // `//@ revisions: true false` Implying `--cfg=true` and `--cfg=false` makes it very
603            // weird for the test, since if the test writer wants a cfg of the same revision name
604            // they'd have to use `cfg(r#true)` and `cfg(r#false)`.
605            "true", "false",
606        ];
607
608        const FILECHECK_FORBIDDEN_REVISION_NAMES: [&str; 9] =
609            ["CHECK", "COM", "NEXT", "SAME", "EMPTY", "NOT", "COUNT", "DAG", "LABEL"];
610
611        if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
612            let &DirectiveLine { file_path: testfile, .. } = line;
613
614            if self.mode == TestMode::RunMake {
615                panic!("`run-make` mode tests do not support revisions: {}", testfile);
616            }
617
618            let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
619            for revision in raw.split_whitespace() {
620                if !duplicates.insert(revision.to_string()) {
621                    panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
622                }
623
624                if FORBIDDEN_REVISION_NAMES.contains(&revision) {
625                    panic!(
626                        "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
627                        revision, raw, testfile
628                    );
629                }
630
631                if matches!(self.mode, TestMode::Assembly | TestMode::Codegen | TestMode::MirOpt)
632                    && FILECHECK_FORBIDDEN_REVISION_NAMES.contains(&revision)
633                {
634                    panic!(
635                        "revision name `{revision}` is not permitted in a test suite that uses \
636                        `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
637                        prefix: `{revision}` in line `{}`: {}",
638                        raw, testfile
639                    );
640                }
641
642                existing.push(revision.to_string());
643            }
644        }
645    }
646
647    fn parse_env(nv: String) -> (String, String) {
648        // nv is either FOO or FOO=BAR
649        // FIXME(Zalathar): The form without `=` seems to be unused; should
650        // we drop support for it?
651        let (name, value) = nv.split_once('=').unwrap_or((&nv, ""));
652        // Trim whitespace from the name, so that `//@ exec-env: FOO=BAR`
653        // sees the name as `FOO` and not ` FOO`.
654        let name = name.trim();
655        (name.to_owned(), value.to_owned())
656    }
657
658    fn parse_pp_exact(&self, line: &DirectiveLine<'_>) -> Option<Utf8PathBuf> {
659        if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
660            Some(Utf8PathBuf::from(&s))
661        } else if self.parse_name_directive(line, "pp-exact") {
662            line.file_path.file_name().map(Utf8PathBuf::from)
663        } else {
664            None
665        }
666    }
667
668    fn parse_custom_normalization(&self, line: &DirectiveLine<'_>) -> Option<NormalizeRule> {
669        let &DirectiveLine { name, .. } = line;
670
671        let kind = match name {
672            "normalize-stdout" => NormalizeKind::Stdout,
673            "normalize-stderr" => NormalizeKind::Stderr,
674            "normalize-stderr-32bit" => NormalizeKind::Stderr32bit,
675            "normalize-stderr-64bit" => NormalizeKind::Stderr64bit,
676            _ => return None,
677        };
678
679        let Some((regex, replacement)) = line.value_after_colon().and_then(parse_normalize_rule)
680        else {
681            error!("couldn't parse custom normalization rule: `{}`", line.display());
682            help!("expected syntax is: `{name}: \"REGEX\" -> \"REPLACEMENT\"`");
683            panic!("invalid normalization rule detected");
684        };
685        Some(NormalizeRule { kind, regex, replacement })
686    }
687
688    fn parse_name_directive(&self, line: &DirectiveLine<'_>, directive: &str) -> bool {
689        // FIXME(Zalathar): Ideally, this should raise an error if a name-only
690        // directive is followed by a colon, since that's the wrong syntax.
691        // But we would need to fix tests that rely on the current behaviour.
692        line.name == directive
693    }
694
695    fn parse_name_value_directive(
696        &self,
697        line: &DirectiveLine<'_>,
698        directive: &str,
699    ) -> Option<String> {
700        let &DirectiveLine { file_path, line_number, .. } = line;
701
702        if line.name != directive {
703            return None;
704        };
705
706        // FIXME(Zalathar): This silently discards directives with a matching
707        // name but no colon. Unfortunately, some directives (e.g. "pp-exact")
708        // currently rely on _not_ panicking here.
709        let value = line.value_after_colon()?;
710        debug!("{}: {}", directive, value);
711        let value = expand_variables(value.to_owned(), self);
712
713        if value.is_empty() {
714            error!("{file_path}:{line_number}: empty value for directive `{directive}`");
715            help!("expected syntax is: `{directive}: value`");
716            panic!("empty directive value detected");
717        }
718
719        Some(value)
720    }
721
722    fn set_name_directive(&self, line: &DirectiveLine<'_>, directive: &str, value: &mut bool) {
723        // If the flag is already true, don't bother looking at the directive.
724        *value = *value || self.parse_name_directive(line, directive);
725    }
726
727    fn set_name_value_directive<T>(
728        &self,
729        line: &DirectiveLine<'_>,
730        directive: &str,
731        value: &mut Option<T>,
732        parse: impl FnOnce(String) -> T,
733    ) {
734        if value.is_none() {
735            *value = self.parse_name_value_directive(line, directive).map(parse);
736        }
737    }
738
739    fn push_name_value_directive<T>(
740        &self,
741        line: &DirectiveLine<'_>,
742        directive: &str,
743        values: &mut Vec<T>,
744        parse: impl FnOnce(String) -> T,
745    ) {
746        if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
747            values.push(value);
748        }
749    }
750}
751
752// FIXME(jieyouxu): fix some of these variable names to more accurately reflect what they do.
753fn expand_variables(mut value: String, config: &Config) -> String {
754    const CWD: &str = "{{cwd}}";
755    const SRC_BASE: &str = "{{src-base}}";
756    const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
757    const RUST_SRC_BASE: &str = "{{rust-src-base}}";
758    const SYSROOT_BASE: &str = "{{sysroot-base}}";
759    const TARGET_LINKER: &str = "{{target-linker}}";
760    const TARGET: &str = "{{target}}";
761
762    if value.contains(CWD) {
763        let cwd = env::current_dir().unwrap();
764        value = value.replace(CWD, &cwd.to_str().unwrap());
765    }
766
767    if value.contains(SRC_BASE) {
768        value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
769    }
770
771    if value.contains(TEST_SUITE_BUILD_BASE) {
772        value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
773    }
774
775    if value.contains(SYSROOT_BASE) {
776        value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
777    }
778
779    if value.contains(TARGET_LINKER) {
780        value = value.replace(TARGET_LINKER, config.target_linker.as_deref().unwrap_or(""));
781    }
782
783    if value.contains(TARGET) {
784        value = value.replace(TARGET, &config.target);
785    }
786
787    if value.contains(RUST_SRC_BASE) {
788        let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
789        src_base.try_exists().expect(&*format!("{} should exists", src_base));
790        let src_base = src_base.read_link_utf8().unwrap_or(src_base);
791        value = value.replace(RUST_SRC_BASE, &src_base.as_str());
792    }
793
794    value
795}
796
797struct NormalizeRule {
798    kind: NormalizeKind,
799    regex: String,
800    replacement: String,
801}
802
803enum NormalizeKind {
804    Stdout,
805    Stderr,
806    Stderr32bit,
807    Stderr64bit,
808}
809
810/// Parses the regex and replacement values of a `//@ normalize-*` directive, in the format:
811/// ```text
812/// "REGEX" -> "REPLACEMENT"
813/// ```
814fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
815    // FIXME: Support escaped double-quotes in strings.
816    let captures = static_regex!(
817        r#"(?x) # (verbose mode regex)
818        ^
819        \s*                     # (leading whitespace)
820        "(?<regex>[^"]*)"       # "REGEX"
821        \s+->\s+                # ->
822        "(?<replacement>[^"]*)" # "REPLACEMENT"
823        $
824        "#
825    )
826    .captures(raw_value)?;
827    let regex = captures["regex"].to_owned();
828    let replacement = captures["replacement"].to_owned();
829    // A `\n` sequence in the replacement becomes an actual newline.
830    // FIXME: Do unescaping in a less ad-hoc way, and perhaps support escaped
831    // backslashes and double-quotes.
832    let replacement = replacement.replace("\\n", "\n");
833    Some((regex, replacement))
834}
835
836/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver. Note that this
837/// accepts more than just strict `semver` syntax (as in `major.minor.patch`); this permits omitting
838/// minor and patch version components so users can write e.g. `//@ min-llvm-version: 19` instead of
839/// having to write `//@ min-llvm-version: 19.0.0`.
840///
841/// Currently panics if the input string is malformed, though we really should not use panic as an
842/// error handling strategy.
843///
844/// FIXME(jieyouxu): improve error handling
845pub fn extract_llvm_version(version: &str) -> Version {
846    // The version substring we're interested in usually looks like the `1.2.3`, without any of the
847    // fancy suffix like `-rc1` or `meow`.
848    let version = version.trim();
849    let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
850    let version_without_suffix = match version.split_once(uninterested) {
851        Some((prefix, _suffix)) => prefix,
852        None => version,
853    };
854
855    let components: Vec<u64> = version_without_suffix
856        .split('.')
857        .map(|s| s.parse().expect("llvm version component should consist of only digits"))
858        .collect();
859
860    match &components[..] {
861        [major] => Version::new(*major, 0, 0),
862        [major, minor] => Version::new(*major, *minor, 0),
863        [major, minor, patch] => Version::new(*major, *minor, *patch),
864        _ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
865    }
866}
867
868pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
869    let output = Command::new(binary_path).arg("--version").output().ok()?;
870    if !output.status.success() {
871        return None;
872    }
873    let version = String::from_utf8(output.stdout).ok()?;
874    for line in version.lines() {
875        if let Some(version) = line.split("LLVM version ").nth(1) {
876            return Some(extract_llvm_version(version));
877        }
878    }
879    None
880}
881
882/// For tests using the `needs-llvm-zstd` directive:
883/// - for local LLVM builds, try to find the static zstd library in the llvm-config system libs.
884/// - for `download-ci-llvm`, see if `lld` was built with zstd support.
885pub fn llvm_has_libzstd(config: &Config) -> bool {
886    // Strategy 1: works for local builds but not with `download-ci-llvm`.
887    //
888    // We check whether `llvm-config` returns the zstd library. Bootstrap's `llvm.libzstd` will only
889    // ask to statically link it when building LLVM, so we only check if the list of system libs
890    // contains a path to that static lib, and that it exists.
891    //
892    // See compiler/rustc_llvm/build.rs for more details and similar expectations.
893    fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> {
894        let llvm_config_path = llvm_bin_dir.join("llvm-config");
895        let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?;
896        assert!(output.status.success(), "running llvm-config --system-libs failed");
897
898        let libs = String::from_utf8(output.stdout).ok()?;
899        for lib in libs.split_whitespace() {
900            if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() {
901                return Some(());
902            }
903        }
904
905        None
906    }
907
908    // Strategy 2: `download-ci-llvm`'s `llvm-config --system-libs` will not return any libs to
909    // use.
910    //
911    // The CI artifacts also don't contain the bootstrap config used to build them: otherwise we
912    // could have looked at the `llvm.libzstd` config.
913    //
914    // We infer whether `LLVM_ENABLE_ZSTD` was used to build LLVM as a byproduct of testing whether
915    // `lld` supports it. If not, an error will be emitted: "LLVM was not built with
916    // LLVM_ENABLE_ZSTD or did not find zstd at build time".
917    #[cfg(unix)]
918    fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> {
919        let lld_path = llvm_bin_dir.join("lld");
920        if lld_path.exists() {
921            // We can't call `lld` as-is, it expects to be invoked by a compiler driver using a
922            // different name. Prepare a temporary symlink to do that.
923            let lld_symlink_path = llvm_bin_dir.join("ld.lld");
924            if !lld_symlink_path.exists() {
925                std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?;
926            }
927
928            // Run `lld` with a zstd flag. We expect this command to always error here, we don't
929            // want to link actual files and don't pass any.
930            let output = Command::new(&lld_symlink_path)
931                .arg("--compress-debug-sections=zstd")
932                .output()
933                .ok()?;
934            assert!(!output.status.success());
935
936            // Look for a specific error caused by LLVM not being built with zstd support. We could
937            // also look for the "no input files" message, indicating the zstd flag was accepted.
938            let stderr = String::from_utf8(output.stderr).ok()?;
939            let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD");
940
941            // We don't particularly need to clean the link up (so the previous commands could fail
942            // in theory but won't in practice), but we can try.
943            std::fs::remove_file(lld_symlink_path).ok()?;
944
945            if zstd_available {
946                return Some(());
947            }
948        }
949
950        None
951    }
952
953    #[cfg(not(unix))]
954    fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> {
955        None
956    }
957
958    if let Some(llvm_bin_dir) = &config.llvm_bin_dir {
959        // Strategy 1: for local LLVM builds.
960        if is_zstd_in_config(llvm_bin_dir).is_some() {
961            return true;
962        }
963
964        // Strategy 2: for LLVM artifacts built on CI via `download-ci-llvm`.
965        //
966        // It doesn't work for cases where the artifacts don't contain the linker, but it's
967        // best-effort: CI has `llvm.libzstd` and `lld` enabled on the x64 linux artifacts, so it
968        // will at least work there.
969        //
970        // If this can be improved and expanded to less common cases in the future, it should.
971        if config.target == "x86_64-unknown-linux-gnu"
972            && config.host == config.target
973            && is_lld_built_with_zstd(llvm_bin_dir).is_some()
974        {
975            return true;
976        }
977    }
978
979    // Otherwise, all hope is lost.
980    false
981}
982
983/// Takes a directive of the form `"<version1> [- <version2>]"`, returns the numeric representation
984/// of `<version1>` and `<version2>` as tuple: `(<version1>, <version2>)`.
985///
986/// If the `<version2>` part is omitted, the second component of the tuple is the same as
987/// `<version1>`.
988fn extract_version_range<'a, F, VersionTy: Clone>(
989    line: &'a str,
990    parse: F,
991) -> Option<(VersionTy, VersionTy)>
992where
993    F: Fn(&'a str) -> Option<VersionTy>,
994{
995    let mut splits = line.splitn(2, "- ").map(str::trim);
996    let min = splits.next().unwrap();
997    if min.ends_with('-') {
998        return None;
999    }
1000
1001    let max = splits.next();
1002
1003    if min.is_empty() {
1004        return None;
1005    }
1006
1007    let min = parse(min)?;
1008    let max = match max {
1009        Some("") => return None,
1010        Some(max) => parse(max)?,
1011        _ => min.clone(),
1012    };
1013
1014    Some((min, max))
1015}
1016
1017pub(crate) fn make_test_description(
1018    config: &Config,
1019    cache: &DirectivesCache,
1020    name: String,
1021    path: &Utf8Path,
1022    filterable_path: &Utf8Path,
1023    file_directives: &FileDirectives<'_>,
1024    test_revision: Option<&str>,
1025    poisoned: &mut bool,
1026    aux_props: &mut AuxProps,
1027) -> CollectedTestDesc {
1028    let mut ignore = false;
1029    let mut ignore_message = None;
1030    let mut should_fail = false;
1031
1032    // Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives.
1033    iter_directives(
1034        config.mode,
1035        file_directives,
1036        &mut |ln @ &DirectiveLine { line_number, .. }| {
1037            if !ln.applies_to_test_revision(test_revision) {
1038                return;
1039            }
1040
1041            // Parse `aux-*` directives, for use by up-to-date checks.
1042            parse_and_update_aux(config, ln, aux_props);
1043
1044            macro_rules! decision {
1045                ($e:expr) => {
1046                    match $e {
1047                        IgnoreDecision::Ignore { reason } => {
1048                            ignore = true;
1049                            ignore_message = Some(reason.into());
1050                        }
1051                        IgnoreDecision::Error { message } => {
1052                            error!("{path}:{line_number}: {message}");
1053                            *poisoned = true;
1054                            return;
1055                        }
1056                        IgnoreDecision::Continue => {}
1057                    }
1058                };
1059            }
1060
1061            decision!(cfg::handle_ignore(config, ln));
1062            decision!(cfg::handle_only(config, ln));
1063            decision!(needs::handle_needs(&cache.needs, config, ln));
1064            decision!(ignore_llvm(config, ln));
1065            decision!(ignore_backends(config, ln));
1066            decision!(needs_backends(config, ln));
1067            decision!(ignore_cdb(config, ln));
1068            decision!(ignore_gdb(config, ln));
1069            decision!(ignore_lldb(config, ln));
1070
1071            if config.target == "wasm32-unknown-unknown"
1072                && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
1073            {
1074                decision!(IgnoreDecision::Ignore {
1075                    reason: "ignored on WASM as the run results cannot be checked there".into(),
1076                });
1077            }
1078
1079            should_fail |= config.parse_name_directive(ln, "should-fail");
1080        },
1081    );
1082
1083    // The `should-fail` annotation doesn't apply to pretty tests,
1084    // since we run the pretty printer across all tests by default.
1085    // If desired, we could add a `should-fail-pretty` annotation.
1086    let should_fail = if should_fail && config.mode != TestMode::Pretty {
1087        ShouldFail::Yes
1088    } else {
1089        ShouldFail::No
1090    };
1091
1092    CollectedTestDesc {
1093        name,
1094        filterable_path: filterable_path.to_owned(),
1095        ignore,
1096        ignore_message,
1097        should_fail,
1098    }
1099}
1100
1101fn ignore_cdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1102    if config.debugger != Some(Debugger::Cdb) {
1103        return IgnoreDecision::Continue;
1104    }
1105
1106    if let Some(actual_version) = config.cdb_version {
1107        if line.name == "min-cdb-version"
1108            && let Some(rest) = line.value_after_colon().map(str::trim)
1109        {
1110            let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1111                panic!("couldn't parse version range: {:?}", rest);
1112            });
1113
1114            // Ignore if actual version is smaller than the minimum
1115            // required version
1116            if actual_version < min_version {
1117                return IgnoreDecision::Ignore {
1118                    reason: format!("ignored when the CDB version is lower than {rest}"),
1119                };
1120            }
1121        }
1122    }
1123    IgnoreDecision::Continue
1124}
1125
1126fn ignore_gdb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1127    if config.debugger != Some(Debugger::Gdb) {
1128        return IgnoreDecision::Continue;
1129    }
1130
1131    if let Some(actual_version) = config.gdb_version {
1132        if line.name == "min-gdb-version"
1133            && let Some(rest) = line.value_after_colon().map(str::trim)
1134        {
1135            let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1136                .unwrap_or_else(|| {
1137                    panic!("couldn't parse version range: {:?}", rest);
1138                });
1139
1140            if start_ver != end_ver {
1141                panic!("Expected single GDB version")
1142            }
1143            // Ignore if actual version is smaller than the minimum
1144            // required version
1145            if actual_version < start_ver {
1146                return IgnoreDecision::Ignore {
1147                    reason: format!("ignored when the GDB version is lower than {rest}"),
1148                };
1149            }
1150        } else if line.name == "ignore-gdb-version"
1151            && let Some(rest) = line.value_after_colon().map(str::trim)
1152        {
1153            let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1154                .unwrap_or_else(|| {
1155                    panic!("couldn't parse version range: {:?}", rest);
1156                });
1157
1158            if max_version < min_version {
1159                panic!("Malformed GDB version range: max < min")
1160            }
1161
1162            if actual_version >= min_version && actual_version <= max_version {
1163                if min_version == max_version {
1164                    return IgnoreDecision::Ignore {
1165                        reason: format!("ignored when the GDB version is {rest}"),
1166                    };
1167                } else {
1168                    return IgnoreDecision::Ignore {
1169                        reason: format!("ignored when the GDB version is between {rest}"),
1170                    };
1171                }
1172            }
1173        }
1174    }
1175    IgnoreDecision::Continue
1176}
1177
1178fn ignore_lldb(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1179    if config.debugger != Some(Debugger::Lldb) {
1180        return IgnoreDecision::Continue;
1181    }
1182
1183    if let Some(actual_version) = config.lldb_version {
1184        if line.name == "min-lldb-version"
1185            && let Some(rest) = line.value_after_colon().map(str::trim)
1186        {
1187            let min_version = rest.parse().unwrap_or_else(|e| {
1188                panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1189            });
1190            // Ignore if actual version is smaller the minimum required
1191            // version
1192            if actual_version < min_version {
1193                return IgnoreDecision::Ignore {
1194                    reason: format!("ignored when the LLDB version is {rest}"),
1195                };
1196            }
1197        }
1198    }
1199    IgnoreDecision::Continue
1200}
1201
1202fn ignore_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1203    let path = line.file_path;
1204    if let Some(backends_to_ignore) = config.parse_name_value_directive(line, "ignore-backends") {
1205        for backend in backends_to_ignore.split_whitespace().map(|backend| {
1206            match CodegenBackend::try_from(backend) {
1207                Ok(backend) => backend,
1208                Err(error) => {
1209                    panic!("Invalid ignore-backends value `{backend}` in `{path}`: {error}")
1210                }
1211            }
1212        }) {
1213            if !config.bypass_ignore_backends && config.default_codegen_backend == backend {
1214                return IgnoreDecision::Ignore {
1215                    reason: format!("{} backend is marked as ignore", backend.as_str()),
1216                };
1217            }
1218        }
1219    }
1220    IgnoreDecision::Continue
1221}
1222
1223fn needs_backends(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1224    let path = line.file_path;
1225    if let Some(needed_backends) = config.parse_name_value_directive(line, "needs-backends") {
1226        if !needed_backends
1227            .split_whitespace()
1228            .map(|backend| match CodegenBackend::try_from(backend) {
1229                Ok(backend) => backend,
1230                Err(error) => {
1231                    panic!("Invalid needs-backends value `{backend}` in `{path}`: {error}")
1232                }
1233            })
1234            .any(|backend| config.default_codegen_backend == backend)
1235        {
1236            return IgnoreDecision::Ignore {
1237                reason: format!(
1238                    "{} backend is not part of required backends",
1239                    config.default_codegen_backend.as_str()
1240                ),
1241            };
1242        }
1243    }
1244    IgnoreDecision::Continue
1245}
1246
1247fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
1248    let path = line.file_path;
1249    if let Some(needed_components) =
1250        config.parse_name_value_directive(line, "needs-llvm-components")
1251    {
1252        let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1253        if let Some(missing_component) = needed_components
1254            .split_whitespace()
1255            .find(|needed_component| !components.contains(needed_component))
1256        {
1257            if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
1258                panic!(
1259                    "missing LLVM component {missing_component}, \
1260                    and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {path}",
1261                );
1262            }
1263            return IgnoreDecision::Ignore {
1264                reason: format!("ignored when the {missing_component} LLVM component is missing"),
1265            };
1266        }
1267    }
1268    if let Some(actual_version) = &config.llvm_version {
1269        // Note that these `min` versions will check for not just major versions.
1270
1271        if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
1272            let min_version = extract_llvm_version(&version_string);
1273            // Ignore if actual version is smaller than the minimum required version.
1274            if *actual_version < min_version {
1275                return IgnoreDecision::Ignore {
1276                    reason: format!(
1277                        "ignored when the LLVM version {actual_version} is older than {min_version}"
1278                    ),
1279                };
1280            }
1281        } else if let Some(version_string) =
1282            config.parse_name_value_directive(line, "max-llvm-major-version")
1283        {
1284            let max_version = extract_llvm_version(&version_string);
1285            // Ignore if actual major version is larger than the maximum required major version.
1286            if actual_version.major > max_version.major {
1287                return IgnoreDecision::Ignore {
1288                    reason: format!(
1289                        "ignored when the LLVM version ({actual_version}) is newer than major\
1290                        version {}",
1291                        max_version.major
1292                    ),
1293                };
1294            }
1295        } else if let Some(version_string) =
1296            config.parse_name_value_directive(line, "min-system-llvm-version")
1297        {
1298            let min_version = extract_llvm_version(&version_string);
1299            // Ignore if using system LLVM and actual version
1300            // is smaller the minimum required version
1301            if config.system_llvm && *actual_version < min_version {
1302                return IgnoreDecision::Ignore {
1303                    reason: format!(
1304                        "ignored when the system LLVM version {actual_version} is older than {min_version}"
1305                    ),
1306                };
1307            }
1308        } else if let Some(version_range) =
1309            config.parse_name_value_directive(line, "ignore-llvm-version")
1310        {
1311            // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
1312            let (v_min, v_max) =
1313                extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
1314                    .unwrap_or_else(|| {
1315                        panic!("couldn't parse version range: \"{version_range}\"");
1316                    });
1317            if v_max < v_min {
1318                panic!("malformed LLVM version range where {v_max} < {v_min}")
1319            }
1320            // Ignore if version lies inside of range.
1321            if *actual_version >= v_min && *actual_version <= v_max {
1322                if v_min == v_max {
1323                    return IgnoreDecision::Ignore {
1324                        reason: format!("ignored when the LLVM version is {actual_version}"),
1325                    };
1326                } else {
1327                    return IgnoreDecision::Ignore {
1328                        reason: format!(
1329                            "ignored when the LLVM version is between {v_min} and {v_max}"
1330                        ),
1331                    };
1332                }
1333            }
1334        } else if let Some(version_string) =
1335            config.parse_name_value_directive(line, "exact-llvm-major-version")
1336        {
1337            // Syntax is "exact-llvm-major-version: <version>"
1338            let version = extract_llvm_version(&version_string);
1339            if actual_version.major != version.major {
1340                return IgnoreDecision::Ignore {
1341                    reason: format!(
1342                        "ignored when the actual LLVM major version is {}, but the test only targets major version {}",
1343                        actual_version.major, version.major
1344                    ),
1345                };
1346            }
1347        }
1348    }
1349    IgnoreDecision::Continue
1350}
1351
1352enum IgnoreDecision {
1353    Ignore { reason: String },
1354    Continue,
1355    Error { message: String },
1356}
1357
1358fn parse_edition_range(config: &Config, line: &DirectiveLine<'_>) -> Option<EditionRange> {
1359    let raw = config.parse_name_value_directive(line, "edition")?;
1360    let &DirectiveLine { file_path: testfile, line_number, .. } = line;
1361
1362    // Edition range is half-open: `[lower_bound, upper_bound)`
1363    if let Some((lower_bound, upper_bound)) = raw.split_once("..") {
1364        Some(match (maybe_parse_edition(lower_bound), maybe_parse_edition(upper_bound)) {
1365            (Some(lower_bound), Some(upper_bound)) if upper_bound <= lower_bound => {
1366                fatal!(
1367                    "{testfile}:{line_number}: the left side of `//@ edition` cannot be greater than or equal to the right side"
1368                );
1369            }
1370            (Some(lower_bound), Some(upper_bound)) => {
1371                EditionRange::Range { lower_bound, upper_bound }
1372            }
1373            (Some(lower_bound), None) => EditionRange::RangeFrom(lower_bound),
1374            (None, Some(_)) => {
1375                fatal!(
1376                    "{testfile}:{line_number}: `..edition` is not a supported range in `//@ edition`"
1377                );
1378            }
1379            (None, None) => {
1380                fatal!("{testfile}:{line_number}: `..` is not a supported range in `//@ edition`");
1381            }
1382        })
1383    } else {
1384        match maybe_parse_edition(&raw) {
1385            Some(edition) => Some(EditionRange::Exact(edition)),
1386            None => {
1387                fatal!("{testfile}:{line_number}: empty value for `//@ edition`");
1388            }
1389        }
1390    }
1391}
1392
1393fn maybe_parse_edition(mut input: &str) -> Option<Edition> {
1394    input = input.trim();
1395    if input.is_empty() {
1396        return None;
1397    }
1398    Some(parse_edition(input))
1399}
1400
1401#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1402enum EditionRange {
1403    Exact(Edition),
1404    RangeFrom(Edition),
1405    /// Half-open range: `[lower_bound, upper_bound)`
1406    Range {
1407        lower_bound: Edition,
1408        upper_bound: Edition,
1409    },
1410}
1411
1412impl EditionRange {
1413    fn edition_to_test(&self, requested: impl Into<Option<Edition>>) -> Edition {
1414        let min_edition = Edition::Year(2015);
1415        let requested = requested.into().unwrap_or(min_edition);
1416
1417        match *self {
1418            EditionRange::Exact(exact) => exact,
1419            EditionRange::RangeFrom(lower_bound) => {
1420                if requested >= lower_bound {
1421                    requested
1422                } else {
1423                    lower_bound
1424                }
1425            }
1426            EditionRange::Range { lower_bound, upper_bound } => {
1427                if requested >= lower_bound && requested < upper_bound {
1428                    requested
1429                } else {
1430                    lower_bound
1431                }
1432            }
1433        }
1434    }
1435}
1436
1437fn split_flags(flags: &str) -> Vec<String> {
1438    // Individual flags can be single-quoted to preserve spaces; see
1439    // <https://github.com/rust-lang/rust/pull/115948/commits/957c5db6>.
1440    // FIXME(#147955): Replace this ad-hoc quoting with an escape/quote system that
1441    // is closer to what actual shells do, so that it's more flexible and familiar.
1442    flags
1443        .split('\'')
1444        .enumerate()
1445        .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() })
1446        .map(move |s| s.to_owned())
1447        .collect::<Vec<_>>()
1448}