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            "loongarch32",
499            "loongarch64",
500            "s390x",
501            // These targets require an additional asm_experimental_arch feature.
502            // "nvptx64", "hexagon", "mips", "mips64", "spirv", "wasm32",
503        ];
504        ASM_SUPPORTED_ARCHS.contains(&self.target_cfg().arch.as_str())
505    }
506
507    pub fn git_config(&self) -> GitConfig<'_> {
508        GitConfig {
509            nightly_branch: &self.nightly_branch,
510            git_merge_commit_email: &self.git_merge_commit_email,
511        }
512    }
513
514    pub fn has_subprocess_support(&self) -> bool {
515        // FIXME(#135928): compiletest is always a **host** tool. Building and running an
516        // capability detection executable against the **target** is not trivial. The short term
517        // solution here is to hard-code some targets to allow/deny, unfortunately.
518
519        let unsupported_target = self.target_cfg().env == "sgx"
520            || matches!(self.target_cfg().arch.as_str(), "wasm32" | "wasm64")
521            || self.target_cfg().os == "emscripten";
522        !unsupported_target
523    }
524}
525
526/// Known widths of `target_has_atomic`.
527pub const KNOWN_TARGET_HAS_ATOMIC_WIDTHS: &[&str] = &["8", "16", "32", "64", "128", "ptr"];
528
529#[derive(Debug, Clone)]
530pub struct TargetCfgs {
531    pub current: TargetCfg,
532    pub all_targets: HashSet<String>,
533    pub all_archs: HashSet<String>,
534    pub all_oses: HashSet<String>,
535    pub all_oses_and_envs: HashSet<String>,
536    pub all_envs: HashSet<String>,
537    pub all_abis: HashSet<String>,
538    pub all_families: HashSet<String>,
539    pub all_pointer_widths: HashSet<String>,
540    pub all_rustc_abis: HashSet<String>,
541}
542
543impl TargetCfgs {
544    fn new(config: &Config) -> TargetCfgs {
545        let mut targets: HashMap<String, TargetCfg> = serde_json::from_str(&rustc_output(
546            config,
547            &["--print=all-target-specs-json", "-Zunstable-options"],
548            Default::default(),
549        ))
550        .unwrap();
551
552        let mut all_targets = HashSet::new();
553        let mut all_archs = HashSet::new();
554        let mut all_oses = HashSet::new();
555        let mut all_oses_and_envs = HashSet::new();
556        let mut all_envs = HashSet::new();
557        let mut all_abis = HashSet::new();
558        let mut all_families = HashSet::new();
559        let mut all_pointer_widths = HashSet::new();
560        // NOTE: for distinction between `abi` and `rustc_abi`, see comment on
561        // `TargetCfg::rustc_abi`.
562        let mut all_rustc_abis = HashSet::new();
563
564        // If current target is not included in the `--print=all-target-specs-json` output,
565        // we check whether it is a custom target from the user or a synthetic target from bootstrap.
566        if !targets.contains_key(&config.target) {
567            let mut envs: HashMap<String, String> = HashMap::new();
568
569            if let Ok(t) = std::env::var("RUST_TARGET_PATH") {
570                envs.insert("RUST_TARGET_PATH".into(), t);
571            }
572
573            // This returns false only when the target is neither a synthetic target
574            // nor a custom target from the user, indicating it is most likely invalid.
575            if config.target.ends_with(".json") || !envs.is_empty() {
576                targets.insert(
577                    config.target.clone(),
578                    serde_json::from_str(&rustc_output(
579                        config,
580                        &[
581                            "--print=target-spec-json",
582                            "-Zunstable-options",
583                            "--target",
584                            &config.target,
585                        ],
586                        envs,
587                    ))
588                    .unwrap(),
589                );
590            }
591        }
592
593        for (target, cfg) in targets.iter() {
594            all_archs.insert(cfg.arch.clone());
595            all_oses.insert(cfg.os.clone());
596            all_oses_and_envs.insert(cfg.os_and_env());
597            all_envs.insert(cfg.env.clone());
598            all_abis.insert(cfg.abi.clone());
599            for family in &cfg.families {
600                all_families.insert(family.clone());
601            }
602            all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
603            if let Some(rustc_abi) = &cfg.rustc_abi {
604                all_rustc_abis.insert(rustc_abi.clone());
605            }
606            all_targets.insert(target.clone());
607        }
608
609        Self {
610            current: Self::get_current_target_config(config, &targets),
611            all_targets,
612            all_archs,
613            all_oses,
614            all_oses_and_envs,
615            all_envs,
616            all_abis,
617            all_families,
618            all_pointer_widths,
619            all_rustc_abis,
620        }
621    }
622
623    fn get_current_target_config(
624        config: &Config,
625        targets: &HashMap<String, TargetCfg>,
626    ) -> TargetCfg {
627        let mut cfg = targets[&config.target].clone();
628
629        // To get the target information for the current target, we take the target spec obtained
630        // from `--print=all-target-specs-json`, and then we enrich it with the information
631        // gathered from `--print=cfg --target=$target`.
632        //
633        // This is done because some parts of the target spec can be overridden with `-C` flags,
634        // which are respected for `--print=cfg` but not for `--print=all-target-specs-json`. The
635        // code below extracts them from `--print=cfg`: make sure to only override fields that can
636        // actually be changed with `-C` flags.
637        for config in
638            rustc_output(config, &["--print=cfg", "--target", &config.target], Default::default())
639                .trim()
640                .lines()
641        {
642            let (name, value) = config
643                .split_once("=\"")
644                .map(|(name, value)| {
645                    (
646                        name,
647                        Some(
648                            value
649                                .strip_suffix('\"')
650                                .expect("key-value pair should be properly quoted"),
651                        ),
652                    )
653                })
654                .unwrap_or_else(|| (config, None));
655
656            match (name, value) {
657                // Can be overridden with `-C panic=$strategy`.
658                ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort,
659                ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind,
660                ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"),
661
662                ("target_has_atomic", Some(width))
663                    if KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width) =>
664                {
665                    cfg.target_has_atomic.insert(width.to_string());
666                }
667                ("target_has_atomic", Some(other)) => {
668                    panic!("unexpected value for `target_has_atomic` cfg: {other:?}")
669                }
670                // Nightly-only std-internal impl detail.
671                ("target_has_atomic", None) => {}
672                _ => {}
673            }
674        }
675
676        cfg
677    }
678}
679
680#[derive(Clone, Debug, serde::Deserialize)]
681#[serde(rename_all = "kebab-case")]
682pub struct TargetCfg {
683    pub(crate) arch: String,
684    #[serde(default = "default_os")]
685    pub(crate) os: String,
686    #[serde(default)]
687    pub(crate) env: String,
688    #[serde(default)]
689    pub(crate) abi: String,
690    #[serde(rename = "target-family", default)]
691    pub(crate) families: Vec<String>,
692    #[serde(rename = "target-pointer-width", deserialize_with = "serde_parse_u32")]
693    pub(crate) pointer_width: u32,
694    #[serde(rename = "target-endian", default)]
695    endian: Endian,
696    #[serde(rename = "panic-strategy", default)]
697    pub(crate) panic: PanicStrategy,
698    #[serde(default)]
699    pub(crate) dynamic_linking: bool,
700    #[serde(rename = "supported-sanitizers", default)]
701    pub(crate) sanitizers: Vec<Sanitizer>,
702    #[serde(rename = "supports-xray", default)]
703    pub(crate) xray: bool,
704    #[serde(default = "default_reloc_model")]
705    pub(crate) relocation_model: String,
706    // NOTE: `rustc_abi` should not be confused with `abi`. `rustc_abi` was introduced in #137037 to
707    // make SSE2 *required* by the ABI (kind of a hack to make a target feature *required* via the
708    // target spec).
709    pub(crate) rustc_abi: Option<String>,
710
711    // Not present in target cfg json output, additional derived information.
712    #[serde(skip)]
713    /// Supported target atomic widths: e.g. `8` to `128` or `ptr`. This is derived from the builtin
714    /// `target_has_atomic` `cfg`s e.g. `target_has_atomic="8"`.
715    pub(crate) target_has_atomic: BTreeSet<String>,
716}
717
718impl TargetCfg {
719    pub(crate) fn os_and_env(&self) -> String {
720        format!("{}-{}", self.os, self.env)
721    }
722}
723
724fn default_os() -> String {
725    "none".into()
726}
727
728fn default_reloc_model() -> String {
729    "pic".into()
730}
731
732#[derive(Eq, PartialEq, Clone, Debug, Default, serde::Deserialize)]
733#[serde(rename_all = "kebab-case")]
734pub enum Endian {
735    #[default]
736    Little,
737    Big,
738}
739
740fn builtin_cfg_names(config: &Config) -> HashSet<String> {
741    rustc_output(
742        config,
743        &["--print=check-cfg", "-Zunstable-options", "--check-cfg=cfg()"],
744        Default::default(),
745    )
746    .lines()
747    .map(|l| if let Some((name, _)) = l.split_once('=') { name.to_string() } else { l.to_string() })
748    .chain(std::iter::once(String::from("test")))
749    .collect()
750}
751
752pub const KNOWN_CRATE_TYPES: &[&str] =
753    &["bin", "cdylib", "dylib", "lib", "proc-macro", "rlib", "staticlib"];
754
755fn supported_crate_types(config: &Config) -> HashSet<String> {
756    let crate_types: HashSet<_> = rustc_output(
757        config,
758        &["--target", &config.target, "--print=supported-crate-types", "-Zunstable-options"],
759        Default::default(),
760    )
761    .lines()
762    .map(|l| l.to_string())
763    .collect();
764
765    for crate_type in crate_types.iter() {
766        assert!(
767            KNOWN_CRATE_TYPES.contains(&crate_type.as_str()),
768            "unexpected crate type `{}`: known crate types are {:?}",
769            crate_type,
770            KNOWN_CRATE_TYPES
771        );
772    }
773
774    crate_types
775}
776
777fn rustc_output(config: &Config, args: &[&str], envs: HashMap<String, String>) -> String {
778    let mut command = Command::new(&config.rustc_path);
779    add_dylib_path(&mut command, iter::once(&config.compile_lib_path));
780    command.args(&config.target_rustcflags).args(args);
781    command.env("RUSTC_BOOTSTRAP", "1");
782    command.envs(envs);
783
784    let output = match command.output() {
785        Ok(output) => output,
786        Err(e) => panic!("error: failed to run {command:?}: {e}"),
787    };
788    if !output.status.success() {
789        panic!(
790            "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}",
791            String::from_utf8(output.stdout).unwrap(),
792            String::from_utf8(output.stderr).unwrap(),
793        );
794    }
795    String::from_utf8(output.stdout).unwrap()
796}
797
798fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
799    let string = String::deserialize(deserializer)?;
800    string.parse().map_err(D::Error::custom)
801}
802
803#[derive(Debug, Clone)]
804pub struct TestPaths {
805    pub file: Utf8PathBuf,         // e.g., compile-test/foo/bar/baz.rs
806    pub relative_dir: Utf8PathBuf, // e.g., foo/bar
807}
808
809/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
810pub fn expected_output_path(
811    testpaths: &TestPaths,
812    revision: Option<&str>,
813    compare_mode: &Option<CompareMode>,
814    kind: &str,
815) -> Utf8PathBuf {
816    assert!(UI_EXTENSIONS.contains(&kind));
817    let mut parts = Vec::new();
818
819    if let Some(x) = revision {
820        parts.push(x);
821    }
822    if let Some(ref x) = *compare_mode {
823        parts.push(x.to_str());
824    }
825    parts.push(kind);
826
827    let extension = parts.join(".");
828    testpaths.file.with_extension(extension)
829}
830
831pub const UI_EXTENSIONS: &[&str] = &[
832    UI_STDERR,
833    UI_SVG,
834    UI_WINDOWS_SVG,
835    UI_STDOUT,
836    UI_FIXED,
837    UI_RUN_STDERR,
838    UI_RUN_STDOUT,
839    UI_STDERR_64,
840    UI_STDERR_32,
841    UI_STDERR_16,
842    UI_COVERAGE,
843    UI_COVERAGE_MAP,
844];
845pub const UI_STDERR: &str = "stderr";
846pub const UI_SVG: &str = "svg";
847pub const UI_WINDOWS_SVG: &str = "windows.svg";
848pub const UI_STDOUT: &str = "stdout";
849pub const UI_FIXED: &str = "fixed";
850pub const UI_RUN_STDERR: &str = "run.stderr";
851pub const UI_RUN_STDOUT: &str = "run.stdout";
852pub const UI_STDERR_64: &str = "64bit.stderr";
853pub const UI_STDERR_32: &str = "32bit.stderr";
854pub const UI_STDERR_16: &str = "16bit.stderr";
855pub const UI_COVERAGE: &str = "coverage";
856pub const UI_COVERAGE_MAP: &str = "cov-map";
857
858/// Absolute path to the directory where all output for all tests in the given `relative_dir` group
859/// should reside. Example:
860///
861/// ```text
862/// /path/to/build/host-tuple/test/ui/relative/
863/// ```
864///
865/// This is created early when tests are collected to avoid race conditions.
866pub fn output_relative_path(config: &Config, relative_dir: &Utf8Path) -> Utf8PathBuf {
867    config.build_test_suite_root.join(relative_dir)
868}
869
870/// Generates a unique name for the test, such as `testname.revision.mode`.
871pub fn output_testname_unique(
872    config: &Config,
873    testpaths: &TestPaths,
874    revision: Option<&str>,
875) -> Utf8PathBuf {
876    let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
877    let debugger = config.debugger.as_ref().map_or("", |m| m.to_str());
878    Utf8PathBuf::from(&testpaths.file.file_stem().unwrap())
879        .with_extra_extension(config.mode.output_dir_disambiguator())
880        .with_extra_extension(revision.unwrap_or(""))
881        .with_extra_extension(mode)
882        .with_extra_extension(debugger)
883}
884
885/// Absolute path to the directory where all output for the given
886/// test/revision should reside. Example:
887///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/
888pub fn output_base_dir(
889    config: &Config,
890    testpaths: &TestPaths,
891    revision: Option<&str>,
892) -> Utf8PathBuf {
893    output_relative_path(config, &testpaths.relative_dir)
894        .join(output_testname_unique(config, testpaths, revision))
895}
896
897/// Absolute path to the base filename used as output for the given
898/// test/revision. Example:
899///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/testname
900pub fn output_base_name(
901    config: &Config,
902    testpaths: &TestPaths,
903    revision: Option<&str>,
904) -> Utf8PathBuf {
905    output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
906}
907
908/// Absolute path to the directory to use for incremental compilation. Example:
909///   /path/to/build/host-tuple/test/ui/relative/testname.mode/testname.inc
910pub fn incremental_dir(
911    config: &Config,
912    testpaths: &TestPaths,
913    revision: Option<&str>,
914) -> Utf8PathBuf {
915    output_base_name(config, testpaths, revision).with_extension("inc")
916}