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#[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 match self {
89 Pretty => ".pretty",
90 _ => "",
91 }
92 }
93
94 pub fn output_dir_disambiguator(self) -> &'static str {
95 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#[derive(Debug, Default, Clone)]
176pub struct Config {
177 pub bless: bool,
179
180 pub fail_fast: bool,
183
184 pub compile_lib_path: Utf8PathBuf,
186
187 pub run_lib_path: Utf8PathBuf,
189
190 pub rustc_path: Utf8PathBuf,
192
193 pub cargo_path: Option<Utf8PathBuf>,
195
196 pub stage0_rustc_path: Option<Utf8PathBuf>,
198
199 pub rustdoc_path: Option<Utf8PathBuf>,
201
202 pub coverage_dump_path: Option<Utf8PathBuf>,
204
205 pub python: String,
207
208 pub jsondocck_path: Option<String>,
210
211 pub jsondoclint_path: Option<String>,
213
214 pub llvm_filecheck: Option<Utf8PathBuf>,
216
217 pub llvm_bin_dir: Option<Utf8PathBuf>,
219
220 pub run_clang_based_tests_with: Option<String>,
223
224 pub src_root: Utf8PathBuf,
226 pub src_test_suite_root: Utf8PathBuf,
228
229 pub build_root: Utf8PathBuf,
231 pub build_test_suite_root: Utf8PathBuf,
233
234 pub sysroot_base: Utf8PathBuf,
236
237 pub stage: u32,
239 pub stage_id: String,
241
242 pub mode: Mode,
244
245 pub suite: String,
248
249 pub debugger: Option<Debugger>,
251
252 pub run_ignored: bool,
254
255 pub with_rustc_debug_assertions: bool,
257
258 pub with_std_debug_assertions: bool,
260
261 pub filters: Vec<String>,
263
264 pub skip: Vec<String>,
267
268 pub filter_exact: bool,
270
271 pub force_pass_mode: Option<PassMode>,
273
274 pub run: Option<bool>,
276
277 pub runner: Option<String>,
282
283 pub host_rustcflags: Vec<String>,
285
286 pub target_rustcflags: Vec<String>,
288
289 pub rust_randomized_layout: bool,
291
292 pub optimize_tests: bool,
295
296 pub target: String,
298
299 pub host: String,
301
302 pub cdb: Option<Utf8PathBuf>,
304
305 pub cdb_version: Option<[u16; 4]>,
307
308 pub gdb: Option<String>,
310
311 pub gdb_version: Option<u32>,
313
314 pub lldb_version: Option<u32>,
316
317 pub llvm_version: Option<Version>,
319
320 pub system_llvm: bool,
322
323 pub android_cross_path: Utf8PathBuf,
325
326 pub adb_path: String,
328
329 pub adb_test_dir: String,
331
332 pub adb_device_status: bool,
334
335 pub lldb_python_dir: Option<String>,
337
338 pub verbose: bool,
340
341 pub format: OutputFormat,
343
344 pub color: ColorConfig,
346
347 pub remote_test_client: Option<Utf8PathBuf>,
349
350 pub compare_mode: Option<CompareMode>,
352
353 pub rustfix_coverage: bool,
357
358 pub has_html_tidy: bool,
360
361 pub has_enzyme: bool,
363
364 pub channel: String,
366
367 pub git_hash: bool,
369
370 pub edition: Option<String>,
372
373 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 pub nodejs: Option<String>,
386 pub npm: Option<String>,
388
389 pub force_rerun: bool,
391
392 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 pub nightly_branch: String,
403 pub git_merge_commit_email: String,
404
405 pub profiler_runtime: bool,
408
409 pub diff_command: Option<String>,
411
412 pub minicore_path: Utf8PathBuf,
416}
417
418impl Config {
419 pub fn run_enabled(&self) -> bool {
420 self.run.unwrap_or_else(|| {
421 !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 (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 pub fn builtin_cfg_names(&self) -> &HashSet<String> {
471 self.builtin_cfg_names.get_or_init(|| builtin_cfg_names(self))
472 }
473
474 pub fn 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 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 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 ];
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 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
526pub 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 let mut all_rustc_abis = HashSet::new();
563
564 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 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 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 ("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 ("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 pub(crate) rustc_abi: Option<String>,
710
711 #[serde(skip)]
713 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, pub relative_dir: Utf8PathBuf, }
808
809pub 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
858pub fn output_relative_path(config: &Config, relative_dir: &Utf8Path) -> Utf8PathBuf {
867 config.build_test_suite_root.join(relative_dir)
868}
869
870pub 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
885pub 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
897pub 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
908pub 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}