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 "loongarch64",
499 "s390x",
500 ];
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 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
525pub 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 let mut all_rustc_abis = HashSet::new();
562
563 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 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 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 ("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 ("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 pub(crate) rustc_abi: Option<String>,
709
710 #[serde(skip)]
712 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, pub relative_dir: Utf8PathBuf, }
807
808pub 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
857pub fn output_relative_path(config: &Config, relative_dir: &Utf8Path) -> Utf8PathBuf {
866 config.build_test_suite_root.join(relative_dir)
867}
868
869pub 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
884pub 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
896pub 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
907pub 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}