1use crate::CommandError;
5use camino::{Utf8Path, Utf8PathBuf};
6use serde::{Deserialize, Serialize};
7use smol_str::SmolStr;
8use std::{
9 borrow::Cow,
10 cmp::Ordering,
11 collections::{BTreeMap, BTreeSet},
12 fmt::{self, Write as _},
13 path::PathBuf,
14 process::Command,
15};
16use target_spec::summaries::PlatformSummary;
17
18pub const GLOBAL_TEST_GROUP: &str = "@global";
23
24#[derive(Clone, Debug, Default)]
26pub struct ListCommand {
27 cargo_path: Option<Box<Utf8Path>>,
28 manifest_path: Option<Box<Utf8Path>>,
29 current_dir: Option<Box<Utf8Path>>,
30 args: Vec<Box<str>>,
31}
32
33impl ListCommand {
34 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn cargo_path(&mut self, path: impl Into<Utf8PathBuf>) -> &mut Self {
44 self.cargo_path = Some(path.into().into());
45 self
46 }
47
48 pub fn manifest_path(&mut self, path: impl Into<Utf8PathBuf>) -> &mut Self {
50 self.manifest_path = Some(path.into().into());
51 self
52 }
53
54 pub fn current_dir(&mut self, path: impl Into<Utf8PathBuf>) -> &mut Self {
56 self.current_dir = Some(path.into().into());
57 self
58 }
59
60 pub fn add_arg(&mut self, arg: impl Into<String>) -> &mut Self {
62 self.args.push(arg.into().into());
63 self
64 }
65
66 pub fn add_args(&mut self, args: impl IntoIterator<Item = impl Into<String>>) -> &mut Self {
68 for arg in args {
69 self.add_arg(arg.into());
70 }
71 self
72 }
73
74 pub fn cargo_command(&self) -> Command {
77 let cargo_path: PathBuf = self.cargo_path.as_ref().map_or_else(
78 || std::env::var_os("CARGO").map_or("cargo".into(), PathBuf::from),
79 |path| PathBuf::from(path.as_std_path()),
80 );
81
82 let mut command = Command::new(cargo_path);
83 if let Some(path) = &self.manifest_path.as_deref() {
84 command.args(["--manifest-path", path.as_str()]);
85 }
86 if let Some(current_dir) = &self.current_dir.as_deref() {
87 command.current_dir(current_dir);
88 }
89
90 command.args(["nextest", "list", "--message-format=json"]);
91
92 command.args(self.args.iter().map(|s| s.as_ref()));
93 command
94 }
95
96 pub fn exec(&self) -> Result<TestListSummary, CommandError> {
98 let mut command = self.cargo_command();
99 let output = command.output().map_err(CommandError::Exec)?;
100
101 if !output.status.success() {
102 let exit_code = output.status.code();
104 let stderr = output.stderr;
105 return Err(CommandError::CommandFailed { exit_code, stderr });
106 }
107
108 serde_json::from_slice(&output.stdout).map_err(CommandError::Json)
110 }
111
112 pub fn exec_binaries_only(&self) -> Result<BinaryListSummary, CommandError> {
115 let mut command = self.cargo_command();
116 command.arg("--list-type=binaries-only");
117 let output = command.output().map_err(CommandError::Exec)?;
118
119 if !output.status.success() {
120 let exit_code = output.status.code();
122 let stderr = output.stderr;
123 return Err(CommandError::CommandFailed { exit_code, stderr });
124 }
125
126 serde_json::from_slice(&output.stdout).map_err(CommandError::Json)
128 }
129}
130
131#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
133#[serde(rename_all = "kebab-case")]
134#[non_exhaustive]
135pub struct TestListSummary {
136 pub rust_build_meta: RustBuildMetaSummary,
138
139 pub test_count: usize,
141
142 pub rust_suites: BTreeMap<RustBinaryId, RustTestSuiteSummary>,
145}
146
147impl TestListSummary {
148 pub fn new(rust_build_meta: RustBuildMetaSummary) -> Self {
150 Self {
151 rust_build_meta,
152 test_count: 0,
153 rust_suites: BTreeMap::new(),
154 }
155 }
156 pub fn parse_json(json: impl AsRef<str>) -> Result<Self, serde_json::Error> {
158 serde_json::from_str(json.as_ref())
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
167#[serde(rename_all = "kebab-case")]
168pub enum BuildPlatform {
169 Target,
171
172 Host,
174}
175
176impl fmt::Display for BuildPlatform {
177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178 match self {
179 Self::Target => write!(f, "target"),
180 Self::Host => write!(f, "host"),
181 }
182 }
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
189#[serde(rename_all = "kebab-case")]
190pub struct RustTestBinarySummary {
191 pub binary_id: RustBinaryId,
193
194 pub binary_name: String,
196
197 pub package_id: String,
201
202 pub kind: RustTestBinaryKind,
204
205 pub binary_path: Utf8PathBuf,
207
208 pub build_platform: BuildPlatform,
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
218#[serde(transparent)]
219pub struct RustTestBinaryKind(pub Cow<'static, str>);
220
221impl RustTestBinaryKind {
222 #[inline]
224 pub fn new(kind: impl Into<Cow<'static, str>>) -> Self {
225 Self(kind.into())
226 }
227
228 #[inline]
230 pub const fn new_const(kind: &'static str) -> Self {
231 Self(Cow::Borrowed(kind))
232 }
233
234 pub fn as_str(&self) -> &str {
236 &self.0
237 }
238
239 pub const LIB: Self = Self::new_const("lib");
241
242 pub const TEST: Self = Self::new_const("test");
244
245 pub const BENCH: Self = Self::new_const("bench");
247
248 pub const BIN: Self = Self::new_const("bin");
250
251 pub const EXAMPLE: Self = Self::new_const("example");
253
254 pub const PROC_MACRO: Self = Self::new_const("proc-macro");
256}
257
258impl fmt::Display for RustTestBinaryKind {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write!(f, "{}", self.0)
261 }
262}
263
264#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
266#[serde(rename_all = "kebab-case")]
267pub struct BinaryListSummary {
268 pub rust_build_meta: RustBuildMetaSummary,
270
271 pub rust_binaries: BTreeMap<RustBinaryId, RustTestBinarySummary>,
273}
274
275#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
279#[serde(transparent)]
280pub struct RustBinaryId(SmolStr);
281
282impl fmt::Display for RustBinaryId {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 f.write_str(&self.0)
285 }
286}
287
288impl RustBinaryId {
289 #[inline]
291 pub fn new(id: &str) -> Self {
292 Self(id.into())
293 }
294
295 pub fn from_parts(package_name: &str, kind: &RustTestBinaryKind, target_name: &str) -> Self {
339 let mut id = package_name.to_owned();
340 if kind == &RustTestBinaryKind::LIB || kind == &RustTestBinaryKind::PROC_MACRO {
342 } else if kind == &RustTestBinaryKind::TEST {
344 id.push_str("::");
347 id.push_str(target_name);
348 } else {
349 write!(id, "::{kind}/{target_name}").unwrap();
353 }
354
355 Self(id.into())
356 }
357
358 #[inline]
360 pub fn as_str(&self) -> &str {
361 &self.0
362 }
363
364 #[inline]
366 pub fn len(&self) -> usize {
367 self.0.len()
368 }
369
370 #[inline]
372 pub fn is_empty(&self) -> bool {
373 self.0.is_empty()
374 }
375
376 #[inline]
378 pub fn components(&self) -> RustBinaryIdComponents<'_> {
379 RustBinaryIdComponents::new(self)
380 }
381}
382
383impl<S> From<S> for RustBinaryId
384where
385 S: AsRef<str>,
386{
387 #[inline]
388 fn from(s: S) -> Self {
389 Self(s.as_ref().into())
390 }
391}
392
393impl Ord for RustBinaryId {
394 fn cmp(&self, other: &RustBinaryId) -> Ordering {
395 self.components().cmp(&other.components())
400 }
401}
402
403impl PartialOrd for RustBinaryId {
404 fn partial_cmp(&self, other: &RustBinaryId) -> Option<Ordering> {
405 Some(self.cmp(other))
406 }
407}
408
409#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
415pub struct RustBinaryIdComponents<'a> {
416 pub package_name: &'a str,
418
419 pub binary_name_and_kind: RustBinaryIdNameAndKind<'a>,
421}
422
423impl<'a> RustBinaryIdComponents<'a> {
424 fn new(id: &'a RustBinaryId) -> Self {
425 let mut parts = id.as_str().splitn(2, "::");
426
427 let package_name = parts
428 .next()
429 .expect("splitn(2) returns at least 1 component");
430 let binary_name_and_kind = if let Some(suffix) = parts.next() {
431 let mut parts = suffix.splitn(2, '/');
432
433 let part1 = parts
434 .next()
435 .expect("splitn(2) returns at least 1 component");
436 if let Some(binary_name) = parts.next() {
437 RustBinaryIdNameAndKind::NameAndKind {
438 kind: part1,
439 binary_name,
440 }
441 } else {
442 RustBinaryIdNameAndKind::NameOnly { binary_name: part1 }
443 }
444 } else {
445 RustBinaryIdNameAndKind::None
446 };
447
448 Self {
449 package_name,
450 binary_name_and_kind,
451 }
452 }
453}
454
455#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
459pub enum RustBinaryIdNameAndKind<'a> {
460 None,
462
463 NameOnly {
465 binary_name: &'a str,
467 },
468
469 NameAndKind {
471 kind: &'a str,
473
474 binary_name: &'a str,
476 },
477}
478
479#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
485#[serde(transparent)]
486pub struct TestCaseName(SmolStr);
487
488impl fmt::Display for TestCaseName {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 f.write_str(&self.0)
491 }
492}
493
494impl TestCaseName {
495 #[inline]
497 pub fn new(name: &str) -> Self {
498 Self(name.into())
499 }
500
501 #[inline]
503 pub fn as_str(&self) -> &str {
504 &self.0
505 }
506
507 #[inline]
509 pub fn as_bytes(&self) -> &[u8] {
510 self.0.as_bytes()
511 }
512
513 #[inline]
515 pub fn len(&self) -> usize {
516 self.0.len()
517 }
518
519 #[inline]
521 pub fn is_empty(&self) -> bool {
522 self.0.is_empty()
523 }
524
525 #[inline]
527 pub fn contains(&self, pattern: &str) -> bool {
528 self.0.contains(pattern)
529 }
530
531 #[inline]
550 pub fn components(&self) -> std::str::Split<'_, &str> {
551 self.0.split("::")
552 }
553
554 #[inline]
571 pub fn module_path_and_name(&self) -> (Option<&str>, &str) {
572 match self.0.rsplit_once("::") {
573 Some((module_path, name)) => (Some(module_path), name),
574 None => (None, &self.0),
575 }
576 }
577}
578
579impl AsRef<str> for TestCaseName {
580 #[inline]
581 fn as_ref(&self) -> &str {
582 &self.0
583 }
584}
585
586#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
588#[serde(rename_all = "kebab-case")]
589pub struct RustBuildMetaSummary {
590 pub target_directory: Utf8PathBuf,
592
593 #[serde(default, skip_serializing_if = "Option::is_none")]
603 pub build_directory: Option<Utf8PathBuf>,
604
605 pub base_output_directories: BTreeSet<Utf8PathBuf>,
615
616 pub non_test_binaries: BTreeMap<String, BTreeSet<RustNonTestBinarySummary>>,
618
619 #[serde(default)]
625 pub build_script_out_dirs: BTreeMap<String, Utf8PathBuf>,
626
627 #[serde(default)]
635 pub build_script_info: Option<BTreeMap<String, BuildScriptInfoSummary>>,
636
637 pub linked_paths: BTreeSet<Utf8PathBuf>,
639
640 #[serde(default)]
644 pub platforms: Option<BuildPlatformsSummary>,
645
646 #[serde(default)]
650 pub target_platforms: Vec<PlatformSummary>,
651
652 #[serde(default)]
657 pub target_platform: Option<String>,
658}
659
660#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
665#[serde(rename_all = "kebab-case")]
666pub struct BuildScriptInfoSummary {
667 #[serde(default)]
670 pub envs: BTreeMap<String, String>,
671}
672
673#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
676#[serde(rename_all = "kebab-case")]
677pub struct RustNonTestBinarySummary {
678 pub name: String,
680
681 pub kind: RustNonTestBinaryKind,
683
684 pub path: Utf8PathBuf,
686
687 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub build_platform: Option<BuildPlatform>,
695}
696
697#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
699#[serde(rename_all = "kebab-case")]
700pub struct BuildPlatformsSummary {
701 pub host: HostPlatformSummary,
703
704 pub targets: Vec<TargetPlatformSummary>,
708}
709
710#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
712#[serde(rename_all = "kebab-case")]
713pub struct HostPlatformSummary {
714 pub platform: PlatformSummary,
716
717 pub libdir: PlatformLibdirSummary,
719}
720
721#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
723#[serde(rename_all = "kebab-case")]
724pub struct TargetPlatformSummary {
725 pub platform: PlatformSummary,
727
728 pub libdir: PlatformLibdirSummary,
732}
733
734#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
736#[serde(tag = "status", rename_all = "kebab-case")]
737pub enum PlatformLibdirSummary {
738 Available {
740 path: Utf8PathBuf,
742 },
743
744 Unavailable {
746 reason: PlatformLibdirUnavailable,
748 },
749}
750
751#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
757pub struct PlatformLibdirUnavailable(pub Cow<'static, str>);
758
759impl PlatformLibdirUnavailable {
760 pub const RUSTC_FAILED: Self = Self::new_const("rustc-failed");
762
763 pub const RUSTC_OUTPUT_ERROR: Self = Self::new_const("rustc-output-error");
766
767 pub const OLD_SUMMARY: Self = Self::new_const("old-summary");
770
771 pub const NOT_IN_ARCHIVE: Self = Self::new_const("not-in-archive");
774
775 pub const fn new_const(reason: &'static str) -> Self {
777 Self(Cow::Borrowed(reason))
778 }
779
780 pub fn new(reason: impl Into<Cow<'static, str>>) -> Self {
782 Self(reason.into())
783 }
784
785 pub fn as_str(&self) -> &str {
787 &self.0
788 }
789}
790
791#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
796#[serde(transparent)]
797pub struct RustNonTestBinaryKind(pub Cow<'static, str>);
798
799impl RustNonTestBinaryKind {
800 #[inline]
802 pub fn new(kind: impl Into<Cow<'static, str>>) -> Self {
803 Self(kind.into())
804 }
805
806 #[inline]
808 pub const fn new_const(kind: &'static str) -> Self {
809 Self(Cow::Borrowed(kind))
810 }
811
812 pub fn as_str(&self) -> &str {
814 &self.0
815 }
816
817 pub const DYLIB: Self = Self::new_const("dylib");
820
821 pub const BIN_EXE: Self = Self::new_const("bin-exe");
823}
824
825impl fmt::Display for RustNonTestBinaryKind {
826 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827 write!(f, "{}", self.0)
828 }
829}
830
831#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
835#[serde(rename_all = "kebab-case")]
836pub struct RustTestSuiteSummary {
837 pub package_name: String,
839
840 #[serde(flatten)]
842 pub binary: RustTestBinarySummary,
843
844 pub cwd: Utf8PathBuf,
846
847 #[serde(default = "listed_status")]
852 pub status: RustTestSuiteStatusSummary,
853
854 #[serde(rename = "testcases")]
856 pub test_cases: BTreeMap<TestCaseName, RustTestCaseSummary>,
857}
858
859fn listed_status() -> RustTestSuiteStatusSummary {
860 RustTestSuiteStatusSummary::LISTED
861}
862
863#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
867#[serde(transparent)]
868pub struct RustTestSuiteStatusSummary(pub Cow<'static, str>);
869
870impl RustTestSuiteStatusSummary {
871 #[inline]
873 pub fn new(kind: impl Into<Cow<'static, str>>) -> Self {
874 Self(kind.into())
875 }
876
877 #[inline]
879 pub const fn new_const(kind: &'static str) -> Self {
880 Self(Cow::Borrowed(kind))
881 }
882
883 pub fn as_str(&self) -> &str {
885 &self.0
886 }
887
888 pub const LISTED: Self = Self::new_const("listed");
891
892 pub const SKIPPED: Self = Self::new_const("skipped");
897
898 pub const SKIPPED_DEFAULT_FILTER: Self = Self::new_const("skipped-default-filter");
902}
903
904#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
908#[serde(rename_all = "kebab-case")]
909pub struct RustTestCaseSummary {
910 pub kind: Option<RustTestKind>,
915
916 pub ignored: bool,
920
921 pub filter_match: FilterMatch,
925}
926
927#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
931#[serde(transparent)]
932pub struct RustTestKind(pub Cow<'static, str>);
933
934impl RustTestKind {
935 #[inline]
937 pub fn new(kind: impl Into<Cow<'static, str>>) -> Self {
938 Self(kind.into())
939 }
940
941 #[inline]
943 pub const fn new_const(kind: &'static str) -> Self {
944 Self(Cow::Borrowed(kind))
945 }
946
947 pub fn as_str(&self) -> &str {
949 &self.0
950 }
951
952 pub const TEST: Self = Self::new_const("test");
954
955 pub const BENCH: Self = Self::new_const("bench");
957}
958
959#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
961#[serde(rename_all = "kebab-case", tag = "status")]
962pub enum FilterMatch {
963 Matches,
965
966 Mismatch {
968 reason: MismatchReason,
970 },
971}
972
973impl FilterMatch {
974 pub fn is_match(&self) -> bool {
976 matches!(self, FilterMatch::Matches)
977 }
978}
979
980#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
982#[serde(rename_all = "kebab-case")]
983#[non_exhaustive]
984pub enum MismatchReason {
985 NotBenchmark,
987
988 Ignored,
990
991 String,
993
994 Expression,
996
997 Partition,
999
1000 RerunAlreadyPassed,
1002
1003 DefaultFilter,
1007}
1008
1009impl MismatchReason {
1010 pub const ALL_VARIANTS: &'static [Self] = &[
1015 Self::NotBenchmark,
1016 Self::Ignored,
1017 Self::String,
1018 Self::Expression,
1019 Self::Partition,
1020 Self::RerunAlreadyPassed,
1021 Self::DefaultFilter,
1022 ];
1023 pub fn is_ignore_mismatch(self) -> bool {
1026 match self {
1027 MismatchReason::Ignored => true,
1028 MismatchReason::NotBenchmark
1029 | MismatchReason::String
1030 | MismatchReason::Expression
1031 | MismatchReason::Partition
1032 | MismatchReason::RerunAlreadyPassed
1033 | MismatchReason::DefaultFilter => false,
1034 }
1035 }
1036
1037 pub fn is_substantive_skip(self) -> bool {
1041 match self {
1042 MismatchReason::NotBenchmark => false,
1043 MismatchReason::Ignored
1044 | MismatchReason::String
1045 | MismatchReason::Expression
1046 | MismatchReason::Partition
1047 | MismatchReason::RerunAlreadyPassed
1048 | MismatchReason::DefaultFilter => true,
1049 }
1050 }
1051}
1052
1053impl fmt::Display for MismatchReason {
1054 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1055 match self {
1056 MismatchReason::NotBenchmark => write!(f, "is not a benchmark"),
1057 MismatchReason::Ignored => write!(f, "does not match the run-ignored option"),
1058 MismatchReason::String => write!(f, "does not match the provided string filters"),
1059 MismatchReason::Expression => {
1060 write!(f, "does not match the provided expression filters")
1061 }
1062 MismatchReason::Partition => write!(f, "is in a different partition"),
1063 MismatchReason::RerunAlreadyPassed => write!(f, "already passed"),
1064 MismatchReason::DefaultFilter => {
1065 write!(f, "is filtered out by the profile's default-filter")
1066 }
1067 }
1068 }
1069}
1070
1071#[cfg(feature = "proptest1")]
1074mod proptest_impls {
1075 use super::*;
1076 use proptest::prelude::*;
1077
1078 impl Arbitrary for RustBinaryId {
1079 type Parameters = ();
1080 type Strategy = BoxedStrategy<Self>;
1081
1082 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1083 any::<String>().prop_map(|s| RustBinaryId::new(&s)).boxed()
1084 }
1085 }
1086
1087 impl Arbitrary for TestCaseName {
1088 type Parameters = ();
1089 type Strategy = BoxedStrategy<Self>;
1090
1091 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1092 any::<String>().prop_map(|s| TestCaseName::new(&s)).boxed()
1093 }
1094 }
1095
1096 impl Arbitrary for MismatchReason {
1097 type Parameters = ();
1098 type Strategy = BoxedStrategy<Self>;
1099
1100 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1101 proptest::sample::select(MismatchReason::ALL_VARIANTS).boxed()
1102 }
1103 }
1104}
1105
1106#[cfg(test)]
1107mod tests {
1108 use super::*;
1109 use test_case::test_case;
1110
1111 #[test_case(r#"{
1112 "target-directory": "/foo",
1113 "base-output-directories": [],
1114 "non-test-binaries": {},
1115 "linked-paths": []
1116 }"#, RustBuildMetaSummary {
1117 target_directory: "/foo".into(),
1118 build_directory: None,
1119 base_output_directories: BTreeSet::new(),
1120 non_test_binaries: BTreeMap::new(),
1121 build_script_out_dirs: BTreeMap::new(),
1122 build_script_info: None,
1123 linked_paths: BTreeSet::new(),
1124 target_platform: None,
1125 target_platforms: vec![],
1126 platforms: None,
1127 }; "no target platform")]
1128 #[test_case(r#"{
1129 "target-directory": "/foo",
1130 "base-output-directories": [],
1131 "non-test-binaries": {},
1132 "linked-paths": [],
1133 "target-platform": "x86_64-unknown-linux-gnu"
1134 }"#, RustBuildMetaSummary {
1135 target_directory: "/foo".into(),
1136 build_directory: None,
1137 base_output_directories: BTreeSet::new(),
1138 non_test_binaries: BTreeMap::new(),
1139 build_script_out_dirs: BTreeMap::new(),
1140 build_script_info: None,
1141 linked_paths: BTreeSet::new(),
1142 target_platform: Some("x86_64-unknown-linux-gnu".to_owned()),
1143 target_platforms: vec![],
1144 platforms: None,
1145 }; "single target platform specified")]
1146 #[test_case(r#"{
1147 "target-directory": "/foo",
1148 "base-output-directories": [],
1149 "non-test-binaries": {
1150 "my-package-id": [
1151 {
1152 "name": "my-name",
1153 "kind": "bin-exe",
1154 "path": "debug/my-name"
1155 }
1156 ]
1157 },
1158 "linked-paths": []
1159 }"#, RustBuildMetaSummary {
1160 target_directory: "/foo".into(),
1161 build_directory: None,
1162 base_output_directories: BTreeSet::new(),
1163 non_test_binaries: BTreeMap::from([("my-package-id".to_owned(), BTreeSet::from([
1164 RustNonTestBinarySummary {
1165 name: "my-name".to_owned(),
1166 kind: RustNonTestBinaryKind::BIN_EXE,
1167 path: "debug/my-name".into(),
1168 build_platform: None,
1169 },
1170 ]))]),
1171 build_script_out_dirs: BTreeMap::new(),
1172 build_script_info: None,
1173 linked_paths: BTreeSet::new(),
1174 target_platform: None,
1175 target_platforms: vec![],
1176 platforms: None,
1177 }; "non-test binary without a build platform")]
1178 fn test_deserialize_old_rust_build_meta(input: &str, expected: RustBuildMetaSummary) {
1179 let build_meta: RustBuildMetaSummary =
1180 serde_json::from_str(input).expect("input deserialized correctly");
1181 assert_eq!(
1182 build_meta, expected,
1183 "deserialized input matched expected output"
1184 );
1185 }
1186
1187 #[test]
1188 fn test_binary_id_ord() {
1189 let empty = RustBinaryId::new("");
1190 let foo = RustBinaryId::new("foo");
1191 let bar = RustBinaryId::new("bar");
1192 let foo_name1 = RustBinaryId::new("foo::name1");
1193 let foo_name2 = RustBinaryId::new("foo::name2");
1194 let bar_name = RustBinaryId::new("bar::name");
1195 let foo_bin_name1 = RustBinaryId::new("foo::bin/name1");
1196 let foo_bin_name2 = RustBinaryId::new("foo::bin/name2");
1197 let bar_bin_name = RustBinaryId::new("bar::bin/name");
1198 let foo_proc_macro_name = RustBinaryId::new("foo::proc_macro/name");
1199 let bar_proc_macro_name = RustBinaryId::new("bar::proc_macro/name");
1200
1201 let sorted_ids = [
1203 empty,
1204 bar,
1205 bar_name,
1206 bar_bin_name,
1207 bar_proc_macro_name,
1208 foo,
1209 foo_name1,
1210 foo_name2,
1211 foo_bin_name1,
1212 foo_bin_name2,
1213 foo_proc_macro_name,
1214 ];
1215
1216 for (i, id) in sorted_ids.iter().enumerate() {
1217 for (j, other_id) in sorted_ids.iter().enumerate() {
1218 let expected = i.cmp(&j);
1219 assert_eq!(
1220 id.cmp(other_id),
1221 expected,
1222 "comparing {id:?} to {other_id:?} gave {expected:?}"
1223 );
1224 }
1225 }
1226 }
1227
1228 #[test]
1230 fn mismatch_reason_all_variants_is_complete() {
1231 fn check_exhaustive(reason: MismatchReason) {
1233 match reason {
1234 MismatchReason::NotBenchmark
1235 | MismatchReason::Ignored
1236 | MismatchReason::String
1237 | MismatchReason::Expression
1238 | MismatchReason::Partition
1239 | MismatchReason::RerunAlreadyPassed
1240 | MismatchReason::DefaultFilter => {}
1241 }
1242 }
1243
1244 for &reason in MismatchReason::ALL_VARIANTS {
1245 check_exhaustive(reason);
1246 }
1247
1248 assert_eq!(MismatchReason::ALL_VARIANTS.len(), 7);
1250 }
1251 #[test]
1252 fn mismatch_reason_predicates() {
1253 assert!(MismatchReason::Ignored.is_ignore_mismatch());
1254 for &reason in MismatchReason::ALL_VARIANTS {
1255 if reason != MismatchReason::Ignored {
1256 assert!(
1257 !reason.is_ignore_mismatch(),
1258 "{reason:?} is not an ignore mismatch"
1259 );
1260 }
1261 }
1262
1263 assert!(!MismatchReason::NotBenchmark.is_substantive_skip());
1264 for &reason in MismatchReason::ALL_VARIANTS {
1265 if reason != MismatchReason::NotBenchmark {
1266 assert!(
1267 reason.is_substantive_skip(),
1268 "{reason:?} is a substantive skip"
1269 );
1270 }
1271 }
1272 }
1273}