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