compiletest/
common.rs

1use std::collections::{BTreeSet, HashMap, HashSet};
2use std::ffi::OsString;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use std::str::FromStr;
6use std::sync::OnceLock;
7use std::{fmt, iter};
8
9use build_helper::git::GitConfig;
10use semver::Version;
11use serde::de::{Deserialize, Deserializer, Error as _};
12use test::{ColorConfig, OutputFormat};
13
14pub use self::Mode::*;
15use crate::util::{PathBufExt, add_dylib_path};
16
17macro_rules! string_enum {
18    ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $repr:expr,)* }) => {
19        $(#[$meta])*
20        $vis enum $name {
21            $($variant,)*
22        }
23
24        impl $name {
25            $vis const VARIANTS: &'static [Self] = &[$(Self::$variant,)*];
26            $vis const STR_VARIANTS: &'static [&'static str] = &[$(Self::$variant.to_str(),)*];
27
28            $vis const fn to_str(&self) -> &'static str {
29                match self {
30                    $(Self::$variant => $repr,)*
31                }
32            }
33        }
34
35        impl fmt::Display for $name {
36            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37                fmt::Display::fmt(self.to_str(), f)
38            }
39        }
40
41        impl FromStr for $name {
42            type Err = String;
43
44            fn from_str(s: &str) -> Result<Self, Self::Err> {
45                match s {
46                    $($repr => Ok(Self::$variant),)*
47                    _ => Err(format!(concat!("unknown `", stringify!($name), "` variant: `{}`"), s)),
48                }
49            }
50        }
51    }
52}
53
54// Make the macro visible outside of this module, for tests.
55#[cfg(test)]
56pub(crate) use string_enum;
57
58string_enum! {
59    #[derive(Clone, Copy, PartialEq, Debug)]
60    pub enum Mode {
61        Pretty => "pretty",
62        DebugInfo => "debuginfo",
63        Codegen => "codegen",
64        Rustdoc => "rustdoc",
65        RustdocJson => "rustdoc-json",
66        CodegenUnits => "codegen-units",
67        Incremental => "incremental",
68        RunMake => "run-make",
69        Ui => "ui",
70        RustdocJs => "rustdoc-js",
71        MirOpt => "mir-opt",
72        Assembly => "assembly",
73        CoverageMap => "coverage-map",
74        CoverageRun => "coverage-run",
75        Crashes => "crashes",
76    }
77}
78
79impl Default for Mode {
80    fn default() -> Self {
81        Mode::Ui
82    }
83}
84
85impl Mode {
86    pub fn aux_dir_disambiguator(self) -> &'static str {
87        // Pretty-printing tests could run concurrently, and if they do,
88        // they need to keep their output segregated.
89        match self {
90            Pretty => ".pretty",
91            _ => "",
92        }
93    }
94
95    pub fn output_dir_disambiguator(self) -> &'static str {
96        // Coverage tests use the same test files for multiple test modes,
97        // so each mode should have a separate output directory.
98        match self {
99            CoverageMap | CoverageRun => self.to_str(),
100            _ => "",
101        }
102    }
103}
104
105string_enum! {
106    #[derive(Clone, Copy, PartialEq, Debug, Hash)]
107    pub enum PassMode {
108        Check => "check",
109        Build => "build",
110        Run => "run",
111    }
112}
113
114#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
115pub enum FailMode {
116    Check,
117    Build,
118    Run,
119}
120
121string_enum! {
122    #[derive(Clone, Debug, PartialEq)]
123    pub enum CompareMode {
124        Polonius => "polonius",
125        NextSolver => "next-solver",
126        NextSolverCoherence => "next-solver-coherence",
127        SplitDwarf => "split-dwarf",
128        SplitDwarfSingle => "split-dwarf-single",
129    }
130}
131
132string_enum! {
133    #[derive(Clone, Copy, Debug, PartialEq)]
134    pub enum Debugger {
135        Cdb => "cdb",
136        Gdb => "gdb",
137        Lldb => "lldb",
138    }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Default, serde::Deserialize)]
142#[serde(rename_all = "kebab-case")]
143pub enum PanicStrategy {
144    #[default]
145    Unwind,
146    Abort,
147}
148
149impl PanicStrategy {
150    pub(crate) fn for_miropt_test_tools(&self) -> miropt_test_tools::PanicStrategy {
151        match self {
152            PanicStrategy::Unwind => miropt_test_tools::PanicStrategy::Unwind,
153            PanicStrategy::Abort => miropt_test_tools::PanicStrategy::Abort,
154        }
155    }
156}
157
158#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
159#[serde(rename_all = "kebab-case")]
160pub enum Sanitizer {
161    Address,
162    Cfi,
163    Dataflow,
164    Kcfi,
165    KernelAddress,
166    Leak,
167    Memory,
168    Memtag,
169    Safestack,
170    ShadowCallStack,
171    Thread,
172    Hwaddress,
173}
174
175/// Configuration for compiletest
176#[derive(Debug, Default, Clone)]
177pub struct Config {
178    /// `true` to overwrite stderr/stdout files instead of complaining about changes in output.
179    pub bless: bool,
180
181    /// The library paths required for running the compiler.
182    pub compile_lib_path: PathBuf,
183
184    /// The library paths required for running compiled programs.
185    pub run_lib_path: PathBuf,
186
187    /// The rustc executable.
188    pub rustc_path: PathBuf,
189
190    /// The cargo executable.
191    pub cargo_path: Option<PathBuf>,
192
193    /// Rustc executable used to compile run-make recipes.
194    pub stage0_rustc_path: Option<PathBuf>,
195
196    /// The rustdoc executable.
197    pub rustdoc_path: Option<PathBuf>,
198
199    /// The coverage-dump executable.
200    pub coverage_dump_path: Option<PathBuf>,
201
202    /// The Python executable to use for LLDB and htmldocck.
203    pub python: String,
204
205    /// The jsondocck executable.
206    pub jsondocck_path: Option<String>,
207
208    /// The jsondoclint executable.
209    pub jsondoclint_path: Option<String>,
210
211    /// The LLVM `FileCheck` binary path.
212    pub llvm_filecheck: Option<PathBuf>,
213
214    /// Path to LLVM's bin directory.
215    pub llvm_bin_dir: Option<PathBuf>,
216
217    /// The path to the Clang executable to run Clang-based tests with. If
218    /// `None` then these tests will be ignored.
219    pub run_clang_based_tests_with: Option<String>,
220
221    /// The directory containing the sources.
222    pub src_root: PathBuf,
223    /// The directory containing the test suite sources. Must be a subdirectory of `src_root`.
224    pub src_test_suite_root: PathBuf,
225
226    /// Root build directory (e.g. `build/`).
227    pub build_root: PathBuf,
228    /// Test suite specific build directory (e.g. `build/host/test/ui/`).
229    pub build_test_suite_root: PathBuf,
230
231    /// The directory containing the compiler sysroot
232    pub sysroot_base: PathBuf,
233
234    /// The number of the stage under test.
235    pub stage: u32,
236    /// The id of the stage under test (stage1-xxx, etc).
237    pub stage_id: String,
238
239    /// The test mode, e.g. ui or debuginfo.
240    pub mode: Mode,
241
242    /// The test suite (essentially which directory is running, but without the
243    /// directory prefix such as tests)
244    pub suite: String,
245
246    /// The debugger to use in debuginfo mode. Unset otherwise.
247    pub debugger: Option<Debugger>,
248
249    /// Run ignored tests
250    pub run_ignored: bool,
251
252    /// Whether rustc was built with debug assertions.
253    pub with_rustc_debug_assertions: bool,
254
255    /// Whether std was built with debug assertions.
256    pub with_std_debug_assertions: bool,
257
258    /// Only run tests that match these filters
259    pub filters: Vec<String>,
260
261    /// Skip tests matching these substrings. Corresponds to
262    /// `test::TestOpts::skip`. `filter_exact` does not apply to these flags.
263    pub skip: Vec<String>,
264
265    /// Exactly match the filter, rather than a substring
266    pub filter_exact: bool,
267
268    /// Force the pass mode of a check/build/run-pass test to this mode.
269    pub force_pass_mode: Option<PassMode>,
270
271    /// Explicitly enable or disable running.
272    pub run: Option<bool>,
273
274    /// Write out a parseable log of tests that were run
275    pub logfile: Option<PathBuf>,
276
277    /// A command line to prefix program execution with,
278    /// for running under valgrind for example.
279    ///
280    /// Similar to `CARGO_*_RUNNER` configuration.
281    pub runner: Option<String>,
282
283    /// Flags to pass to the compiler when building for the host
284    pub host_rustcflags: Vec<String>,
285
286    /// Flags to pass to the compiler when building for the target
287    pub target_rustcflags: Vec<String>,
288
289    /// Whether the compiler and stdlib has been built with randomized struct layouts
290    pub rust_randomized_layout: bool,
291
292    /// Whether tests should be optimized by default. Individual test-suites and test files may
293    /// override this setting.
294    pub optimize_tests: bool,
295
296    /// Target system to be tested
297    pub target: String,
298
299    /// Host triple for the compiler being invoked
300    pub host: String,
301
302    /// Path to / name of the Microsoft Console Debugger (CDB) executable
303    pub cdb: Option<OsString>,
304
305    /// Version of CDB
306    pub cdb_version: Option<[u16; 4]>,
307
308    /// Path to / name of the GDB executable
309    pub gdb: Option<String>,
310
311    /// Version of GDB, encoded as ((major * 1000) + minor) * 1000 + patch
312    pub gdb_version: Option<u32>,
313
314    /// Version of LLDB
315    pub lldb_version: Option<u32>,
316
317    /// Version of LLVM
318    pub llvm_version: Option<Version>,
319
320    /// Is LLVM a system LLVM
321    pub system_llvm: bool,
322
323    /// Path to the android tools
324    pub android_cross_path: PathBuf,
325
326    /// Extra parameter to run adb on arm-linux-androideabi
327    pub adb_path: String,
328
329    /// Extra parameter to run test suite on arm-linux-androideabi
330    pub adb_test_dir: String,
331
332    /// status whether android device available or not
333    pub adb_device_status: bool,
334
335    /// the path containing LLDB's Python module
336    pub lldb_python_dir: Option<String>,
337
338    /// Explain what's going on
339    pub verbose: bool,
340
341    /// Print one character per test instead of one line
342    pub format: OutputFormat,
343
344    /// Whether to use colors in test.
345    pub color: ColorConfig,
346
347    /// where to find the remote test client process, if we're using it
348    pub remote_test_client: Option<PathBuf>,
349
350    /// mode describing what file the actual ui output will be compared to
351    pub compare_mode: Option<CompareMode>,
352
353    /// If true, this will generate a coverage file with UI test files that run `MachineApplicable`
354    /// diagnostics but are missing `run-rustfix` annotations. The generated coverage file is
355    /// created in `<test_suite_build_root>/rustfix_missing_coverage.txt`
356    pub rustfix_coverage: bool,
357
358    /// whether to run `tidy` (html-tidy) when a rustdoc test fails
359    pub has_html_tidy: bool,
360
361    /// whether to run `enzyme` autodiff tests
362    pub has_enzyme: bool,
363
364    /// The current Rust channel
365    pub channel: String,
366
367    /// Whether adding git commit information such as the commit hash has been enabled for building
368    pub git_hash: bool,
369
370    /// The default Rust edition
371    pub edition: Option<String>,
372
373    // Configuration for various run-make tests frobbing things like C compilers
374    // or querying about various LLVM component information.
375    pub cc: String,
376    pub cxx: String,
377    pub cflags: String,
378    pub cxxflags: String,
379    pub ar: String,
380    pub target_linker: Option<String>,
381    pub host_linker: Option<String>,
382    pub llvm_components: String,
383
384    /// Path to a NodeJS executable. Used for JS doctests, emscripten and WASM tests
385    pub nodejs: Option<String>,
386    /// Path to a npm executable. Used for rustdoc GUI tests
387    pub npm: Option<String>,
388
389    /// Whether to rerun tests even if the inputs are unchanged.
390    pub force_rerun: bool,
391
392    /// Only rerun the tests that result has been modified according to Git status
393    pub only_modified: bool,
394
395    pub target_cfgs: OnceLock<TargetCfgs>,
396    pub builtin_cfg_names: OnceLock<HashSet<String>>,
397
398    pub nocapture: bool,
399
400    // Needed both to construct build_helper::git::GitConfig
401    pub git_repository: String,
402    pub nightly_branch: String,
403    pub git_merge_commit_email: String,
404
405    /// True if the profiler runtime is enabled for this target.
406    /// Used by the "needs-profiler-runtime" directive in test files.
407    pub profiler_runtime: bool,
408
409    /// Command for visual diff display, e.g. `diff-tool --color=always`.
410    pub diff_command: Option<String>,
411
412    /// Path to minicore aux library, used for `no_core` tests that need `core` stubs in
413    /// cross-compilation scenarios that do not otherwise want/need to `-Zbuild-std`. Used in e.g.
414    /// ABI tests.
415    pub minicore_path: PathBuf,
416}
417
418impl Config {
419    pub fn run_enabled(&self) -> bool {
420        self.run.unwrap_or_else(|| {
421            // Auto-detect whether to run based on the platform.
422            !self.target.ends_with("-fuchsia")
423        })
424    }
425
426    pub fn target_cfgs(&self) -> &TargetCfgs {
427        self.target_cfgs.get_or_init(|| TargetCfgs::new(self))
428    }
429
430    pub fn target_cfg(&self) -> &TargetCfg {
431        &self.target_cfgs().current
432    }
433
434    pub fn matches_arch(&self, arch: &str) -> bool {
435        self.target_cfg().arch == arch ||
436        // Matching all the thumb variants as one can be convenient.
437        // (thumbv6m, thumbv7em, thumbv7m, etc.)
438        (arch == "thumb" && self.target.starts_with("thumb"))
439    }
440
441    pub fn matches_os(&self, os: &str) -> bool {
442        self.target_cfg().os == os
443    }
444
445    pub fn matches_env(&self, env: &str) -> bool {
446        self.target_cfg().env == env
447    }
448
449    pub fn matches_abi(&self, abi: &str) -> bool {
450        self.target_cfg().abi == abi
451    }
452
453    pub fn matches_family(&self, family: &str) -> bool {
454        self.target_cfg().families.iter().any(|f| f == family)
455    }
456
457    pub fn is_big_endian(&self) -> bool {
458        self.target_cfg().endian == Endian::Big
459    }
460
461    pub fn get_pointer_width(&self) -> u32 {
462        *&self.target_cfg().pointer_width
463    }
464
465    pub fn can_unwind(&self) -> bool {
466        self.target_cfg().panic == PanicStrategy::Unwind
467    }
468
469    /// Get the list of builtin, 'well known' cfg names
470    pub fn builtin_cfg_names(&self) -> &HashSet<String> {
471        self.builtin_cfg_names.get_or_init(|| builtin_cfg_names(self))
472    }
473
474    pub fn has_threads(&self) -> bool {
475        // Wasm targets don't have threads unless `-threads` is in the target
476        // name, such as `wasm32-wasip1-threads`.
477        if self.target.starts_with("wasm") {
478            return self.target.contains("threads");
479        }
480        true
481    }
482
483    pub fn has_asm_support(&self) -> bool {
484        // This should match the stable list in `LoweringContext::lower_inline_asm`.
485        static ASM_SUPPORTED_ARCHS: &[&str] = &[
486            "x86",
487            "x86_64",
488            "arm",
489            "aarch64",
490            "arm64ec",
491            "riscv32",
492            "riscv64",
493            "loongarch64",
494            "s390x",
495            // These targets require an additional asm_experimental_arch feature.
496            // "nvptx64", "hexagon", "mips", "mips64", "spirv", "wasm32",
497        ];
498        ASM_SUPPORTED_ARCHS.contains(&self.target_cfg().arch.as_str())
499    }
500
501    pub fn git_config(&self) -> GitConfig<'_> {
502        GitConfig {
503            git_repository: &self.git_repository,
504            nightly_branch: &self.nightly_branch,
505            git_merge_commit_email: &self.git_merge_commit_email,
506        }
507    }
508
509    pub fn has_subprocess_support(&self) -> bool {
510        // FIXME(#135928): compiletest is always a **host** tool. Building and running an
511        // capability detection executable against the **target** is not trivial. The short term
512        // solution here is to hard-code some targets to allow/deny, unfortunately.
513
514        let unsupported_target = self.target_cfg().env == "sgx"
515            || matches!(self.target_cfg().arch.as_str(), "wasm32" | "wasm64")
516            || self.target_cfg().os == "emscripten";
517        !unsupported_target
518    }
519}
520
521/// Known widths of `target_has_atomic`.
522pub const KNOWN_TARGET_HAS_ATOMIC_WIDTHS: &[&str] = &["8", "16", "32", "64", "128", "ptr"];
523
524#[derive(Debug, Clone)]
525pub struct TargetCfgs {
526    pub current: TargetCfg,
527    pub all_targets: HashSet<String>,
528    pub all_archs: HashSet<String>,
529    pub all_oses: HashSet<String>,
530    pub all_oses_and_envs: HashSet<String>,
531    pub all_envs: HashSet<String>,
532    pub all_abis: HashSet<String>,
533    pub all_families: HashSet<String>,
534    pub all_pointer_widths: HashSet<String>,
535    pub all_rustc_abis: HashSet<String>,
536}
537
538impl TargetCfgs {
539    fn new(config: &Config) -> TargetCfgs {
540        let mut targets: HashMap<String, TargetCfg> = serde_json::from_str(&rustc_output(
541            config,
542            &["--print=all-target-specs-json", "-Zunstable-options"],
543            Default::default(),
544        ))
545        .unwrap();
546
547        let mut all_targets = HashSet::new();
548        let mut all_archs = HashSet::new();
549        let mut all_oses = HashSet::new();
550        let mut all_oses_and_envs = HashSet::new();
551        let mut all_envs = HashSet::new();
552        let mut all_abis = HashSet::new();
553        let mut all_families = HashSet::new();
554        let mut all_pointer_widths = HashSet::new();
555        // NOTE: for distinction between `abi` and `rustc_abi`, see comment on
556        // `TargetCfg::rustc_abi`.
557        let mut all_rustc_abis = HashSet::new();
558
559        // If current target is not included in the `--print=all-target-specs-json` output,
560        // we check whether it is a custom target from the user or a synthetic target from bootstrap.
561        if !targets.contains_key(&config.target) {
562            let mut envs: HashMap<String, String> = HashMap::new();
563
564            if let Ok(t) = std::env::var("RUST_TARGET_PATH") {
565                envs.insert("RUST_TARGET_PATH".into(), t);
566            }
567
568            // This returns false only when the target is neither a synthetic target
569            // nor a custom target from the user, indicating it is most likely invalid.
570            if config.target.ends_with(".json") || !envs.is_empty() {
571                targets.insert(
572                    config.target.clone(),
573                    serde_json::from_str(&rustc_output(
574                        config,
575                        &[
576                            "--print=target-spec-json",
577                            "-Zunstable-options",
578                            "--target",
579                            &config.target,
580                        ],
581                        envs,
582                    ))
583                    .unwrap(),
584                );
585            }
586        }
587
588        for (target, cfg) in targets.iter() {
589            all_archs.insert(cfg.arch.clone());
590            all_oses.insert(cfg.os.clone());
591            all_oses_and_envs.insert(cfg.os_and_env());
592            all_envs.insert(cfg.env.clone());
593            all_abis.insert(cfg.abi.clone());
594            for family in &cfg.families {
595                all_families.insert(family.clone());
596            }
597            all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
598            if let Some(rustc_abi) = &cfg.rustc_abi {
599                all_rustc_abis.insert(rustc_abi.clone());
600            }
601            all_targets.insert(target.clone());
602        }
603
604        Self {
605            current: Self::get_current_target_config(config, &targets),
606            all_targets,
607            all_archs,
608            all_oses,
609            all_oses_and_envs,
610            all_envs,
611            all_abis,
612            all_families,
613            all_pointer_widths,
614            all_rustc_abis,
615        }
616    }
617
618    fn get_current_target_config(
619        config: &Config,
620        targets: &HashMap<String, TargetCfg>,
621    ) -> TargetCfg {
622        let mut cfg = targets[&config.target].clone();
623
624        // To get the target information for the current target, we take the target spec obtained
625        // from `--print=all-target-specs-json`, and then we enrich it with the information
626        // gathered from `--print=cfg --target=$target`.
627        //
628        // This is done because some parts of the target spec can be overridden with `-C` flags,
629        // which are respected for `--print=cfg` but not for `--print=all-target-specs-json`. The
630        // code below extracts them from `--print=cfg`: make sure to only override fields that can
631        // actually be changed with `-C` flags.
632        for config in
633            rustc_output(config, &["--print=cfg", "--target", &config.target], Default::default())
634                .trim()
635                .lines()
636        {
637            let (name, value) = config
638                .split_once("=\"")
639                .map(|(name, value)| {
640                    (
641                        name,
642                        Some(
643                            value
644                                .strip_suffix('\"')
645                                .expect("key-value pair should be properly quoted"),
646                        ),
647                    )
648                })
649                .unwrap_or_else(|| (config, None));
650
651            match (name, value) {
652                // Can be overridden with `-C panic=$strategy`.
653                ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort,
654                ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind,
655                ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"),
656
657                ("target_has_atomic", Some(width))
658                    if KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width) =>
659                {
660                    cfg.target_has_atomic.insert(width.to_string());
661                }
662                ("target_has_atomic", Some(other)) => {
663                    panic!("unexpected value for `target_has_atomic` cfg: {other:?}")
664                }
665                // Nightly-only std-internal impl detail.
666                ("target_has_atomic", None) => {}
667                _ => {}
668            }
669        }
670
671        cfg
672    }
673}
674
675#[derive(Clone, Debug, serde::Deserialize)]
676#[serde(rename_all = "kebab-case")]
677pub struct TargetCfg {
678    pub(crate) arch: String,
679    #[serde(default = "default_os")]
680    pub(crate) os: String,
681    #[serde(default)]
682    pub(crate) env: String,
683    #[serde(default)]
684    pub(crate) abi: String,
685    #[serde(rename = "target-family", default)]
686    pub(crate) families: Vec<String>,
687    #[serde(rename = "target-pointer-width", deserialize_with = "serde_parse_u32")]
688    pub(crate) pointer_width: u32,
689    #[serde(rename = "target-endian", default)]
690    endian: Endian,
691    #[serde(rename = "panic-strategy", default)]
692    pub(crate) panic: PanicStrategy,
693    #[serde(default)]
694    pub(crate) dynamic_linking: bool,
695    #[serde(rename = "supported-sanitizers", default)]
696    pub(crate) sanitizers: Vec<Sanitizer>,
697    #[serde(rename = "supports-xray", default)]
698    pub(crate) xray: bool,
699    #[serde(default = "default_reloc_model")]
700    pub(crate) relocation_model: String,
701    // NOTE: `rustc_abi` should not be confused with `abi`. `rustc_abi` was introduced in #137037 to
702    // make SSE2 *required* by the ABI (kind of a hack to make a target feature *required* via the
703    // target spec).
704    pub(crate) rustc_abi: Option<String>,
705
706    // Not present in target cfg json output, additional derived information.
707    #[serde(skip)]
708    /// Supported target atomic widths: e.g. `8` to `128` or `ptr`. This is derived from the builtin
709    /// `target_has_atomic` `cfg`s e.g. `target_has_atomic="8"`.
710    pub(crate) target_has_atomic: BTreeSet<String>,
711}
712
713impl TargetCfg {
714    pub(crate) fn os_and_env(&self) -> String {
715        format!("{}-{}", self.os, self.env)
716    }
717}
718
719fn default_os() -> String {
720    "none".into()
721}
722
723fn default_reloc_model() -> String {
724    "pic".into()
725}
726
727#[derive(Eq, PartialEq, Clone, Debug, Default, serde::Deserialize)]
728#[serde(rename_all = "kebab-case")]
729pub enum Endian {
730    #[default]
731    Little,
732    Big,
733}
734
735fn builtin_cfg_names(config: &Config) -> HashSet<String> {
736    rustc_output(
737        config,
738        &["--print=check-cfg", "-Zunstable-options", "--check-cfg=cfg()"],
739        Default::default(),
740    )
741    .lines()
742    .map(|l| if let Some((name, _)) = l.split_once('=') { name.to_string() } else { l.to_string() })
743    .chain(std::iter::once(String::from("test")))
744    .collect()
745}
746
747fn rustc_output(config: &Config, args: &[&str], envs: HashMap<String, String>) -> String {
748    let mut command = Command::new(&config.rustc_path);
749    add_dylib_path(&mut command, iter::once(&config.compile_lib_path));
750    command.args(&config.target_rustcflags).args(args);
751    command.env("RUSTC_BOOTSTRAP", "1");
752    command.envs(envs);
753
754    let output = match command.output() {
755        Ok(output) => output,
756        Err(e) => panic!("error: failed to run {command:?}: {e}"),
757    };
758    if !output.status.success() {
759        panic!(
760            "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}",
761            String::from_utf8(output.stdout).unwrap(),
762            String::from_utf8(output.stderr).unwrap(),
763        );
764    }
765    String::from_utf8(output.stdout).unwrap()
766}
767
768fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
769    let string = String::deserialize(deserializer)?;
770    string.parse().map_err(D::Error::custom)
771}
772
773#[derive(Debug, Clone)]
774pub struct TestPaths {
775    pub file: PathBuf,         // e.g., compile-test/foo/bar/baz.rs
776    pub relative_dir: PathBuf, // e.g., foo/bar
777}
778
779/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
780pub fn expected_output_path(
781    testpaths: &TestPaths,
782    revision: Option<&str>,
783    compare_mode: &Option<CompareMode>,
784    kind: &str,
785) -> PathBuf {
786    assert!(UI_EXTENSIONS.contains(&kind));
787    let mut parts = Vec::new();
788
789    if let Some(x) = revision {
790        parts.push(x);
791    }
792    if let Some(ref x) = *compare_mode {
793        parts.push(x.to_str());
794    }
795    parts.push(kind);
796
797    let extension = parts.join(".");
798    testpaths.file.with_extension(extension)
799}
800
801pub const UI_EXTENSIONS: &[&str] = &[
802    UI_STDERR,
803    UI_SVG,
804    UI_WINDOWS_SVG,
805    UI_STDOUT,
806    UI_FIXED,
807    UI_RUN_STDERR,
808    UI_RUN_STDOUT,
809    UI_STDERR_64,
810    UI_STDERR_32,
811    UI_STDERR_16,
812    UI_COVERAGE,
813    UI_COVERAGE_MAP,
814];
815pub const UI_STDERR: &str = "stderr";
816pub const UI_SVG: &str = "svg";
817pub const UI_WINDOWS_SVG: &str = "windows.svg";
818pub const UI_STDOUT: &str = "stdout";
819pub const UI_FIXED: &str = "fixed";
820pub const UI_RUN_STDERR: &str = "run.stderr";
821pub const UI_RUN_STDOUT: &str = "run.stdout";
822pub const UI_STDERR_64: &str = "64bit.stderr";
823pub const UI_STDERR_32: &str = "32bit.stderr";
824pub const UI_STDERR_16: &str = "16bit.stderr";
825pub const UI_COVERAGE: &str = "coverage";
826pub const UI_COVERAGE_MAP: &str = "cov-map";
827
828/// Absolute path to the directory where all output for all tests in the given `relative_dir` group
829/// should reside. Example:
830///
831/// ```text
832/// /path/to/build/host-tuple/test/ui/relative/
833/// ```
834///
835/// This is created early when tests are collected to avoid race conditions.
836pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf {
837    config.build_test_suite_root.join(relative_dir)
838}
839
840/// Generates a unique name for the test, such as `testname.revision.mode`.
841pub fn output_testname_unique(
842    config: &Config,
843    testpaths: &TestPaths,
844    revision: Option<&str>,
845) -> PathBuf {
846    let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
847    let debugger = config.debugger.as_ref().map_or("", |m| m.to_str());
848    PathBuf::from(&testpaths.file.file_stem().unwrap())
849        .with_extra_extension(config.mode.output_dir_disambiguator())
850        .with_extra_extension(revision.unwrap_or(""))
851        .with_extra_extension(mode)
852        .with_extra_extension(debugger)
853}
854
855/// Absolute path to the directory where all output for the given
856/// test/revision should reside. Example:
857///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/
858pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
859    output_relative_path(config, &testpaths.relative_dir)
860        .join(output_testname_unique(config, testpaths, revision))
861}
862
863/// Absolute path to the base filename used as output for the given
864/// test/revision. Example:
865///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/testname
866pub fn output_base_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
867    output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
868}
869
870/// Absolute path to the directory to use for incremental compilation. Example:
871///   /path/to/build/host-tuple/test/ui/relative/testname.mode/testname.inc
872pub fn incremental_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
873    output_base_name(config, testpaths, revision).with_extension("inc")
874}