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    /// The rustdoc executable.
194    pub rustdoc_path: Option<PathBuf>,
195
196    /// The coverage-dump executable.
197    pub coverage_dump_path: Option<PathBuf>,
198
199    /// The Python executable to use for LLDB and htmldocck.
200    pub python: String,
201
202    /// The jsondocck executable.
203    pub jsondocck_path: Option<String>,
204
205    /// The jsondoclint executable.
206    pub jsondoclint_path: Option<String>,
207
208    /// The LLVM `FileCheck` binary path.
209    pub llvm_filecheck: Option<PathBuf>,
210
211    /// Path to LLVM's bin directory.
212    pub llvm_bin_dir: Option<PathBuf>,
213
214    /// The path to the Clang executable to run Clang-based tests with. If
215    /// `None` then these tests will be ignored.
216    pub run_clang_based_tests_with: Option<String>,
217
218    /// The directory containing the tests to run
219    pub src_base: PathBuf,
220
221    /// The directory where programs should be built
222    pub build_base: PathBuf,
223
224    /// The directory containing the compiler sysroot
225    pub sysroot_base: PathBuf,
226
227    /// The number of the stage under test.
228    pub stage: u32,
229    /// The id of the stage under test (stage1-xxx, etc).
230    pub stage_id: String,
231
232    /// The test mode, e.g. ui or debuginfo.
233    pub mode: Mode,
234
235    /// The test suite (essentially which directory is running, but without the
236    /// directory prefix such as tests)
237    pub suite: String,
238
239    /// The debugger to use in debuginfo mode. Unset otherwise.
240    pub debugger: Option<Debugger>,
241
242    /// Run ignored tests
243    pub run_ignored: bool,
244
245    /// Whether rustc was built with debug assertions.
246    pub with_rustc_debug_assertions: bool,
247
248    /// Whether std was built with debug assertions.
249    pub with_std_debug_assertions: bool,
250
251    /// Only run tests that match these filters
252    pub filters: Vec<String>,
253
254    /// Skip tests matching these substrings. Corresponds to
255    /// `test::TestOpts::skip`. `filter_exact` does not apply to these flags.
256    pub skip: Vec<String>,
257
258    /// Exactly match the filter, rather than a substring
259    pub filter_exact: bool,
260
261    /// Force the pass mode of a check/build/run-pass test to this mode.
262    pub force_pass_mode: Option<PassMode>,
263
264    /// Explicitly enable or disable running.
265    pub run: Option<bool>,
266
267    /// Write out a parseable log of tests that were run
268    pub logfile: Option<PathBuf>,
269
270    /// A command line to prefix program execution with,
271    /// for running under valgrind for example.
272    ///
273    /// Similar to `CARGO_*_RUNNER` configuration.
274    pub runner: Option<String>,
275
276    /// Flags to pass to the compiler when building for the host
277    pub host_rustcflags: Vec<String>,
278
279    /// Flags to pass to the compiler when building for the target
280    pub target_rustcflags: Vec<String>,
281
282    /// Whether the compiler and stdlib has been built with randomized struct layouts
283    pub rust_randomized_layout: bool,
284
285    /// Whether tests should be optimized by default. Individual test-suites and test files may
286    /// override this setting.
287    pub optimize_tests: bool,
288
289    /// Target system to be tested
290    pub target: String,
291
292    /// Host triple for the compiler being invoked
293    pub host: String,
294
295    /// Path to / name of the Microsoft Console Debugger (CDB) executable
296    pub cdb: Option<OsString>,
297
298    /// Version of CDB
299    pub cdb_version: Option<[u16; 4]>,
300
301    /// Path to / name of the GDB executable
302    pub gdb: Option<String>,
303
304    /// Version of GDB, encoded as ((major * 1000) + minor) * 1000 + patch
305    pub gdb_version: Option<u32>,
306
307    /// Version of LLDB
308    pub lldb_version: Option<u32>,
309
310    /// Version of LLVM
311    pub llvm_version: Option<Version>,
312
313    /// Is LLVM a system LLVM
314    pub system_llvm: bool,
315
316    /// Path to the android tools
317    pub android_cross_path: PathBuf,
318
319    /// Extra parameter to run adb on arm-linux-androideabi
320    pub adb_path: String,
321
322    /// Extra parameter to run test suite on arm-linux-androideabi
323    pub adb_test_dir: String,
324
325    /// status whether android device available or not
326    pub adb_device_status: bool,
327
328    /// the path containing LLDB's Python module
329    pub lldb_python_dir: Option<String>,
330
331    /// Explain what's going on
332    pub verbose: bool,
333
334    /// Print one character per test instead of one line
335    pub format: OutputFormat,
336
337    /// Whether to use colors in test.
338    pub color: ColorConfig,
339
340    /// where to find the remote test client process, if we're using it
341    pub remote_test_client: Option<PathBuf>,
342
343    /// mode describing what file the actual ui output will be compared to
344    pub compare_mode: Option<CompareMode>,
345
346    /// If true, this will generate a coverage file with UI test files that run `MachineApplicable`
347    /// diagnostics but are missing `run-rustfix` annotations. The generated coverage file is
348    /// created in `/<build_base>/rustfix_missing_coverage.txt`
349    pub rustfix_coverage: bool,
350
351    /// whether to run `tidy` (html-tidy) when a rustdoc test fails
352    pub has_html_tidy: bool,
353
354    /// whether to run `enzyme` autodiff tests
355    pub has_enzyme: bool,
356
357    /// The current Rust channel
358    pub channel: String,
359
360    /// Whether adding git commit information such as the commit hash has been enabled for building
361    pub git_hash: bool,
362
363    /// The default Rust edition
364    pub edition: Option<String>,
365
366    // Configuration for various run-make tests frobbing things like C compilers
367    // or querying about various LLVM component information.
368    pub cc: String,
369    pub cxx: String,
370    pub cflags: String,
371    pub cxxflags: String,
372    pub ar: String,
373    pub target_linker: Option<String>,
374    pub host_linker: Option<String>,
375    pub llvm_components: String,
376
377    /// Path to a NodeJS executable. Used for JS doctests, emscripten and WASM tests
378    pub nodejs: Option<String>,
379    /// Path to a npm executable. Used for rustdoc GUI tests
380    pub npm: Option<String>,
381
382    /// Whether to rerun tests even if the inputs are unchanged.
383    pub force_rerun: bool,
384
385    /// Only rerun the tests that result has been modified according to Git status
386    pub only_modified: bool,
387
388    pub target_cfgs: OnceLock<TargetCfgs>,
389    pub builtin_cfg_names: OnceLock<HashSet<String>>,
390
391    pub nocapture: bool,
392
393    // Needed both to construct build_helper::git::GitConfig
394    pub git_repository: String,
395    pub nightly_branch: String,
396    pub git_merge_commit_email: String,
397
398    /// True if the profiler runtime is enabled for this target.
399    /// Used by the "needs-profiler-runtime" directive in test files.
400    pub profiler_runtime: bool,
401
402    /// Command for visual diff display, e.g. `diff-tool --color=always`.
403    pub diff_command: Option<String>,
404
405    /// Path to minicore aux library, used for `no_core` tests that need `core` stubs in
406    /// cross-compilation scenarios that do not otherwise want/need to `-Zbuild-std`. Used in e.g.
407    /// ABI tests.
408    pub minicore_path: PathBuf,
409}
410
411impl Config {
412    pub fn run_enabled(&self) -> bool {
413        self.run.unwrap_or_else(|| {
414            // Auto-detect whether to run based on the platform.
415            !self.target.ends_with("-fuchsia")
416        })
417    }
418
419    pub fn target_cfgs(&self) -> &TargetCfgs {
420        self.target_cfgs.get_or_init(|| TargetCfgs::new(self))
421    }
422
423    pub fn target_cfg(&self) -> &TargetCfg {
424        &self.target_cfgs().current
425    }
426
427    pub fn matches_arch(&self, arch: &str) -> bool {
428        self.target_cfg().arch == arch ||
429        // Matching all the thumb variants as one can be convenient.
430        // (thumbv6m, thumbv7em, thumbv7m, etc.)
431        (arch == "thumb" && self.target.starts_with("thumb"))
432    }
433
434    pub fn matches_os(&self, os: &str) -> bool {
435        self.target_cfg().os == os
436    }
437
438    pub fn matches_env(&self, env: &str) -> bool {
439        self.target_cfg().env == env
440    }
441
442    pub fn matches_abi(&self, abi: &str) -> bool {
443        self.target_cfg().abi == abi
444    }
445
446    pub fn matches_family(&self, family: &str) -> bool {
447        self.target_cfg().families.iter().any(|f| f == family)
448    }
449
450    pub fn is_big_endian(&self) -> bool {
451        self.target_cfg().endian == Endian::Big
452    }
453
454    pub fn get_pointer_width(&self) -> u32 {
455        *&self.target_cfg().pointer_width
456    }
457
458    pub fn can_unwind(&self) -> bool {
459        self.target_cfg().panic == PanicStrategy::Unwind
460    }
461
462    /// Get the list of builtin, 'well known' cfg names
463    pub fn builtin_cfg_names(&self) -> &HashSet<String> {
464        self.builtin_cfg_names.get_or_init(|| builtin_cfg_names(self))
465    }
466
467    pub fn has_threads(&self) -> bool {
468        // Wasm targets don't have threads unless `-threads` is in the target
469        // name, such as `wasm32-wasip1-threads`.
470        if self.target.starts_with("wasm") {
471            return self.target.contains("threads");
472        }
473        true
474    }
475
476    pub fn has_asm_support(&self) -> bool {
477        static ASM_SUPPORTED_ARCHS: &[&str] = &[
478            "x86", "x86_64", "arm", "aarch64", "riscv32",
479            "riscv64",
480            // These targets require an additional asm_experimental_arch feature.
481            // "nvptx64", "hexagon", "mips", "mips64", "spirv", "wasm32",
482        ];
483        ASM_SUPPORTED_ARCHS.contains(&self.target_cfg().arch.as_str())
484    }
485
486    pub fn git_config(&self) -> GitConfig<'_> {
487        GitConfig {
488            git_repository: &self.git_repository,
489            nightly_branch: &self.nightly_branch,
490            git_merge_commit_email: &self.git_merge_commit_email,
491        }
492    }
493
494    pub fn has_subprocess_support(&self) -> bool {
495        // FIXME(#135928): compiletest is always a **host** tool. Building and running an
496        // capability detection executable against the **target** is not trivial. The short term
497        // solution here is to hard-code some targets to allow/deny, unfortunately.
498
499        let unsupported_target = self.target_cfg().env == "sgx"
500            || matches!(self.target_cfg().arch.as_str(), "wasm32" | "wasm64")
501            || self.target_cfg().os == "emscripten";
502        !unsupported_target
503    }
504}
505
506/// Known widths of `target_has_atomic`.
507pub const KNOWN_TARGET_HAS_ATOMIC_WIDTHS: &[&str] = &["8", "16", "32", "64", "128", "ptr"];
508
509#[derive(Debug, Clone)]
510pub struct TargetCfgs {
511    pub current: TargetCfg,
512    pub all_targets: HashSet<String>,
513    pub all_archs: HashSet<String>,
514    pub all_oses: HashSet<String>,
515    pub all_oses_and_envs: HashSet<String>,
516    pub all_envs: HashSet<String>,
517    pub all_abis: HashSet<String>,
518    pub all_families: HashSet<String>,
519    pub all_pointer_widths: HashSet<String>,
520}
521
522impl TargetCfgs {
523    fn new(config: &Config) -> TargetCfgs {
524        let mut targets: HashMap<String, TargetCfg> = serde_json::from_str(&rustc_output(
525            config,
526            &["--print=all-target-specs-json", "-Zunstable-options"],
527            Default::default(),
528        ))
529        .unwrap();
530
531        let mut all_targets = HashSet::new();
532        let mut all_archs = HashSet::new();
533        let mut all_oses = HashSet::new();
534        let mut all_oses_and_envs = HashSet::new();
535        let mut all_envs = HashSet::new();
536        let mut all_abis = HashSet::new();
537        let mut all_families = HashSet::new();
538        let mut all_pointer_widths = HashSet::new();
539
540        // If current target is not included in the `--print=all-target-specs-json` output,
541        // we check whether it is a custom target from the user or a synthetic target from bootstrap.
542        if !targets.contains_key(&config.target) {
543            let mut envs: HashMap<String, String> = HashMap::new();
544
545            if let Ok(t) = std::env::var("RUST_TARGET_PATH") {
546                envs.insert("RUST_TARGET_PATH".into(), t);
547            }
548
549            // This returns false only when the target is neither a synthetic target
550            // nor a custom target from the user, indicating it is most likely invalid.
551            if config.target.ends_with(".json") || !envs.is_empty() {
552                targets.insert(
553                    config.target.clone(),
554                    serde_json::from_str(&rustc_output(
555                        config,
556                        &[
557                            "--print=target-spec-json",
558                            "-Zunstable-options",
559                            "--target",
560                            &config.target,
561                        ],
562                        envs,
563                    ))
564                    .unwrap(),
565                );
566            }
567        }
568
569        for (target, cfg) in targets.iter() {
570            all_archs.insert(cfg.arch.clone());
571            all_oses.insert(cfg.os.clone());
572            all_oses_and_envs.insert(cfg.os_and_env());
573            all_envs.insert(cfg.env.clone());
574            all_abis.insert(cfg.abi.clone());
575            for family in &cfg.families {
576                all_families.insert(family.clone());
577            }
578            all_pointer_widths.insert(format!("{}bit", cfg.pointer_width));
579
580            all_targets.insert(target.clone());
581        }
582
583        Self {
584            current: Self::get_current_target_config(config, &targets),
585            all_targets,
586            all_archs,
587            all_oses,
588            all_oses_and_envs,
589            all_envs,
590            all_abis,
591            all_families,
592            all_pointer_widths,
593        }
594    }
595
596    fn get_current_target_config(
597        config: &Config,
598        targets: &HashMap<String, TargetCfg>,
599    ) -> TargetCfg {
600        let mut cfg = targets[&config.target].clone();
601
602        // To get the target information for the current target, we take the target spec obtained
603        // from `--print=all-target-specs-json`, and then we enrich it with the information
604        // gathered from `--print=cfg --target=$target`.
605        //
606        // This is done because some parts of the target spec can be overridden with `-C` flags,
607        // which are respected for `--print=cfg` but not for `--print=all-target-specs-json`. The
608        // code below extracts them from `--print=cfg`: make sure to only override fields that can
609        // actually be changed with `-C` flags.
610        for config in
611            rustc_output(config, &["--print=cfg", "--target", &config.target], Default::default())
612                .trim()
613                .lines()
614        {
615            let (name, value) = config
616                .split_once("=\"")
617                .map(|(name, value)| {
618                    (
619                        name,
620                        Some(
621                            value
622                                .strip_suffix('\"')
623                                .expect("key-value pair should be properly quoted"),
624                        ),
625                    )
626                })
627                .unwrap_or_else(|| (config, None));
628
629            match (name, value) {
630                // Can be overridden with `-C panic=$strategy`.
631                ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort,
632                ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind,
633                ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"),
634
635                ("target_has_atomic", Some(width))
636                    if KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width) =>
637                {
638                    cfg.target_has_atomic.insert(width.to_string());
639                }
640                ("target_has_atomic", Some(other)) => {
641                    panic!("unexpected value for `target_has_atomic` cfg: {other:?}")
642                }
643                // Nightly-only std-internal impl detail.
644                ("target_has_atomic", None) => {}
645                _ => {}
646            }
647        }
648
649        cfg
650    }
651}
652
653#[derive(Clone, Debug, serde::Deserialize)]
654#[serde(rename_all = "kebab-case")]
655pub struct TargetCfg {
656    pub(crate) arch: String,
657    #[serde(default = "default_os")]
658    pub(crate) os: String,
659    #[serde(default)]
660    pub(crate) env: String,
661    #[serde(default)]
662    pub(crate) abi: String,
663    #[serde(rename = "target-family", default)]
664    pub(crate) families: Vec<String>,
665    #[serde(rename = "target-pointer-width", deserialize_with = "serde_parse_u32")]
666    pub(crate) pointer_width: u32,
667    #[serde(rename = "target-endian", default)]
668    endian: Endian,
669    #[serde(rename = "panic-strategy", default)]
670    pub(crate) panic: PanicStrategy,
671    #[serde(default)]
672    pub(crate) dynamic_linking: bool,
673    #[serde(rename = "supported-sanitizers", default)]
674    pub(crate) sanitizers: Vec<Sanitizer>,
675    #[serde(rename = "supports-xray", default)]
676    pub(crate) xray: bool,
677    #[serde(default = "default_reloc_model")]
678    pub(crate) relocation_model: String,
679
680    // Not present in target cfg json output, additional derived information.
681    #[serde(skip)]
682    /// Supported target atomic widths: e.g. `8` to `128` or `ptr`. This is derived from the builtin
683    /// `target_has_atomic` `cfg`s e.g. `target_has_atomic="8"`.
684    pub(crate) target_has_atomic: BTreeSet<String>,
685}
686
687impl TargetCfg {
688    pub(crate) fn os_and_env(&self) -> String {
689        format!("{}-{}", self.os, self.env)
690    }
691}
692
693fn default_os() -> String {
694    "none".into()
695}
696
697fn default_reloc_model() -> String {
698    "pic".into()
699}
700
701#[derive(Eq, PartialEq, Clone, Debug, Default, serde::Deserialize)]
702#[serde(rename_all = "kebab-case")]
703pub enum Endian {
704    #[default]
705    Little,
706    Big,
707}
708
709fn builtin_cfg_names(config: &Config) -> HashSet<String> {
710    rustc_output(
711        config,
712        &["--print=check-cfg", "-Zunstable-options", "--check-cfg=cfg()"],
713        Default::default(),
714    )
715    .lines()
716    .map(|l| if let Some((name, _)) = l.split_once('=') { name.to_string() } else { l.to_string() })
717    .chain(std::iter::once(String::from("test")))
718    .collect()
719}
720
721fn rustc_output(config: &Config, args: &[&str], envs: HashMap<String, String>) -> String {
722    let mut command = Command::new(&config.rustc_path);
723    add_dylib_path(&mut command, iter::once(&config.compile_lib_path));
724    command.args(&config.target_rustcflags).args(args);
725    command.env("RUSTC_BOOTSTRAP", "1");
726    command.envs(envs);
727
728    let output = match command.output() {
729        Ok(output) => output,
730        Err(e) => panic!("error: failed to run {command:?}: {e}"),
731    };
732    if !output.status.success() {
733        panic!(
734            "error: failed to run {command:?}\n--- stdout\n{}\n--- stderr\n{}",
735            String::from_utf8(output.stdout).unwrap(),
736            String::from_utf8(output.stderr).unwrap(),
737        );
738    }
739    String::from_utf8(output.stdout).unwrap()
740}
741
742fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
743    let string = String::deserialize(deserializer)?;
744    string.parse().map_err(D::Error::custom)
745}
746
747#[derive(Debug, Clone)]
748pub struct TestPaths {
749    pub file: PathBuf,         // e.g., compile-test/foo/bar/baz.rs
750    pub relative_dir: PathBuf, // e.g., foo/bar
751}
752
753/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
754pub fn expected_output_path(
755    testpaths: &TestPaths,
756    revision: Option<&str>,
757    compare_mode: &Option<CompareMode>,
758    kind: &str,
759) -> PathBuf {
760    assert!(UI_EXTENSIONS.contains(&kind));
761    let mut parts = Vec::new();
762
763    if let Some(x) = revision {
764        parts.push(x);
765    }
766    if let Some(ref x) = *compare_mode {
767        parts.push(x.to_str());
768    }
769    parts.push(kind);
770
771    let extension = parts.join(".");
772    testpaths.file.with_extension(extension)
773}
774
775pub const UI_EXTENSIONS: &[&str] = &[
776    UI_STDERR,
777    UI_SVG,
778    UI_WINDOWS_SVG,
779    UI_STDOUT,
780    UI_FIXED,
781    UI_RUN_STDERR,
782    UI_RUN_STDOUT,
783    UI_STDERR_64,
784    UI_STDERR_32,
785    UI_STDERR_16,
786    UI_COVERAGE,
787    UI_COVERAGE_MAP,
788];
789pub const UI_STDERR: &str = "stderr";
790pub const UI_SVG: &str = "svg";
791pub const UI_WINDOWS_SVG: &str = "windows.svg";
792pub const UI_STDOUT: &str = "stdout";
793pub const UI_FIXED: &str = "fixed";
794pub const UI_RUN_STDERR: &str = "run.stderr";
795pub const UI_RUN_STDOUT: &str = "run.stdout";
796pub const UI_STDERR_64: &str = "64bit.stderr";
797pub const UI_STDERR_32: &str = "32bit.stderr";
798pub const UI_STDERR_16: &str = "16bit.stderr";
799pub const UI_COVERAGE: &str = "coverage";
800pub const UI_COVERAGE_MAP: &str = "cov-map";
801
802/// Absolute path to the directory where all output for all tests in the given
803/// `relative_dir` group should reside. Example:
804///   /path/to/build/host-tuple/test/ui/relative/
805/// This is created early when tests are collected to avoid race conditions.
806pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf {
807    config.build_base.join(relative_dir)
808}
809
810/// Generates a unique name for the test, such as `testname.revision.mode`.
811pub fn output_testname_unique(
812    config: &Config,
813    testpaths: &TestPaths,
814    revision: Option<&str>,
815) -> PathBuf {
816    let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
817    let debugger = config.debugger.as_ref().map_or("", |m| m.to_str());
818    PathBuf::from(&testpaths.file.file_stem().unwrap())
819        .with_extra_extension(config.mode.output_dir_disambiguator())
820        .with_extra_extension(revision.unwrap_or(""))
821        .with_extra_extension(mode)
822        .with_extra_extension(debugger)
823}
824
825/// Absolute path to the directory where all output for the given
826/// test/revision should reside. Example:
827///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/
828pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
829    output_relative_path(config, &testpaths.relative_dir)
830        .join(output_testname_unique(config, testpaths, revision))
831}
832
833/// Absolute path to the base filename used as output for the given
834/// test/revision. Example:
835///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/testname
836pub fn output_base_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
837    output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
838}
839
840/// Absolute path to the directory to use for incremental compilation. Example:
841///   /path/to/build/host-tuple/test/ui/relative/testname.mode/testname.inc
842pub fn incremental_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
843    output_base_name(config, testpaths, revision).with_extension("inc")
844}