compiletest/
common.rs

1use std::collections::{BTreeSet, HashMap, HashSet};
2use std::process::Command;
3use std::str::FromStr;
4use std::sync::OnceLock;
5use std::{fmt, iter};
6
7use build_helper::git::GitConfig;
8use camino::{Utf8Path, Utf8PathBuf};
9use semver::Version;
10use serde::de::{Deserialize, Deserializer, Error as _};
11
12pub use self::Mode::*;
13use crate::executor::{ColorConfig, OutputFormat};
14use crate::util::{Utf8PathBufExt, add_dylib_path};
15
16macro_rules! string_enum {
17    ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $repr:expr,)* }) => {
18        $(#[$meta])*
19        $vis enum $name {
20            $($variant,)*
21        }
22
23        impl $name {
24            $vis const VARIANTS: &'static [Self] = &[$(Self::$variant,)*];
25            $vis const STR_VARIANTS: &'static [&'static str] = &[$(Self::$variant.to_str(),)*];
26
27            $vis const fn to_str(&self) -> &'static str {
28                match self {
29                    $(Self::$variant => $repr,)*
30                }
31            }
32        }
33
34        impl fmt::Display for $name {
35            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36                fmt::Display::fmt(self.to_str(), f)
37            }
38        }
39
40        impl FromStr for $name {
41            type Err = String;
42
43            fn from_str(s: &str) -> Result<Self, Self::Err> {
44                match s {
45                    $($repr => Ok(Self::$variant),)*
46                    _ => Err(format!(concat!("unknown `", stringify!($name), "` variant: `{}`"), s)),
47                }
48            }
49        }
50    }
51}
52
53// Make the macro visible outside of this module, for tests.
54#[cfg(test)]
55pub(crate) use string_enum;
56
57string_enum! {
58    #[derive(Clone, Copy, PartialEq, Debug)]
59    pub enum Mode {
60        Pretty => "pretty",
61        DebugInfo => "debuginfo",
62        Codegen => "codegen",
63        Rustdoc => "rustdoc",
64        RustdocJson => "rustdoc-json",
65        CodegenUnits => "codegen-units",
66        Incremental => "incremental",
67        RunMake => "run-make",
68        Ui => "ui",
69        RustdocJs => "rustdoc-js",
70        MirOpt => "mir-opt",
71        Assembly => "assembly",
72        CoverageMap => "coverage-map",
73        CoverageRun => "coverage-run",
74        Crashes => "crashes",
75    }
76}
77
78impl Default for Mode {
79    fn default() -> Self {
80        Mode::Ui
81    }
82}
83
84impl Mode {
85    pub fn aux_dir_disambiguator(self) -> &'static str {
86        // Pretty-printing tests could run concurrently, and if they do,
87        // they need to keep their output segregated.
88        match self {
89            Pretty => ".pretty",
90            _ => "",
91        }
92    }
93
94    pub fn output_dir_disambiguator(self) -> &'static str {
95        // Coverage tests use the same test files for multiple test modes,
96        // so each mode should have a separate output directory.
97        match self {
98            CoverageMap | CoverageRun => self.to_str(),
99            _ => "",
100        }
101    }
102}
103
104string_enum! {
105    #[derive(Clone, Copy, PartialEq, Debug, Hash)]
106    pub enum PassMode {
107        Check => "check",
108        Build => "build",
109        Run => "run",
110    }
111}
112
113#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
114pub enum FailMode {
115    Check,
116    Build,
117    Run,
118}
119
120string_enum! {
121    #[derive(Clone, Debug, PartialEq)]
122    pub enum CompareMode {
123        Polonius => "polonius",
124        NextSolver => "next-solver",
125        NextSolverCoherence => "next-solver-coherence",
126        SplitDwarf => "split-dwarf",
127        SplitDwarfSingle => "split-dwarf-single",
128    }
129}
130
131string_enum! {
132    #[derive(Clone, Copy, Debug, PartialEq)]
133    pub enum Debugger {
134        Cdb => "cdb",
135        Gdb => "gdb",
136        Lldb => "lldb",
137    }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Default, serde::Deserialize)]
141#[serde(rename_all = "kebab-case")]
142pub enum PanicStrategy {
143    #[default]
144    Unwind,
145    Abort,
146}
147
148impl PanicStrategy {
149    pub(crate) fn for_miropt_test_tools(&self) -> miropt_test_tools::PanicStrategy {
150        match self {
151            PanicStrategy::Unwind => miropt_test_tools::PanicStrategy::Unwind,
152            PanicStrategy::Abort => miropt_test_tools::PanicStrategy::Abort,
153        }
154    }
155}
156
157#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
158#[serde(rename_all = "kebab-case")]
159pub enum Sanitizer {
160    Address,
161    Cfi,
162    Dataflow,
163    Kcfi,
164    KernelAddress,
165    Leak,
166    Memory,
167    Memtag,
168    Safestack,
169    ShadowCallStack,
170    Thread,
171    Hwaddress,
172}
173
174/// Configuration for compiletest
175#[derive(Debug, Default, Clone)]
176pub struct Config {
177    /// `true` to overwrite stderr/stdout files instead of complaining about changes in output.
178    pub bless: bool,
179
180    /// Stop as soon as possible after any test fails.
181    /// May run a few more tests before stopping, due to threading.
182    pub fail_fast: bool,
183
184    /// The library paths required for running the compiler.
185    pub compile_lib_path: Utf8PathBuf,
186
187    /// The library paths required for running compiled programs.
188    pub run_lib_path: Utf8PathBuf,
189
190    /// The rustc executable.
191    pub rustc_path: Utf8PathBuf,
192
193    /// The cargo executable.
194    pub cargo_path: Option<Utf8PathBuf>,
195
196    /// Rustc executable used to compile run-make recipes.
197    pub stage0_rustc_path: Option<Utf8PathBuf>,
198
199    /// The rustdoc executable.
200    pub rustdoc_path: Option<Utf8PathBuf>,
201
202    /// The coverage-dump executable.
203    pub coverage_dump_path: Option<Utf8PathBuf>,
204
205    /// The Python executable to use for LLDB and htmldocck.
206    pub python: String,
207
208    /// The jsondocck executable.
209    pub jsondocck_path: Option<String>,
210
211    /// The jsondoclint executable.
212    pub jsondoclint_path: Option<String>,
213
214    /// The LLVM `FileCheck` binary path.
215    pub llvm_filecheck: Option<Utf8PathBuf>,
216
217    /// Path to LLVM's bin directory.
218    pub llvm_bin_dir: Option<Utf8PathBuf>,
219
220    /// The path to the Clang executable to run Clang-based tests with. If
221    /// `None` then these tests will be ignored.
222    pub run_clang_based_tests_with: Option<String>,
223
224    /// The directory containing the sources.
225    pub src_root: Utf8PathBuf,
226    /// The directory containing the test suite sources. Must be a subdirectory of `src_root`.
227    pub src_test_suite_root: Utf8PathBuf,
228
229    /// Root build directory (e.g. `build/`).
230    pub build_root: Utf8PathBuf,
231    /// Test suite specific build directory (e.g. `build/host/test/ui/`).
232    pub build_test_suite_root: Utf8PathBuf,
233
234    /// The directory containing the compiler sysroot
235    pub sysroot_base: Utf8PathBuf,
236
237    /// The number of the stage under test.
238    pub stage: u32,
239    /// The id of the stage under test (stage1-xxx, etc).
240    pub stage_id: String,
241
242    /// The test mode, e.g. ui or debuginfo.
243    pub mode: Mode,
244
245    /// The test suite (essentially which directory is running, but without the
246    /// directory prefix such as tests)
247    pub suite: String,
248
249    /// The debugger to use in debuginfo mode. Unset otherwise.
250    pub debugger: Option<Debugger>,
251
252    /// Run ignored tests
253    pub run_ignored: bool,
254
255    /// Whether rustc was built with debug assertions.
256    pub with_rustc_debug_assertions: bool,
257
258    /// Whether std was built with debug assertions.
259    pub with_std_debug_assertions: bool,
260
261    /// Only run tests that match these filters
262    pub filters: Vec<String>,
263
264    /// Skip tests matching these substrings. Corresponds to
265    /// `test::TestOpts::skip`. `filter_exact` does not apply to these flags.
266    pub skip: Vec<String>,
267
268    /// Exactly match the filter, rather than a substring
269    pub filter_exact: bool,
270
271    /// Force the pass mode of a check/build/run-pass test to this mode.
272    pub force_pass_mode: Option<PassMode>,
273
274    /// Explicitly enable or disable running.
275    pub run: Option<bool>,
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<Utf8PathBuf>,
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: Utf8PathBuf,
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<Utf8PathBuf>,
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    pub supported_crate_types: OnceLock<HashSet<String>>,
398
399    pub nocapture: bool,
400
401    // Needed both to construct build_helper::git::GitConfig
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: Utf8PathBuf,
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    /// Get the list of crate types that the target platform supports.
475    pub fn supported_crate_types(&self) -> &HashSet<String> {
476        self.supported_crate_types.get_or_init(|| supported_crate_types(self))
477    }
478
479    pub fn has_threads(&self) -> bool {
480        // Wasm targets don't have threads unless `-threads` is in the target
481        // name, such as `wasm32-wasip1-threads`.
482        if self.target.starts_with("wasm") {
483            return self.target.contains("threads");
484        }
485        true
486    }
487
488    pub fn has_asm_support(&self) -> bool {
489        // This should match the stable list in `LoweringContext::lower_inline_asm`.
490        static ASM_SUPPORTED_ARCHS: &[&str] = &[
491            "x86",
492            "x86_64",
493            "arm",
494            "aarch64",
495            "arm64ec",
496            "riscv32",
497            "riscv64",
498            "loongarch64",
499            "s390x",
500            // These targets require an additional asm_experimental_arch feature.
501            // "nvptx64", "hexagon", "mips", "mips64", "spirv", "wasm32",
502        ];
503        ASM_SUPPORTED_ARCHS.contains(&self.target_cfg().arch.as_str())
504    }
505
506    pub fn git_config(&self) -> GitConfig<'_> {
507        GitConfig {
508            nightly_branch: &self.nightly_branch,
509            git_merge_commit_email: &self.git_merge_commit_email,
510        }
511    }
512
513    pub fn has_subprocess_support(&self) -> bool {
514        // FIXME(#135928): compiletest is always a **host** tool. Building and running an
515        // capability detection executable against the **target** is not trivial. The short term
516        // solution here is to hard-code some targets to allow/deny, unfortunately.
517
518        let unsupported_target = self.target_cfg().env == "sgx"
519            || matches!(self.target_cfg().arch.as_str(), "wasm32" | "wasm64")
520            || self.target_cfg().os == "emscripten";
521        !unsupported_target
522    }
523}
524
525/// Known widths of `target_has_atomic`.
526pub const KNOWN_TARGET_HAS_ATOMIC_WIDTHS: &[&str] = &["8", "16", "32", "64", "128", "ptr"];
527
528#[derive(Debug, Clone)]
529pub struct TargetCfgs {
530    pub current: TargetCfg,
531    pub all_targets: HashSet<String>,
532    pub all_archs: HashSet<String>,
533    pub all_oses: HashSet<String>,
534    pub all_oses_and_envs: HashSet<String>,
535    pub all_envs: HashSet<String>,
536    pub all_abis: HashSet<String>,
537    pub all_families: HashSet<String>,
538    pub all_pointer_widths: HashSet<String>,
539    pub all_rustc_abis: HashSet<String>,
540}
541
542impl TargetCfgs {
543    fn new(config: &Config) -> TargetCfgs {
544        let mut targets: HashMap<String, TargetCfg> = serde_json::from_str(&rustc_output(
545            config,
546            &["--print=all-target-specs-json", "-Zunstable-options"],
547            Default::default(),
548        ))
549        .unwrap();
550
551        let mut all_targets = HashSet::new();
552        let mut all_archs = HashSet::new();
553        let mut all_oses = HashSet::new();
554        let mut all_oses_and_envs = HashSet::new();
555        let mut all_envs = HashSet::new();
556        let mut all_abis = HashSet::new();
557        let mut all_families = HashSet::new();
558        let mut all_pointer_widths = HashSet::new();
559        // NOTE: for distinction between `abi` and `rustc_abi`, see comment on
560        // `TargetCfg::rustc_abi`.
561        let mut all_rustc_abis = HashSet::new();
562
563        // If current target is not included in the `--print=all-target-specs-json` output,
564        // we check whether it is a custom target from the user or a synthetic target from bootstrap.
565        if !targets.contains_key(&config.target) {
566            let mut envs: HashMap<String, String> = HashMap::new();
567
568            if let Ok(t) = std::env::var("RUST_TARGET_PATH") {
569                envs.insert("RUST_TARGET_PATH".into(), t);
570            }
571
572            // This returns false only when the target is neither a synthetic target
573            // nor a custom target from the user, indicating it is most likely invalid.
574            if config.target.ends_with(".json") || !envs.is_empty() {
575                targets.insert(
576                    config.target.clone(),
577                    serde_json::from_str(&rustc_output(
578                        config,
579                        &[
580                            "--print=target-spec-json",
581                            "-Zunstable-options",
582                            "--target",
583                            &config.target,
584                        ],
585                        envs,
586                    ))
587                    .unwrap(),
588                );
589            }
590        }
591
592        for (target, cfg) in targets.iter() {
593            all_archs.insert(cfg.arch.clone());
594            all_oses.insert(cfg.os.clone());
595            all_oses_and_envs.insert(cfg.os_and_env());
596            all_envs.insert(cfg.env.clone());
597            all_abis.insert(cfg.abi.clone());
598            for family in &cfg.families {
599                all_families.insert(family.clone());
600            }
601            all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
602            if let Some(rustc_abi) = &cfg.rustc_abi {
603                all_rustc_abis.insert(rustc_abi.clone());
604            }
605            all_targets.insert(target.clone());
606        }
607
608        Self {
609            current: Self::get_current_target_config(config, &targets),
610            all_targets,
611            all_archs,
612            all_oses,
613            all_oses_and_envs,
614            all_envs,
615            all_abis,
616            all_families,
617            all_pointer_widths,
618            all_rustc_abis,
619        }
620    }
621
622    fn get_current_target_config(
623        config: &Config,
624        targets: &HashMap<String, TargetCfg>,
625    ) -> TargetCfg {
626        let mut cfg = targets[&config.target].clone();
627
628        // To get the target information for the current target, we take the target spec obtained
629        // from `--print=all-target-specs-json`, and then we enrich it with the information
630        // gathered from `--print=cfg --target=$target`.
631        //
632        // This is done because some parts of the target spec can be overridden with `-C` flags,
633        // which are respected for `--print=cfg` but not for `--print=all-target-specs-json`. The
634        // code below extracts them from `--print=cfg`: make sure to only override fields that can
635        // actually be changed with `-C` flags.
636        for config in
637            rustc_output(config, &["--print=cfg", "--target", &config.target], Default::default())
638                .trim()
639                .lines()
640        {
641            let (name, value) = config
642                .split_once("=\"")
643                .map(|(name, value)| {
644                    (
645                        name,
646                        Some(
647                            value
648                                .strip_suffix('\"')
649                                .expect("key-value pair should be properly quoted"),
650                        ),
651                    )
652                })
653                .unwrap_or_else(|| (config, None));
654
655            match (name, value) {
656                // Can be overridden with `-C panic=$strategy`.
657                ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort,
658                ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind,
659                ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"),
660
661                ("target_has_atomic", Some(width))
662                    if KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width) =>
663                {
664                    cfg.target_has_atomic.insert(width.to_string());
665                }
666                ("target_has_atomic", Some(other)) => {
667                    panic!("unexpected value for `target_has_atomic` cfg: {other:?}")
668                }
669                // Nightly-only std-internal impl detail.
670                ("target_has_atomic", None) => {}
671                _ => {}
672            }
673        }
674
675        cfg
676    }
677}
678
679#[derive(Clone, Debug, serde::Deserialize)]
680#[serde(rename_all = "kebab-case")]
681pub struct TargetCfg {
682    pub(crate) arch: String,
683    #[serde(default = "default_os")]
684    pub(crate) os: String,
685    #[serde(default)]
686    pub(crate) env: String,
687    #[serde(default)]
688    pub(crate) abi: String,
689    #[serde(rename = "target-family", default)]
690    pub(crate) families: Vec<String>,
691    #[serde(rename = "target-pointer-width", deserialize_with = "serde_parse_u32")]
692    pub(crate) pointer_width: u32,
693    #[serde(rename = "target-endian", default)]
694    endian: Endian,
695    #[serde(rename = "panic-strategy", default)]
696    pub(crate) panic: PanicStrategy,
697    #[serde(default)]
698    pub(crate) dynamic_linking: bool,
699    #[serde(rename = "supported-sanitizers", default)]
700    pub(crate) sanitizers: Vec<Sanitizer>,
701    #[serde(rename = "supports-xray", default)]
702    pub(crate) xray: bool,
703    #[serde(default = "default_reloc_model")]
704    pub(crate) relocation_model: String,
705    // NOTE: `rustc_abi` should not be confused with `abi`. `rustc_abi` was introduced in #137037 to
706    // make SSE2 *required* by the ABI (kind of a hack to make a target feature *required* via the
707    // target spec).
708    pub(crate) rustc_abi: Option<String>,
709
710    // Not present in target cfg json output, additional derived information.
711    #[serde(skip)]
712    /// Supported target atomic widths: e.g. `8` to `128` or `ptr`. This is derived from the builtin
713    /// `target_has_atomic` `cfg`s e.g. `target_has_atomic="8"`.
714    pub(crate) target_has_atomic: BTreeSet<String>,
715}
716
717impl TargetCfg {
718    pub(crate) fn os_and_env(&self) -> String {
719        format!("{}-{}", self.os, self.env)
720    }
721}
722
723fn default_os() -> String {
724    "none".into()
725}
726
727fn default_reloc_model() -> String {
728    "pic".into()
729}
730
731#[derive(Eq, PartialEq, Clone, Debug, Default, serde::Deserialize)]
732#[serde(rename_all = "kebab-case")]
733pub enum Endian {
734    #[default]
735    Little,
736    Big,
737}
738
739fn builtin_cfg_names(config: &Config) -> HashSet<String> {
740    rustc_output(
741        config,
742        &["--print=check-cfg", "-Zunstable-options", "--check-cfg=cfg()"],
743        Default::default(),
744    )
745    .lines()
746    .map(|l| if let Some((name, _)) = l.split_once('=') { name.to_string() } else { l.to_string() })
747    .chain(std::iter::once(String::from("test")))
748    .collect()
749}
750
751pub const KNOWN_CRATE_TYPES: &[&str] =
752    &["bin", "cdylib", "dylib", "lib", "proc-macro", "rlib", "staticlib"];
753
754fn supported_crate_types(config: &Config) -> HashSet<String> {
755    let crate_types: HashSet<_> = rustc_output(
756        config,
757        &["--target", &config.target, "--print=supported-crate-types", "-Zunstable-options"],
758        Default::default(),
759    )
760    .lines()
761    .map(|l| l.to_string())
762    .collect();
763
764    for crate_type in crate_types.iter() {
765        assert!(
766            KNOWN_CRATE_TYPES.contains(&crate_type.as_str()),
767            "unexpected crate type `{}`: known crate types are {:?}",
768            crate_type,
769            KNOWN_CRATE_TYPES
770        );
771    }
772
773    crate_types
774}
775
776fn rustc_output(config: &Config, args: &[&str], envs: HashMap<String, String>) -> String {
777    let mut command = Command::new(&config.rustc_path);
778    add_dylib_path(&mut command, iter::once(&config.compile_lib_path));
779    command.args(&config.target_rustcflags).args(args);
780    command.env("RUSTC_BOOTSTRAP", "1");
781    command.envs(envs);
782
783    let output = match command.output() {
784        Ok(output) => output,
785        Err(e) => panic!("error: failed to run {command:?}: {e}"),
786    };
787    if !output.status.success() {
788        panic!(
789            "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}",
790            String::from_utf8(output.stdout).unwrap(),
791            String::from_utf8(output.stderr).unwrap(),
792        );
793    }
794    String::from_utf8(output.stdout).unwrap()
795}
796
797fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
798    let string = String::deserialize(deserializer)?;
799    string.parse().map_err(D::Error::custom)
800}
801
802#[derive(Debug, Clone)]
803pub struct TestPaths {
804    pub file: Utf8PathBuf,         // e.g., compile-test/foo/bar/baz.rs
805    pub relative_dir: Utf8PathBuf, // e.g., foo/bar
806}
807
808/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
809pub fn expected_output_path(
810    testpaths: &TestPaths,
811    revision: Option<&str>,
812    compare_mode: &Option<CompareMode>,
813    kind: &str,
814) -> Utf8PathBuf {
815    assert!(UI_EXTENSIONS.contains(&kind));
816    let mut parts = Vec::new();
817
818    if let Some(x) = revision {
819        parts.push(x);
820    }
821    if let Some(ref x) = *compare_mode {
822        parts.push(x.to_str());
823    }
824    parts.push(kind);
825
826    let extension = parts.join(".");
827    testpaths.file.with_extension(extension)
828}
829
830pub const UI_EXTENSIONS: &[&str] = &[
831    UI_STDERR,
832    UI_SVG,
833    UI_WINDOWS_SVG,
834    UI_STDOUT,
835    UI_FIXED,
836    UI_RUN_STDERR,
837    UI_RUN_STDOUT,
838    UI_STDERR_64,
839    UI_STDERR_32,
840    UI_STDERR_16,
841    UI_COVERAGE,
842    UI_COVERAGE_MAP,
843];
844pub const UI_STDERR: &str = "stderr";
845pub const UI_SVG: &str = "svg";
846pub const UI_WINDOWS_SVG: &str = "windows.svg";
847pub const UI_STDOUT: &str = "stdout";
848pub const UI_FIXED: &str = "fixed";
849pub const UI_RUN_STDERR: &str = "run.stderr";
850pub const UI_RUN_STDOUT: &str = "run.stdout";
851pub const UI_STDERR_64: &str = "64bit.stderr";
852pub const UI_STDERR_32: &str = "32bit.stderr";
853pub const UI_STDERR_16: &str = "16bit.stderr";
854pub const UI_COVERAGE: &str = "coverage";
855pub const UI_COVERAGE_MAP: &str = "cov-map";
856
857/// Absolute path to the directory where all output for all tests in the given `relative_dir` group
858/// should reside. Example:
859///
860/// ```text
861/// /path/to/build/host-tuple/test/ui/relative/
862/// ```
863///
864/// This is created early when tests are collected to avoid race conditions.
865pub fn output_relative_path(config: &Config, relative_dir: &Utf8Path) -> Utf8PathBuf {
866    config.build_test_suite_root.join(relative_dir)
867}
868
869/// Generates a unique name for the test, such as `testname.revision.mode`.
870pub fn output_testname_unique(
871    config: &Config,
872    testpaths: &TestPaths,
873    revision: Option<&str>,
874) -> Utf8PathBuf {
875    let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
876    let debugger = config.debugger.as_ref().map_or("", |m| m.to_str());
877    Utf8PathBuf::from(&testpaths.file.file_stem().unwrap())
878        .with_extra_extension(config.mode.output_dir_disambiguator())
879        .with_extra_extension(revision.unwrap_or(""))
880        .with_extra_extension(mode)
881        .with_extra_extension(debugger)
882}
883
884/// Absolute path to the directory where all output for the given
885/// test/revision should reside. Example:
886///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/
887pub fn output_base_dir(
888    config: &Config,
889    testpaths: &TestPaths,
890    revision: Option<&str>,
891) -> Utf8PathBuf {
892    output_relative_path(config, &testpaths.relative_dir)
893        .join(output_testname_unique(config, testpaths, revision))
894}
895
896/// Absolute path to the base filename used as output for the given
897/// test/revision. Example:
898///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/testname
899pub fn output_base_name(
900    config: &Config,
901    testpaths: &TestPaths,
902    revision: Option<&str>,
903) -> Utf8PathBuf {
904    output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
905}
906
907/// Absolute path to the directory to use for incremental compilation. Example:
908///   /path/to/build/host-tuple/test/ui/relative/testname.mode/testname.inc
909pub fn incremental_dir(
910    config: &Config,
911    testpaths: &TestPaths,
912    revision: Option<&str>,
913) -> Utf8PathBuf {
914    output_base_name(config, testpaths, revision).with_extension("inc")
915}