1use super::{FinalStatusLevel, StatusLevel, TestOutputDisplay};
10#[cfg(test)]
11use crate::output_spec::ArbitraryOutputSpec;
12use crate::{
13 config::{
14 elements::{
15 FlakyResult, JunitFlakyFailStatus, LeakTimeoutResult, ReportSkipPolicy,
16 SlowTimeoutResult, TestGroup,
17 },
18 scripts::ScriptId,
19 },
20 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
21 list::{OwnedTestInstanceId, TestInstanceId, TestList},
22 output_spec::{LiveSpec, OutputSpec, SerializableOutputSpec},
23 runner::{StressCondition, StressCount},
24 test_output::{ChildExecutionOutput, ChildOutput, ChildSingleOutput},
25};
26use chrono::{DateTime, FixedOffset};
27use nextest_metadata::MismatchReason;
28use quick_junit::ReportUuid;
29use serde::{Deserialize, Serialize};
30use smol_str::SmolStr;
31use std::{
32 collections::BTreeMap, ffi::c_int, fmt, num::NonZero, process::ExitStatus, time::Duration,
33};
34
35pub const SIGTERM: c_int = 15;
40
41#[derive(Clone, Debug)]
43pub enum ReporterEvent<'a> {
44 Tick,
46
47 Test(Box<TestEvent<'a>>),
49}
50#[derive(Clone, Debug)]
55pub struct TestEvent<'a> {
56 pub timestamp: DateTime<FixedOffset>,
58
59 pub elapsed: Duration,
61
62 pub kind: TestEventKind<'a>,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "kebab-case")]
72pub struct TestSlotAssignment {
73 pub global_slot: u64,
76
77 pub group_slot: Option<u64>,
80
81 pub test_group: TestGroup,
83}
84
85#[derive(Clone, Debug)]
89pub enum TestEventKind<'a> {
90 RunStarted {
92 test_list: &'a TestList<'a>,
96
97 run_id: ReportUuid,
99
100 profile_name: String,
102
103 cli_args: Vec<String>,
105
106 stress_condition: Option<StressCondition>,
108 },
109
110 StressSubRunStarted {
112 progress: StressProgress,
114 },
115
116 SetupScriptStarted {
118 stress_index: Option<StressIndex>,
120
121 index: usize,
123
124 total: usize,
126
127 script_id: ScriptId,
129
130 program: String,
132
133 args: Vec<String>,
135
136 no_capture: bool,
138 },
139
140 SetupScriptSlow {
142 stress_index: Option<StressIndex>,
144
145 script_id: ScriptId,
147
148 program: String,
150
151 args: Vec<String>,
153
154 elapsed: Duration,
156
157 will_terminate: bool,
159 },
160
161 SetupScriptFinished {
163 stress_index: Option<StressIndex>,
165
166 index: usize,
168
169 total: usize,
171
172 script_id: ScriptId,
174
175 program: String,
177
178 args: Vec<String>,
180
181 junit_store_success_output: bool,
183
184 junit_store_failure_output: bool,
186
187 no_capture: bool,
189
190 run_status: SetupScriptExecuteStatus<LiveSpec>,
192 },
193
194 TestStarted {
199 stress_index: Option<StressIndex>,
201
202 test_instance: TestInstanceId<'a>,
204
205 slot_assignment: TestSlotAssignment,
207
208 current_stats: RunStats,
210
211 running: usize,
213
214 command_line: Vec<String>,
216 },
217
218 TestSlow {
220 stress_index: Option<StressIndex>,
222
223 test_instance: TestInstanceId<'a>,
225
226 retry_data: RetryData,
228
229 elapsed: Duration,
231
232 will_terminate: bool,
234 },
235
236 TestAttemptFailedWillRetry {
240 stress_index: Option<StressIndex>,
242
243 test_instance: TestInstanceId<'a>,
245
246 run_status: ExecuteStatus<LiveSpec>,
248
249 delay_before_next_attempt: Duration,
251
252 failure_output: TestOutputDisplay,
254
255 running: usize,
257 },
258
259 TestRetryStarted {
261 stress_index: Option<StressIndex>,
263
264 test_instance: TestInstanceId<'a>,
266
267 slot_assignment: TestSlotAssignment,
270
271 retry_data: RetryData,
273
274 running: usize,
276
277 command_line: Vec<String>,
279 },
280
281 TestFinished {
283 stress_index: Option<StressIndex>,
285
286 test_instance: TestInstanceId<'a>,
288
289 success_output: TestOutputDisplay,
291
292 failure_output: TestOutputDisplay,
294
295 junit_store_success_output: bool,
297
298 junit_store_failure_output: bool,
300
301 junit_flaky_fail_status: JunitFlakyFailStatus,
303
304 run_statuses: ExecutionStatuses<LiveSpec>,
306
307 current_stats: RunStats,
309
310 running: usize,
312 },
313
314 TestSkipped {
316 stress_index: Option<StressIndex>,
318
319 test_instance: TestInstanceId<'a>,
321
322 reason: MismatchReason,
324
325 junit_report_skipped: ReportSkipPolicy,
328 },
329
330 InfoStarted {
332 total: usize,
335
336 run_stats: RunStats,
338 },
339
340 InfoResponse {
342 index: usize,
344
345 total: usize,
347
348 response: InfoResponse<'a>,
350 },
351
352 InfoFinished {
354 missing: usize,
357 },
358
359 InputEnter {
362 current_stats: RunStats,
364
365 running: usize,
367 },
368
369 RunBeginCancel {
371 setup_scripts_running: usize,
373
374 current_stats: RunStats,
378
379 running: usize,
381 },
382
383 RunBeginKill {
385 setup_scripts_running: usize,
387
388 current_stats: RunStats,
392
393 running: usize,
395 },
396
397 RunPaused {
399 setup_scripts_running: usize,
401
402 running: usize,
404 },
405
406 RunContinued {
408 setup_scripts_running: usize,
410
411 running: usize,
413 },
414
415 StressSubRunFinished {
417 progress: StressProgress,
419
420 sub_elapsed: Duration,
422
423 sub_stats: RunStats,
425 },
426
427 RunFinished {
429 run_id: ReportUuid,
431
432 start_time: DateTime<FixedOffset>,
434
435 elapsed: Duration,
437
438 run_stats: RunFinishedStats,
440
441 outstanding_not_seen: Option<TestsNotSeen>,
446 },
447}
448
449#[derive(Clone, Debug)]
451pub struct TestsNotSeen {
452 pub not_seen: Vec<OwnedTestInstanceId>,
459
460 pub total_not_seen: usize,
462}
463
464#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
466#[serde(tag = "progress-type", rename_all = "kebab-case")]
467#[cfg_attr(test, derive(test_strategy::Arbitrary))]
468pub enum StressProgress {
469 Count {
471 total: StressCount,
473
474 elapsed: Duration,
476
477 completed: u32,
479 },
480
481 Time {
483 total: Duration,
485
486 elapsed: Duration,
488
489 completed: u32,
491 },
492}
493
494impl StressProgress {
495 pub fn remaining(&self) -> Option<StressRemaining> {
498 match self {
499 Self::Count {
500 total: StressCount::Count { count },
501 elapsed: _,
502 completed,
503 } => count
504 .get()
505 .checked_sub(*completed)
506 .and_then(|remaining| NonZero::try_from(remaining).ok())
507 .map(StressRemaining::Count),
508 Self::Count {
509 total: StressCount::Infinite,
510 ..
511 } => Some(StressRemaining::Infinite),
512 Self::Time {
513 total,
514 elapsed,
515 completed: _,
516 } => total.checked_sub(*elapsed).map(StressRemaining::Time),
517 }
518 }
519
520 pub fn unique_id(&self, run_id: ReportUuid) -> String {
522 let stress_current = match self {
523 Self::Count { completed, .. } | Self::Time { completed, .. } => *completed,
524 };
525 format!("{}:@stress-{}", run_id, stress_current)
526 }
527}
528
529#[derive(Clone, Debug)]
531pub enum StressRemaining {
532 Count(NonZero<u32>),
534
535 Infinite,
537
538 Time(Duration),
540}
541
542#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
544#[serde(rename_all = "kebab-case")]
545#[cfg_attr(test, derive(test_strategy::Arbitrary))]
546pub struct StressIndex {
547 pub current: u32,
549
550 pub total: Option<NonZero<u32>>,
552}
553
554impl StressIndex {
555 pub fn total_get(&self) -> Option<u32> {
557 self.total.map(|t| t.get())
558 }
559}
560
561#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
563#[serde(tag = "type", rename_all = "kebab-case")]
564#[cfg_attr(test, derive(test_strategy::Arbitrary))]
565pub enum RunFinishedStats {
566 Single(RunStats),
568
569 Stress(StressRunStats),
571}
572
573impl RunFinishedStats {
574 pub fn final_stats(&self) -> FinalRunStats {
577 match self {
578 Self::Single(stats) => stats.summarize_final(),
579 Self::Stress(stats) => stats.last_final_stats,
580 }
581 }
582}
583
584#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
586#[serde(rename_all = "kebab-case")]
587#[cfg_attr(test, derive(test_strategy::Arbitrary))]
588pub struct RunStats {
589 pub initial_run_count: usize,
593
594 pub finished_count: usize,
596
597 pub setup_scripts_initial_count: usize,
601
602 pub setup_scripts_finished_count: usize,
604
605 pub setup_scripts_passed: usize,
607
608 pub setup_scripts_failed: usize,
610
611 pub setup_scripts_exec_failed: usize,
613
614 pub setup_scripts_timed_out: usize,
616
617 pub passed: usize,
620
621 pub passed_slow: usize,
623
624 pub passed_timed_out: usize,
626
627 pub flaky: usize,
629
630 pub failed: usize,
633
634 pub failed_slow: usize,
636
637 pub failed_timed_out: usize,
639
640 pub leaky: usize,
642
643 pub leaky_failed: usize,
648
649 pub exec_failed: usize,
651
652 pub skipped: usize,
654
655 pub cancel_reason: Option<CancelReason>,
657}
658
659impl RunStats {
660 pub fn has_failures(&self) -> bool {
662 self.failed_setup_script_count() > 0 || self.failed_count() > 0
663 }
664
665 pub fn failed_setup_script_count(&self) -> usize {
667 self.setup_scripts_failed + self.setup_scripts_exec_failed + self.setup_scripts_timed_out
668 }
669
670 pub fn failed_count(&self) -> usize {
672 self.failed + self.exec_failed + self.failed_timed_out
673 }
674
675 pub fn summarize_final(&self) -> FinalRunStats {
677 if self.failed_setup_script_count() > 0 {
680 if self.cancel_reason > Some(CancelReason::TestFailure) {
683 FinalRunStats::Cancelled {
684 reason: self.cancel_reason,
685 kind: RunStatsFailureKind::SetupScript,
686 }
687 } else {
688 FinalRunStats::Failed {
689 kind: RunStatsFailureKind::SetupScript,
690 }
691 }
692 } else if self.setup_scripts_initial_count > self.setup_scripts_finished_count {
693 FinalRunStats::Cancelled {
694 reason: self.cancel_reason,
695 kind: RunStatsFailureKind::SetupScript,
696 }
697 } else if self.failed_count() > 0 {
698 let kind = RunStatsFailureKind::Test {
699 initial_run_count: self.initial_run_count,
700 not_run: self.initial_run_count.saturating_sub(self.finished_count),
701 };
702
703 if self.cancel_reason > Some(CancelReason::TestFailure) {
706 FinalRunStats::Cancelled {
707 reason: self.cancel_reason,
708 kind,
709 }
710 } else {
711 FinalRunStats::Failed { kind }
712 }
713 } else if self.initial_run_count > self.finished_count {
714 FinalRunStats::Cancelled {
715 reason: self.cancel_reason,
716 kind: RunStatsFailureKind::Test {
717 initial_run_count: self.initial_run_count,
718 not_run: self.initial_run_count.saturating_sub(self.finished_count),
719 },
720 }
721 } else if self.finished_count == 0 {
722 FinalRunStats::NoTestsRun
723 } else {
724 FinalRunStats::Success
725 }
726 }
727
728 pub(crate) fn on_setup_script_finished(&mut self, status: &SetupScriptExecuteStatus<LiveSpec>) {
729 self.setup_scripts_finished_count += 1;
730
731 match status.result {
732 ExecutionResultDescription::Pass
733 | ExecutionResultDescription::Leak {
734 result: LeakTimeoutResult::Pass,
735 } => {
736 self.setup_scripts_passed += 1;
737 }
738 ExecutionResultDescription::Fail { .. }
739 | ExecutionResultDescription::Leak {
740 result: LeakTimeoutResult::Fail,
741 } => {
742 self.setup_scripts_failed += 1;
743 }
744 ExecutionResultDescription::ExecFail => {
745 self.setup_scripts_exec_failed += 1;
746 }
747 ExecutionResultDescription::Timeout { .. } => {
749 self.setup_scripts_timed_out += 1;
750 }
751 }
752 }
753
754 pub(crate) fn on_test_finished(&mut self, run_statuses: &ExecutionStatuses<LiveSpec>) {
755 self.finished_count += 1;
756 let last_status = run_statuses.last_status();
765 match last_status.result {
766 ExecutionResultDescription::Pass => {
767 let is_flaky = run_statuses.len() > 1;
771 if is_flaky {
772 match run_statuses.flaky_result() {
773 FlakyResult::Fail => {
774 self.failed += 1;
775 if last_status.is_slow {
776 self.failed_slow += 1;
777 }
778 }
779 FlakyResult::Pass => {
780 self.passed += 1;
781 if last_status.is_slow {
782 self.passed_slow += 1;
783 }
784 self.flaky += 1;
785 }
786 }
787 } else {
788 self.passed += 1;
789 if last_status.is_slow {
790 self.passed_slow += 1;
791 }
792 }
793 }
794 ExecutionResultDescription::Leak {
795 result: LeakTimeoutResult::Pass,
796 } => {
797 let is_flaky = run_statuses.len() > 1;
798 if is_flaky {
799 match run_statuses.flaky_result() {
800 FlakyResult::Fail => {
801 self.failed += 1;
802 if last_status.is_slow {
803 self.failed_slow += 1;
804 }
805 self.leaky += 1;
807 }
808 FlakyResult::Pass => {
809 self.passed += 1;
810 self.leaky += 1;
811 if last_status.is_slow {
812 self.passed_slow += 1;
813 }
814 self.flaky += 1;
815 }
816 }
817 } else {
818 self.passed += 1;
819 self.leaky += 1;
820 if last_status.is_slow {
821 self.passed_slow += 1;
822 }
823 }
824 }
825 ExecutionResultDescription::Leak {
826 result: LeakTimeoutResult::Fail,
827 } => {
828 self.failed += 1;
829 self.leaky_failed += 1;
830 if last_status.is_slow {
831 self.failed_slow += 1;
832 }
833 }
834 ExecutionResultDescription::Fail { .. } => {
835 self.failed += 1;
836 if last_status.is_slow {
837 self.failed_slow += 1;
838 }
839 }
840 ExecutionResultDescription::Timeout {
841 result: SlowTimeoutResult::Pass,
842 } => {
843 let is_flaky = run_statuses.len() > 1;
844 if is_flaky {
845 match run_statuses.flaky_result() {
846 FlakyResult::Fail => {
847 self.failed += 1;
848 if last_status.is_slow {
851 self.failed_slow += 1;
852 }
853 }
854 FlakyResult::Pass => {
855 self.passed += 1;
856 self.passed_timed_out += 1;
857 self.flaky += 1;
858 }
859 }
860 } else {
861 self.passed += 1;
862 self.passed_timed_out += 1;
863 }
864 }
865 ExecutionResultDescription::Timeout {
866 result: SlowTimeoutResult::Fail,
867 } => {
868 self.failed_timed_out += 1;
869 }
870 ExecutionResultDescription::ExecFail => self.exec_failed += 1,
871 }
872 }
873}
874
875#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
877#[serde(tag = "outcome", rename_all = "kebab-case")]
878#[cfg_attr(test, derive(test_strategy::Arbitrary))]
879pub enum FinalRunStats {
880 Success,
882
883 NoTestsRun,
885
886 Cancelled {
888 reason: Option<CancelReason>,
893
894 kind: RunStatsFailureKind,
896 },
897
898 Failed {
900 kind: RunStatsFailureKind,
902 },
903}
904
905#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
907#[serde(rename_all = "kebab-case")]
908#[cfg_attr(test, derive(test_strategy::Arbitrary))]
909pub struct StressRunStats {
910 pub completed: StressIndex,
912
913 pub success_count: u32,
915
916 pub failed_count: u32,
918
919 pub last_final_stats: FinalRunStats,
921}
922
923impl StressRunStats {
924 pub fn summarize_final(&self) -> StressFinalRunStats {
926 if self.failed_count > 0 {
927 StressFinalRunStats::Failed
928 } else if matches!(self.last_final_stats, FinalRunStats::Cancelled { .. }) {
929 StressFinalRunStats::Cancelled
930 } else if matches!(self.last_final_stats, FinalRunStats::NoTestsRun) {
931 StressFinalRunStats::NoTestsRun
932 } else {
933 StressFinalRunStats::Success
934 }
935 }
936}
937
938pub enum StressFinalRunStats {
940 Success,
942
943 NoTestsRun,
945
946 Cancelled,
948
949 Failed,
951}
952
953#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
955#[serde(tag = "step", rename_all = "kebab-case")]
956#[cfg_attr(test, derive(test_strategy::Arbitrary))]
957pub enum RunStatsFailureKind {
958 SetupScript,
960
961 Test {
963 initial_run_count: usize,
965
966 not_run: usize,
969 },
970}
971
972#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
977#[derive(Serialize)]
978#[serde(
979 rename_all = "kebab-case",
980 bound(serialize = "S: SerializableOutputSpec")
981)]
982#[cfg_attr(
983 test,
984 derive(test_strategy::Arbitrary),
985 arbitrary(bound(S: ArbitraryOutputSpec))
986)]
987pub struct ExecutionStatuses<S: OutputSpec> {
988 #[cfg_attr(test, strategy(proptest::collection::vec(proptest::arbitrary::any::<ExecuteStatus<S>>(), 1..=3)))]
990 statuses: Vec<ExecuteStatus<S>>,
991
992 #[serde(default)]
994 flaky_result: FlakyResult,
995}
996
997impl<'de, S: SerializableOutputSpec> Deserialize<'de> for ExecutionStatuses<S> {
998 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
999 #[derive(Deserialize)]
1002 #[serde(
1003 rename_all = "kebab-case",
1004 bound(deserialize = "S: SerializableOutputSpec")
1005 )]
1006 struct Helper<S: OutputSpec> {
1007 statuses: Vec<ExecuteStatus<S>>,
1008 #[serde(default)]
1009 flaky_result: FlakyResult,
1010 }
1011
1012 let helper = Helper::<S>::deserialize(deserializer)?;
1013 if helper.statuses.is_empty() {
1014 return Err(serde::de::Error::custom("expected non-empty statuses"));
1015 }
1016 Ok(Self {
1017 statuses: helper.statuses,
1018 flaky_result: helper.flaky_result,
1019 })
1020 }
1021}
1022
1023#[expect(clippy::len_without_is_empty)] impl<S: OutputSpec> ExecutionStatuses<S> {
1025 pub(crate) fn new(statuses: Vec<ExecuteStatus<S>>, flaky_result: FlakyResult) -> Self {
1026 debug_assert!(!statuses.is_empty(), "ExecutionStatuses must be non-empty");
1027 Self {
1028 statuses,
1029 flaky_result,
1030 }
1031 }
1032
1033 pub fn flaky_result(&self) -> FlakyResult {
1035 self.flaky_result
1036 }
1037
1038 pub fn last_status(&self) -> &ExecuteStatus<S> {
1042 self.statuses
1043 .last()
1044 .expect("execution statuses is non-empty")
1045 }
1046
1047 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &'_ ExecuteStatus<S>> + '_ {
1049 self.statuses.iter()
1050 }
1051
1052 pub fn len(&self) -> usize {
1054 self.statuses.len()
1055 }
1056
1057 pub fn describe(&self) -> ExecutionDescription<'_, S> {
1059 let last_status = self.last_status();
1060 if last_status.result.is_success() {
1061 if self.statuses.len() > 1 {
1062 ExecutionDescription::Flaky {
1063 last_status,
1064 prior_statuses: &self.statuses[..self.statuses.len() - 1],
1065 result: self.flaky_result,
1066 }
1067 } else {
1068 ExecutionDescription::Success {
1069 single_status: last_status,
1070 }
1071 }
1072 } else {
1073 let first_status = self
1074 .statuses
1075 .first()
1076 .expect("execution statuses is non-empty");
1077 let retries = &self.statuses[1..];
1078 ExecutionDescription::Failure {
1079 first_status,
1080 last_status,
1081 retries,
1082 }
1083 }
1084 }
1085}
1086
1087impl<S: OutputSpec> IntoIterator for ExecutionStatuses<S> {
1088 type Item = ExecuteStatus<S>;
1089 type IntoIter = std::vec::IntoIter<ExecuteStatus<S>>;
1090
1091 fn into_iter(self) -> Self::IntoIter {
1092 self.statuses.into_iter()
1093 }
1094}
1095
1096#[derive_where::derive_where(Debug; S::ChildOutputDesc)]
1103pub enum ExecutionDescription<'a, S: OutputSpec> {
1104 Success {
1106 single_status: &'a ExecuteStatus<S>,
1108 },
1109
1110 Flaky {
1112 last_status: &'a ExecuteStatus<S>,
1114
1115 prior_statuses: &'a [ExecuteStatus<S>],
1117
1118 result: FlakyResult,
1120 },
1121
1122 Failure {
1124 first_status: &'a ExecuteStatus<S>,
1126
1127 last_status: &'a ExecuteStatus<S>,
1129
1130 retries: &'a [ExecuteStatus<S>],
1134 },
1135}
1136
1137impl<S: OutputSpec> Clone for ExecutionDescription<'_, S> {
1140 fn clone(&self) -> Self {
1141 *self
1142 }
1143}
1144
1145impl<S: OutputSpec> Copy for ExecutionDescription<'_, S> {}
1146
1147impl<'a, S: OutputSpec> ExecutionDescription<'a, S> {
1148 pub fn status_level(&self) -> StatusLevel {
1150 match self {
1151 ExecutionDescription::Success { single_status } => match single_status.result {
1152 ExecutionResultDescription::Leak {
1153 result: LeakTimeoutResult::Pass,
1154 } => StatusLevel::Leak,
1155 ExecutionResultDescription::Pass => StatusLevel::Pass,
1156 ExecutionResultDescription::Timeout {
1157 result: SlowTimeoutResult::Pass,
1158 } => StatusLevel::Slow,
1159 ref other => unreachable!(
1160 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
1161 ),
1162 },
1163 ExecutionDescription::Flaky {
1165 result: FlakyResult::Pass,
1166 ..
1167 } => StatusLevel::Retry,
1168 ExecutionDescription::Flaky {
1169 result: FlakyResult::Fail,
1170 ..
1171 } => StatusLevel::Fail,
1172 ExecutionDescription::Failure { .. } => StatusLevel::Fail,
1173 }
1174 }
1175
1176 pub fn final_status_level(&self) -> FinalStatusLevel {
1178 match self {
1179 ExecutionDescription::Success { single_status, .. } => {
1180 if single_status.is_slow {
1182 FinalStatusLevel::Slow
1183 } else {
1184 match single_status.result {
1185 ExecutionResultDescription::Pass => FinalStatusLevel::Pass,
1186 ExecutionResultDescription::Leak {
1187 result: LeakTimeoutResult::Pass,
1188 } => FinalStatusLevel::Leak,
1189 ExecutionResultDescription::Timeout {
1193 result: SlowTimeoutResult::Pass,
1194 } => FinalStatusLevel::Slow,
1195 ref other => unreachable!(
1196 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
1197 ),
1198 }
1199 }
1200 }
1201 ExecutionDescription::Flaky {
1203 result: FlakyResult::Pass,
1204 ..
1205 } => FinalStatusLevel::Flaky,
1206 ExecutionDescription::Flaky {
1208 result: FlakyResult::Fail,
1209 ..
1210 } => FinalStatusLevel::Fail,
1211 ExecutionDescription::Failure { .. } => FinalStatusLevel::Fail,
1212 }
1213 }
1214
1215 pub fn is_success_for_output(&self) -> bool {
1233 match self {
1234 ExecutionDescription::Success { .. } => true,
1235 ExecutionDescription::Flaky { .. } => true,
1239 ExecutionDescription::Failure { .. } => false,
1240 }
1241 }
1242
1243 pub fn last_status(&self) -> &'a ExecuteStatus<S> {
1245 match self {
1246 ExecutionDescription::Success {
1247 single_status: last_status,
1248 }
1249 | ExecutionDescription::Flaky { last_status, .. }
1250 | ExecutionDescription::Failure { last_status, .. } => last_status,
1251 }
1252 }
1253}
1254
1255#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1261#[serde(rename_all = "kebab-case")]
1262#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1263pub struct ErrorSummary {
1264 pub short_message: String,
1266
1267 pub description: String,
1269}
1270
1271#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1276#[serde(rename_all = "kebab-case")]
1277#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1278pub struct OutputErrorSlice {
1279 pub slice: String,
1281
1282 pub start: usize,
1284}
1285
1286#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1295#[derive(Serialize, Deserialize)]
1296#[serde(
1297 rename_all = "kebab-case",
1298 bound(
1299 serialize = "S: SerializableOutputSpec",
1300 deserialize = "S: SerializableOutputSpec"
1301 )
1302)]
1303#[cfg_attr(
1304 test,
1305 derive(test_strategy::Arbitrary),
1306 arbitrary(bound(S: ArbitraryOutputSpec))
1307)]
1308pub struct ExecuteStatus<S: OutputSpec> {
1309 pub retry_data: RetryData,
1311 pub output: ChildExecutionOutputDescription<S>,
1313 pub result: ExecutionResultDescription,
1315 #[cfg_attr(
1317 test,
1318 strategy(crate::reporter::test_helpers::arb_datetime_fixed_offset())
1319 )]
1320 pub start_time: DateTime<FixedOffset>,
1321 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1323 pub time_taken: Duration,
1324 pub is_slow: bool,
1326 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1328 pub delay_before_start: Duration,
1329 pub error_summary: Option<ErrorSummary>,
1334 pub output_error_slice: Option<OutputErrorSlice>,
1339}
1340
1341#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1350#[derive(Serialize, Deserialize)]
1351#[serde(
1352 rename_all = "kebab-case",
1353 bound(
1354 serialize = "S: SerializableOutputSpec",
1355 deserialize = "S: SerializableOutputSpec"
1356 )
1357)]
1358#[cfg_attr(
1359 test,
1360 derive(test_strategy::Arbitrary),
1361 arbitrary(bound(S: ArbitraryOutputSpec))
1362)]
1363pub struct SetupScriptExecuteStatus<S: OutputSpec> {
1364 pub output: ChildExecutionOutputDescription<S>,
1366
1367 pub result: ExecutionResultDescription,
1369
1370 #[cfg_attr(
1372 test,
1373 strategy(crate::reporter::test_helpers::arb_datetime_fixed_offset())
1374 )]
1375 pub start_time: DateTime<FixedOffset>,
1376
1377 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1379 pub time_taken: Duration,
1380
1381 pub is_slow: bool,
1383
1384 pub env_map: Option<SetupScriptEnvMap>,
1389
1390 pub error_summary: Option<ErrorSummary>,
1395}
1396
1397#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1401#[serde(rename_all = "kebab-case")]
1402#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1403pub struct SetupScriptEnvMap {
1404 pub env_map: BTreeMap<String, String>,
1406}
1407
1408#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1417#[derive(Serialize, Deserialize)]
1418#[serde(
1419 tag = "type",
1420 rename_all = "kebab-case",
1421 bound(
1422 serialize = "S: SerializableOutputSpec",
1423 deserialize = "S: SerializableOutputSpec"
1424 )
1425)]
1426#[cfg_attr(
1427 test,
1428 derive(test_strategy::Arbitrary),
1429 arbitrary(bound(S: ArbitraryOutputSpec))
1430)]
1431pub enum ChildExecutionOutputDescription<S: OutputSpec> {
1432 Output {
1434 result: Option<ExecutionResultDescription>,
1438
1439 output: S::ChildOutputDesc,
1441
1442 errors: Option<ErrorList<ChildErrorDescription>>,
1445 },
1446
1447 StartError(ChildStartErrorDescription),
1449}
1450
1451impl<S: OutputSpec> ChildExecutionOutputDescription<S> {
1452 pub fn has_errors(&self) -> bool {
1454 match self {
1455 Self::Output { errors, result, .. } => {
1456 if errors.is_some() {
1457 return true;
1458 }
1459 if let Some(result) = result {
1460 return !result.is_success();
1461 }
1462 false
1463 }
1464 Self::StartError(_) => true,
1465 }
1466 }
1467}
1468
1469#[derive(Clone, Debug)]
1481pub enum ChildOutputDescription {
1482 Split {
1484 stdout: Option<ChildSingleOutput>,
1486 stderr: Option<ChildSingleOutput>,
1488 },
1489
1490 Combined {
1492 output: ChildSingleOutput,
1494 },
1495
1496 NotLoaded,
1502}
1503
1504impl ChildOutputDescription {
1505 pub fn stdout_stderr_len(&self) -> (Option<u64>, Option<u64>) {
1509 match self {
1510 Self::Split { stdout, stderr } => (
1511 stdout.as_ref().map(|s| s.buf().len() as u64),
1512 stderr.as_ref().map(|s| s.buf().len() as u64),
1513 ),
1514 Self::Combined { output } => (Some(output.buf().len() as u64), None),
1515 Self::NotLoaded => {
1516 unreachable!(
1517 "attempted to get output lengths from output that was not loaded \
1518 (this method is only called from the live runner, where NotLoaded \
1519 is never produced)"
1520 );
1521 }
1522 }
1523 }
1524}
1525
1526#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1530#[serde(tag = "kind", rename_all = "kebab-case")]
1531#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1532pub enum ChildStartErrorDescription {
1533 TempPath {
1535 source: SerializableError,
1537 },
1538
1539 Spawn {
1541 source: SerializableError,
1543 },
1544}
1545
1546impl fmt::Display for ChildStartErrorDescription {
1547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1548 match self {
1549 Self::TempPath { .. } => {
1550 write!(f, "error creating temporary path for setup script")
1551 }
1552 Self::Spawn { .. } => write!(f, "error spawning child process"),
1553 }
1554 }
1555}
1556
1557impl std::error::Error for ChildStartErrorDescription {
1558 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1559 match self {
1560 Self::TempPath { source } | Self::Spawn { source } => Some(source),
1561 }
1562 }
1563}
1564
1565#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1569#[serde(tag = "kind", rename_all = "kebab-case")]
1570#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1571pub enum ChildErrorDescription {
1572 ReadStdout {
1574 source: SerializableError,
1576 },
1577
1578 ReadStderr {
1580 source: SerializableError,
1582 },
1583
1584 ReadCombined {
1586 source: SerializableError,
1588 },
1589
1590 Wait {
1592 source: SerializableError,
1594 },
1595
1596 SetupScriptOutput {
1598 source: SerializableError,
1600 },
1601}
1602
1603impl fmt::Display for ChildErrorDescription {
1604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1605 match self {
1606 Self::ReadStdout { .. } => write!(f, "error reading standard output"),
1607 Self::ReadStderr { .. } => write!(f, "error reading standard error"),
1608 Self::ReadCombined { .. } => {
1609 write!(f, "error reading combined stream")
1610 }
1611 Self::Wait { .. } => {
1612 write!(f, "error waiting for child process to exit")
1613 }
1614 Self::SetupScriptOutput { .. } => {
1615 write!(f, "error reading setup script output")
1616 }
1617 }
1618 }
1619}
1620
1621impl std::error::Error for ChildErrorDescription {
1622 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1623 match self {
1624 Self::ReadStdout { source }
1625 | Self::ReadStderr { source }
1626 | Self::ReadCombined { source }
1627 | Self::Wait { source }
1628 | Self::SetupScriptOutput { source } => Some(source),
1629 }
1630 }
1631}
1632
1633#[derive(Clone, Debug, PartialEq, Eq)]
1638pub struct SerializableError {
1639 message: String,
1640 source: Option<Box<SerializableError>>,
1641}
1642
1643impl SerializableError {
1644 pub fn new(error: &dyn std::error::Error) -> Self {
1647 let message = error.to_string();
1648 let mut causes = Vec::new();
1649 let mut source = error.source();
1650 while let Some(err) = source {
1651 causes.push(err.to_string());
1652 source = err.source();
1653 }
1654 Self::from_message_and_causes(message, causes)
1655 }
1656
1657 pub fn from_message_and_causes(message: String, causes: Vec<String>) -> Self {
1660 let mut next = None;
1664 for cause in causes.into_iter().rev() {
1665 let error = Self {
1666 message: cause,
1667 source: next.map(Box::new),
1668 };
1669 next = Some(error);
1670 }
1671 Self {
1672 message,
1673 source: next.map(Box::new),
1674 }
1675 }
1676
1677 pub fn message(&self) -> &str {
1679 &self.message
1680 }
1681
1682 pub fn sources(&self) -> SerializableErrorSources<'_> {
1684 SerializableErrorSources {
1685 current: self.source.as_deref(),
1686 }
1687 }
1688}
1689
1690impl fmt::Display for SerializableError {
1691 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1692 f.write_str(&self.message)
1693 }
1694}
1695
1696impl std::error::Error for SerializableError {
1697 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1698 self.source
1699 .as_deref()
1700 .map(|s| s as &(dyn std::error::Error + 'static))
1701 }
1702}
1703
1704#[derive(Debug)]
1706pub struct SerializableErrorSources<'a> {
1707 current: Option<&'a SerializableError>,
1708}
1709
1710impl<'a> Iterator for SerializableErrorSources<'a> {
1711 type Item = &'a SerializableError;
1712
1713 fn next(&mut self) -> Option<Self::Item> {
1714 let current = self.current?;
1715 self.current = current.source.as_deref();
1716 Some(current)
1717 }
1718}
1719
1720mod serializable_error_serde {
1721 use super::*;
1722
1723 #[derive(Serialize, Deserialize)]
1724 struct Ser {
1725 message: String,
1726 #[serde(default)]
1729 causes: Vec<String>,
1730 }
1731
1732 impl Serialize for SerializableError {
1733 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1734 let mut causes = Vec::new();
1735 let mut cause = self.source.as_ref();
1736 while let Some(c) = cause {
1737 causes.push(c.message.clone());
1738 cause = c.source.as_ref();
1739 }
1740
1741 let ser = Ser {
1742 message: self.message.clone(),
1743 causes,
1744 };
1745 ser.serialize(serializer)
1746 }
1747 }
1748
1749 impl<'de> Deserialize<'de> for SerializableError {
1750 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1751 let ser = Ser::deserialize(deserializer)?;
1752 Ok(SerializableError::from_message_and_causes(
1753 ser.message,
1754 ser.causes,
1755 ))
1756 }
1757 }
1758}
1759
1760#[cfg(test)]
1761mod serializable_error_arbitrary {
1762 use super::*;
1763 use proptest::prelude::*;
1764
1765 impl Arbitrary for SerializableError {
1766 type Parameters = ();
1767 type Strategy = BoxedStrategy<Self>;
1768
1769 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1770 (
1771 any::<String>(),
1772 proptest::collection::vec(any::<String>(), 0..3),
1773 )
1774 .prop_map(|(message, causes)| {
1775 SerializableError::from_message_and_causes(message, causes)
1776 })
1777 .boxed()
1778 }
1779 }
1780}
1781
1782impl From<ChildExecutionOutput> for ChildExecutionOutputDescription<LiveSpec> {
1783 fn from(output: ChildExecutionOutput) -> Self {
1784 match output {
1785 ChildExecutionOutput::Output {
1786 result,
1787 output,
1788 errors,
1789 } => Self::Output {
1790 result: result.map(ExecutionResultDescription::from),
1791 output: ChildOutputDescription::from(output),
1792 errors: errors.map(|e| e.map(ChildErrorDescription::from)),
1793 },
1794 ChildExecutionOutput::StartError(error) => {
1795 Self::StartError(ChildStartErrorDescription::from(error))
1796 }
1797 }
1798 }
1799}
1800
1801impl From<ChildOutput> for ChildOutputDescription {
1802 fn from(output: ChildOutput) -> Self {
1803 match output {
1804 ChildOutput::Split(split) => Self::Split {
1805 stdout: split.stdout,
1806 stderr: split.stderr,
1807 },
1808 ChildOutput::Combined { output } => Self::Combined { output },
1809 }
1810 }
1811}
1812
1813impl From<ChildStartError> for ChildStartErrorDescription {
1814 fn from(error: ChildStartError) -> Self {
1815 match error {
1816 ChildStartError::TempPath(e) => Self::TempPath {
1817 source: SerializableError::new(&*e),
1818 },
1819 ChildStartError::Spawn(e) => Self::Spawn {
1820 source: SerializableError::new(&*e),
1821 },
1822 }
1823 }
1824}
1825
1826impl From<ChildError> for ChildErrorDescription {
1827 fn from(error: ChildError) -> Self {
1828 match error {
1829 ChildError::Fd(ChildFdError::ReadStdout(e)) => Self::ReadStdout {
1830 source: SerializableError::new(&*e),
1831 },
1832 ChildError::Fd(ChildFdError::ReadStderr(e)) => Self::ReadStderr {
1833 source: SerializableError::new(&*e),
1834 },
1835 ChildError::Fd(ChildFdError::ReadCombined(e)) => Self::ReadCombined {
1836 source: SerializableError::new(&*e),
1837 },
1838 ChildError::Fd(ChildFdError::Wait(e)) => Self::Wait {
1839 source: SerializableError::new(&*e),
1840 },
1841 ChildError::SetupScriptOutput(e) => Self::SetupScriptOutput {
1842 source: SerializableError::new(&e),
1843 },
1844 }
1845 }
1846}
1847
1848#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
1850#[serde(rename_all = "kebab-case")]
1851#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1852pub struct RetryData {
1853 pub attempt: u32,
1855
1856 pub total_attempts: u32,
1858}
1859
1860impl RetryData {
1861 pub fn is_last_attempt(&self) -> bool {
1863 self.attempt >= self.total_attempts
1864 }
1865}
1866
1867#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1869pub enum ExecutionResult {
1870 Pass,
1872 Leak {
1876 result: LeakTimeoutResult,
1883 },
1884 Fail {
1886 failure_status: FailureStatus,
1888
1889 leaked: bool,
1893 },
1894 ExecFail,
1896 Timeout {
1898 result: SlowTimeoutResult,
1900 },
1901}
1902
1903impl ExecutionResult {
1904 pub fn is_success(self) -> bool {
1906 match self {
1907 ExecutionResult::Pass
1908 | ExecutionResult::Timeout {
1909 result: SlowTimeoutResult::Pass,
1910 }
1911 | ExecutionResult::Leak {
1912 result: LeakTimeoutResult::Pass,
1913 } => true,
1914 ExecutionResult::Leak {
1915 result: LeakTimeoutResult::Fail,
1916 }
1917 | ExecutionResult::Fail { .. }
1918 | ExecutionResult::ExecFail
1919 | ExecutionResult::Timeout {
1920 result: SlowTimeoutResult::Fail,
1921 } => false,
1922 }
1923 }
1924
1925 pub fn as_static_str(&self) -> &'static str {
1927 match self {
1928 ExecutionResult::Pass => "pass",
1929 ExecutionResult::Leak { .. } => "leak",
1930 ExecutionResult::Fail { .. } => "fail",
1931 ExecutionResult::ExecFail => "exec-fail",
1932 ExecutionResult::Timeout { .. } => "timeout",
1933 }
1934 }
1935}
1936
1937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1939pub enum FailureStatus {
1940 ExitCode(i32),
1942
1943 Abort(AbortStatus),
1945}
1946
1947impl FailureStatus {
1948 pub fn extract(exit_status: ExitStatus) -> Self {
1950 if let Some(abort_status) = AbortStatus::extract(exit_status) {
1951 FailureStatus::Abort(abort_status)
1952 } else {
1953 FailureStatus::ExitCode(
1954 exit_status
1955 .code()
1956 .expect("if abort_status is None, then code must be present"),
1957 )
1958 }
1959 }
1960}
1961
1962#[derive(Copy, Clone, Eq, PartialEq)]
1966pub enum AbortStatus {
1967 #[cfg(unix)]
1969 UnixSignal(i32),
1970
1971 #[cfg(windows)]
1973 WindowsNtStatus(windows_sys::Win32::Foundation::NTSTATUS),
1974
1975 #[cfg(windows)]
1977 JobObject,
1978}
1979
1980impl AbortStatus {
1981 pub fn extract(exit_status: ExitStatus) -> Option<Self> {
1983 cfg_if::cfg_if! {
1984 if #[cfg(unix)] {
1985 use std::os::unix::process::ExitStatusExt;
1987 exit_status.signal().map(AbortStatus::UnixSignal)
1988 } else if #[cfg(windows)] {
1989 exit_status.code().and_then(|code| {
1990 (code < 0).then_some(AbortStatus::WindowsNtStatus(code))
1991 })
1992 } else {
1993 None
1994 }
1995 }
1996 }
1997}
1998
1999impl fmt::Debug for AbortStatus {
2000 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2001 match self {
2002 #[cfg(unix)]
2003 AbortStatus::UnixSignal(signal) => write!(f, "UnixSignal({signal})"),
2004 #[cfg(windows)]
2005 AbortStatus::WindowsNtStatus(status) => write!(f, "WindowsNtStatus({status:x})"),
2006 #[cfg(windows)]
2007 AbortStatus::JobObject => write!(f, "JobObject"),
2008 }
2009 }
2010}
2011
2012#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2018#[serde(tag = "kind", rename_all = "kebab-case")]
2019#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2020#[non_exhaustive]
2021pub enum AbortDescription {
2022 UnixSignal {
2024 signal: i32,
2026 #[cfg_attr(
2029 test,
2030 strategy(proptest::option::of(crate::reporter::test_helpers::arb_smol_str()))
2031 )]
2032 name: Option<SmolStr>,
2033 },
2034
2035 WindowsNtStatus {
2037 code: i32,
2039 #[cfg_attr(
2041 test,
2042 strategy(proptest::option::of(crate::reporter::test_helpers::arb_smol_str()))
2043 )]
2044 message: Option<SmolStr>,
2045 },
2046
2047 WindowsJobObject,
2049}
2050
2051impl fmt::Display for AbortDescription {
2052 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2053 match self {
2054 Self::UnixSignal { signal, name } => {
2055 write!(f, "aborted with signal {signal}")?;
2056 if let Some(name) = name {
2057 write!(f, " (SIG{name})")?;
2058 }
2059 Ok(())
2060 }
2061 Self::WindowsNtStatus { code, message } => {
2062 write!(f, "aborted with code {code:#010x}")?;
2063 if let Some(message) = message {
2064 write!(f, ": {message}")?;
2065 }
2066 Ok(())
2067 }
2068 Self::WindowsJobObject => {
2069 write!(f, "terminated via job object")
2070 }
2071 }
2072 }
2073}
2074
2075impl From<AbortStatus> for AbortDescription {
2076 fn from(status: AbortStatus) -> Self {
2077 cfg_if::cfg_if! {
2078 if #[cfg(unix)] {
2079 match status {
2080 AbortStatus::UnixSignal(signal) => Self::UnixSignal {
2081 signal,
2082 name: crate::helpers::signal_str(signal).map(SmolStr::new_static),
2083 },
2084 }
2085 } else if #[cfg(windows)] {
2086 match status {
2087 AbortStatus::WindowsNtStatus(code) => Self::WindowsNtStatus {
2088 code,
2089 message: crate::helpers::windows_nt_status_message(code),
2090 },
2091 AbortStatus::JobObject => Self::WindowsJobObject,
2092 }
2093 } else {
2094 match status {}
2095 }
2096 }
2097 }
2098}
2099
2100#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2104#[serde(tag = "kind", rename_all = "kebab-case")]
2105#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2106#[non_exhaustive]
2107pub enum FailureDescription {
2108 ExitCode {
2110 code: i32,
2112 },
2113
2114 Abort {
2121 abort: AbortDescription,
2123 },
2124}
2125
2126impl From<FailureStatus> for FailureDescription {
2127 fn from(status: FailureStatus) -> Self {
2128 match status {
2129 FailureStatus::ExitCode(code) => Self::ExitCode { code },
2130 FailureStatus::Abort(abort) => Self::Abort {
2131 abort: AbortDescription::from(abort),
2132 },
2133 }
2134 }
2135}
2136
2137impl fmt::Display for FailureDescription {
2138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2139 match self {
2140 Self::ExitCode { code } => write!(f, "exited with code {code}"),
2141 Self::Abort { abort } => write!(f, "{abort}"),
2142 }
2143 }
2144}
2145
2146#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2151#[serde(tag = "status", rename_all = "kebab-case")]
2152#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2153#[non_exhaustive]
2154pub enum ExecutionResultDescription {
2155 Pass,
2157
2158 Leak {
2160 result: LeakTimeoutResult,
2162 },
2163
2164 Fail {
2166 failure: FailureDescription,
2168
2169 leaked: bool,
2171 },
2172
2173 ExecFail,
2175
2176 Timeout {
2178 result: SlowTimeoutResult,
2180 },
2181}
2182
2183impl ExecutionResultDescription {
2184 pub fn is_success(&self) -> bool {
2186 match self {
2187 Self::Pass
2188 | Self::Timeout {
2189 result: SlowTimeoutResult::Pass,
2190 }
2191 | Self::Leak {
2192 result: LeakTimeoutResult::Pass,
2193 } => true,
2194 Self::Leak {
2195 result: LeakTimeoutResult::Fail,
2196 }
2197 | Self::Fail { .. }
2198 | Self::ExecFail
2199 | Self::Timeout {
2200 result: SlowTimeoutResult::Fail,
2201 } => false,
2202 }
2203 }
2204
2205 pub fn as_static_str(&self) -> &'static str {
2207 match self {
2208 Self::Pass => "pass",
2209 Self::Leak { .. } => "leak",
2210 Self::Fail { .. } => "fail",
2211 Self::ExecFail => "exec-fail",
2212 Self::Timeout { .. } => "timeout",
2213 }
2214 }
2215
2216 pub fn is_termination_failure(&self) -> bool {
2228 matches!(
2229 self,
2230 Self::Fail {
2231 failure: FailureDescription::Abort {
2232 abort: AbortDescription::UnixSignal {
2233 signal: SIGTERM,
2234 ..
2235 },
2236 },
2237 ..
2238 } | Self::Fail {
2239 failure: FailureDescription::Abort {
2240 abort: AbortDescription::WindowsJobObject,
2241 },
2242 ..
2243 }
2244 )
2245 }
2246}
2247
2248impl From<ExecutionResult> for ExecutionResultDescription {
2249 fn from(result: ExecutionResult) -> Self {
2250 match result {
2251 ExecutionResult::Pass => Self::Pass,
2252 ExecutionResult::Leak { result } => Self::Leak { result },
2253 ExecutionResult::Fail {
2254 failure_status,
2255 leaked,
2256 } => Self::Fail {
2257 failure: FailureDescription::from(failure_status),
2258 leaked,
2259 },
2260 ExecutionResult::ExecFail => Self::ExecFail,
2261 ExecutionResult::Timeout { result } => Self::Timeout { result },
2262 }
2263 }
2264}
2265
2266#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
2269#[serde(rename_all = "kebab-case")]
2270#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2271pub enum CancelReason {
2272 SetupScriptFailure,
2274
2275 TestFailure,
2277
2278 ReportError,
2280
2281 GlobalTimeout,
2283
2284 TestFailureImmediate,
2286
2287 Signal,
2289
2290 Interrupt,
2292
2293 SecondSignal,
2295}
2296
2297impl CancelReason {
2298 pub(crate) fn to_static_str(self) -> &'static str {
2299 match self {
2300 CancelReason::SetupScriptFailure => "setup script failure",
2301 CancelReason::TestFailure => "test failure",
2302 CancelReason::ReportError => "reporting error",
2303 CancelReason::GlobalTimeout => "global timeout",
2304 CancelReason::TestFailureImmediate => "test failure",
2305 CancelReason::Signal => "signal",
2306 CancelReason::Interrupt => "interrupt",
2307 CancelReason::SecondSignal => "second signal",
2308 }
2309 }
2310}
2311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2313pub enum UnitKind {
2314 Test,
2316
2317 Script,
2319}
2320
2321impl UnitKind {
2322 pub(crate) const WAITING_ON_TEST_MESSAGE: &str = "waiting on test process";
2323 pub(crate) const WAITING_ON_SCRIPT_MESSAGE: &str = "waiting on script process";
2324
2325 pub(crate) const EXECUTING_TEST_MESSAGE: &str = "executing test";
2326 pub(crate) const EXECUTING_SCRIPT_MESSAGE: &str = "executing script";
2327
2328 pub(crate) fn waiting_on_message(&self) -> &'static str {
2329 match self {
2330 UnitKind::Test => Self::WAITING_ON_TEST_MESSAGE,
2331 UnitKind::Script => Self::WAITING_ON_SCRIPT_MESSAGE,
2332 }
2333 }
2334
2335 pub(crate) fn executing_message(&self) -> &'static str {
2336 match self {
2337 UnitKind::Test => Self::EXECUTING_TEST_MESSAGE,
2338 UnitKind::Script => Self::EXECUTING_SCRIPT_MESSAGE,
2339 }
2340 }
2341}
2342
2343#[derive(Clone, Debug)]
2345pub enum InfoResponse<'a> {
2346 SetupScript(SetupScriptInfoResponse),
2348
2349 Test(TestInfoResponse<'a>),
2351}
2352
2353#[derive(Clone, Debug)]
2355pub struct SetupScriptInfoResponse {
2356 pub stress_index: Option<StressIndex>,
2358
2359 pub script_id: ScriptId,
2361
2362 pub program: String,
2364
2365 pub args: Vec<String>,
2367
2368 pub state: UnitState,
2370
2371 pub output: ChildExecutionOutputDescription<LiveSpec>,
2373}
2374
2375#[derive(Clone, Debug)]
2377pub struct TestInfoResponse<'a> {
2378 pub stress_index: Option<StressIndex>,
2380
2381 pub test_instance: TestInstanceId<'a>,
2383
2384 pub retry_data: RetryData,
2386
2387 pub state: UnitState,
2389
2390 pub output: ChildExecutionOutputDescription<LiveSpec>,
2392}
2393
2394#[derive(Clone, Debug)]
2399pub enum UnitState {
2400 Running {
2402 pid: u32,
2404
2405 time_taken: Duration,
2407
2408 slow_after: Option<Duration>,
2411 },
2412
2413 Exiting {
2416 pid: u32,
2418
2419 time_taken: Duration,
2421
2422 slow_after: Option<Duration>,
2425
2426 tentative_result: Option<ExecutionResultDescription>,
2431
2432 waiting_duration: Duration,
2434
2435 remaining: Duration,
2437 },
2438
2439 Terminating(UnitTerminatingState),
2441
2442 Exited {
2444 result: ExecutionResultDescription,
2446
2447 time_taken: Duration,
2449
2450 slow_after: Option<Duration>,
2453 },
2454
2455 DelayBeforeNextAttempt {
2458 previous_result: ExecutionResultDescription,
2460
2461 previous_slow: bool,
2463
2464 waiting_duration: Duration,
2466
2467 remaining: Duration,
2469 },
2470}
2471
2472impl UnitState {
2473 pub fn has_valid_output(&self) -> bool {
2475 match self {
2476 UnitState::Running { .. }
2477 | UnitState::Exiting { .. }
2478 | UnitState::Terminating(_)
2479 | UnitState::Exited { .. } => true,
2480 UnitState::DelayBeforeNextAttempt { .. } => false,
2481 }
2482 }
2483}
2484
2485#[derive(Clone, Debug)]
2489pub struct UnitTerminatingState {
2490 pub pid: u32,
2492
2493 pub time_taken: Duration,
2495
2496 pub reason: UnitTerminateReason,
2498
2499 pub method: UnitTerminateMethod,
2501
2502 pub waiting_duration: Duration,
2504
2505 pub remaining: Duration,
2507}
2508
2509#[derive(Clone, Copy, Debug)]
2513pub enum UnitTerminateReason {
2514 Timeout,
2516
2517 Signal,
2519
2520 Interrupt,
2522}
2523
2524impl fmt::Display for UnitTerminateReason {
2525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2526 match self {
2527 UnitTerminateReason::Timeout => write!(f, "timeout"),
2528 UnitTerminateReason::Signal => write!(f, "signal"),
2529 UnitTerminateReason::Interrupt => write!(f, "interrupt"),
2530 }
2531 }
2532}
2533
2534#[derive(Clone, Copy, Debug)]
2536pub enum UnitTerminateMethod {
2537 #[cfg(unix)]
2539 Signal(UnitTerminateSignal),
2540
2541 #[cfg(windows)]
2543 JobObject,
2544
2545 #[cfg(windows)]
2553 Wait,
2554
2555 #[cfg(test)]
2557 Fake,
2558}
2559
2560#[cfg(unix)]
2561#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2563pub enum UnitTerminateSignal {
2564 Interrupt,
2566
2567 Term,
2569
2570 Hangup,
2572
2573 Quit,
2575
2576 Kill,
2578}
2579
2580#[cfg(unix)]
2581impl fmt::Display for UnitTerminateSignal {
2582 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2583 match self {
2584 UnitTerminateSignal::Interrupt => write!(f, "SIGINT"),
2585 UnitTerminateSignal::Term => write!(f, "SIGTERM"),
2586 UnitTerminateSignal::Hangup => write!(f, "SIGHUP"),
2587 UnitTerminateSignal::Quit => write!(f, "SIGQUIT"),
2588 UnitTerminateSignal::Kill => write!(f, "SIGKILL"),
2589 }
2590 }
2591}
2592
2593#[cfg(test)]
2594mod tests {
2595 use super::*;
2596
2597 #[test]
2598 fn test_is_success() {
2599 assert_eq!(
2600 RunStats::default().summarize_final(),
2601 FinalRunStats::NoTestsRun,
2602 "empty run => no tests run"
2603 );
2604 assert_eq!(
2605 RunStats {
2606 initial_run_count: 42,
2607 finished_count: 42,
2608 ..RunStats::default()
2609 }
2610 .summarize_final(),
2611 FinalRunStats::Success,
2612 "initial run count = final run count => success"
2613 );
2614 assert_eq!(
2615 RunStats {
2616 initial_run_count: 42,
2617 finished_count: 41,
2618 ..RunStats::default()
2619 }
2620 .summarize_final(),
2621 FinalRunStats::Cancelled {
2622 reason: None,
2623 kind: RunStatsFailureKind::Test {
2624 initial_run_count: 42,
2625 not_run: 1
2626 }
2627 },
2628 "initial run count > final run count => cancelled"
2629 );
2630 assert_eq!(
2631 RunStats {
2632 initial_run_count: 42,
2633 finished_count: 42,
2634 failed: 1,
2635 ..RunStats::default()
2636 }
2637 .summarize_final(),
2638 FinalRunStats::Failed {
2639 kind: RunStatsFailureKind::Test {
2640 initial_run_count: 42,
2641 not_run: 0,
2642 },
2643 },
2644 "failed => failure"
2645 );
2646 assert_eq!(
2647 RunStats {
2648 initial_run_count: 42,
2649 finished_count: 42,
2650 exec_failed: 1,
2651 ..RunStats::default()
2652 }
2653 .summarize_final(),
2654 FinalRunStats::Failed {
2655 kind: RunStatsFailureKind::Test {
2656 initial_run_count: 42,
2657 not_run: 0,
2658 },
2659 },
2660 "exec failed => failure"
2661 );
2662 assert_eq!(
2663 RunStats {
2664 initial_run_count: 42,
2665 finished_count: 42,
2666 failed_timed_out: 1,
2667 ..RunStats::default()
2668 }
2669 .summarize_final(),
2670 FinalRunStats::Failed {
2671 kind: RunStatsFailureKind::Test {
2672 initial_run_count: 42,
2673 not_run: 0,
2674 },
2675 },
2676 "timed out => failure {:?} {:?}",
2677 RunStats {
2678 initial_run_count: 42,
2679 finished_count: 42,
2680 failed_timed_out: 1,
2681 ..RunStats::default()
2682 }
2683 .summarize_final(),
2684 FinalRunStats::Failed {
2685 kind: RunStatsFailureKind::Test {
2686 initial_run_count: 42,
2687 not_run: 0,
2688 },
2689 },
2690 );
2691 assert_eq!(
2692 RunStats {
2693 initial_run_count: 42,
2694 finished_count: 42,
2695 skipped: 1,
2696 ..RunStats::default()
2697 }
2698 .summarize_final(),
2699 FinalRunStats::Success,
2700 "skipped => not considered a failure"
2701 );
2702
2703 assert_eq!(
2704 RunStats {
2705 setup_scripts_initial_count: 2,
2706 setup_scripts_finished_count: 1,
2707 ..RunStats::default()
2708 }
2709 .summarize_final(),
2710 FinalRunStats::Cancelled {
2711 reason: None,
2712 kind: RunStatsFailureKind::SetupScript,
2713 },
2714 "setup script failed => failure"
2715 );
2716
2717 assert_eq!(
2718 RunStats {
2719 setup_scripts_initial_count: 2,
2720 setup_scripts_finished_count: 2,
2721 setup_scripts_failed: 1,
2722 ..RunStats::default()
2723 }
2724 .summarize_final(),
2725 FinalRunStats::Failed {
2726 kind: RunStatsFailureKind::SetupScript,
2727 },
2728 "setup script failed => failure"
2729 );
2730 assert_eq!(
2731 RunStats {
2732 setup_scripts_initial_count: 2,
2733 setup_scripts_finished_count: 2,
2734 setup_scripts_exec_failed: 1,
2735 ..RunStats::default()
2736 }
2737 .summarize_final(),
2738 FinalRunStats::Failed {
2739 kind: RunStatsFailureKind::SetupScript,
2740 },
2741 "setup script exec failed => failure"
2742 );
2743 assert_eq!(
2744 RunStats {
2745 setup_scripts_initial_count: 2,
2746 setup_scripts_finished_count: 2,
2747 setup_scripts_timed_out: 1,
2748 ..RunStats::default()
2749 }
2750 .summarize_final(),
2751 FinalRunStats::Failed {
2752 kind: RunStatsFailureKind::SetupScript,
2753 },
2754 "setup script timed out => failure"
2755 );
2756 assert_eq!(
2757 RunStats {
2758 setup_scripts_initial_count: 2,
2759 setup_scripts_finished_count: 2,
2760 setup_scripts_passed: 2,
2761 ..RunStats::default()
2762 }
2763 .summarize_final(),
2764 FinalRunStats::NoTestsRun,
2765 "setup scripts passed => success, but no tests run"
2766 );
2767
2768 }
2771
2772 fn make_execute_status(
2774 result: ExecutionResultDescription,
2775 attempt: u32,
2776 total_attempts: u32,
2777 ) -> ExecuteStatus<LiveSpec> {
2778 make_execute_status_slow(result, attempt, total_attempts, false)
2779 }
2780
2781 fn make_execute_status_slow(
2784 result: ExecutionResultDescription,
2785 attempt: u32,
2786 total_attempts: u32,
2787 is_slow: bool,
2788 ) -> ExecuteStatus<LiveSpec> {
2789 ExecuteStatus {
2790 retry_data: RetryData {
2791 attempt,
2792 total_attempts,
2793 },
2794 output: ChildExecutionOutputDescription::Output {
2795 result: Some(result.clone()),
2796 output: ChildOutputDescription::Split {
2797 stdout: None,
2798 stderr: None,
2799 },
2800 errors: None,
2801 },
2802 result,
2803 start_time: chrono::Utc::now().into(),
2804 time_taken: Duration::from_millis(100),
2805 is_slow,
2806 delay_before_start: Duration::ZERO,
2807 error_summary: None,
2808 output_error_slice: None,
2809 }
2810 }
2811
2812 #[test]
2813 fn is_success_for_output_by_variant() {
2814 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 1, 1);
2816 let success_statuses = ExecutionStatuses::new(vec![pass_status], FlakyResult::Pass);
2817 let describe = success_statuses.describe();
2818 assert!(
2819 matches!(describe, ExecutionDescription::Success { .. }),
2820 "single pass is Success"
2821 );
2822 assert!(
2823 describe.is_success_for_output(),
2824 "Success: output is success output"
2825 );
2826
2827 let fail_status = make_execute_status(
2829 ExecutionResultDescription::Fail {
2830 failure: FailureDescription::ExitCode { code: 1 },
2831 leaked: false,
2832 },
2833 1,
2834 2,
2835 );
2836 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 2, 2);
2837 let flaky_pass_statuses =
2838 ExecutionStatuses::new(vec![fail_status, pass_status], FlakyResult::Pass);
2839 let describe = flaky_pass_statuses.describe();
2840 assert!(
2841 matches!(
2842 describe,
2843 ExecutionDescription::Flaky {
2844 result: FlakyResult::Pass,
2845 ..
2846 }
2847 ),
2848 "fail then pass with FlakyResult::Pass is Flaky Pass"
2849 );
2850 assert!(
2851 describe.is_success_for_output(),
2852 "Flaky pass: output is success output"
2853 );
2854
2855 let fail_status = make_execute_status(
2857 ExecutionResultDescription::Fail {
2858 failure: FailureDescription::ExitCode { code: 1 },
2859 leaked: false,
2860 },
2861 1,
2862 2,
2863 );
2864 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 2, 2);
2865 let flaky_fail_statuses =
2866 ExecutionStatuses::new(vec![fail_status, pass_status], FlakyResult::Fail);
2867 let describe = flaky_fail_statuses.describe();
2868 assert!(
2869 matches!(
2870 describe,
2871 ExecutionDescription::Flaky {
2872 result: FlakyResult::Fail,
2873 ..
2874 }
2875 ),
2876 "fail then pass with FlakyResult::Fail is Flaky Fail"
2877 );
2878 assert!(
2879 describe.is_success_for_output(),
2880 "Flaky fail: output is still success output (last attempt passed)"
2881 );
2882
2883 let fail_status = make_execute_status(
2885 ExecutionResultDescription::Fail {
2886 failure: FailureDescription::ExitCode { code: 1 },
2887 leaked: false,
2888 },
2889 1,
2890 1,
2891 );
2892 let failure_statuses = ExecutionStatuses::new(vec![fail_status], FlakyResult::Pass);
2893 let describe = failure_statuses.describe();
2894 assert!(
2895 matches!(describe, ExecutionDescription::Failure { .. }),
2896 "single fail is Failure"
2897 );
2898 assert!(
2899 !describe.is_success_for_output(),
2900 "Failure: output is not success output"
2901 );
2902
2903 let fail1 = make_execute_status(
2905 ExecutionResultDescription::Fail {
2906 failure: FailureDescription::ExitCode { code: 1 },
2907 leaked: false,
2908 },
2909 1,
2910 2,
2911 );
2912 let fail2 = make_execute_status(
2913 ExecutionResultDescription::Fail {
2914 failure: FailureDescription::ExitCode { code: 1 },
2915 leaked: false,
2916 },
2917 2,
2918 2,
2919 );
2920 let failure_retry_statuses = ExecutionStatuses::new(vec![fail1, fail2], FlakyResult::Pass);
2921 let describe = failure_retry_statuses.describe();
2922 assert!(
2923 matches!(describe, ExecutionDescription::Failure { .. }),
2924 "all-fail with retries is Failure"
2925 );
2926 assert!(
2927 !describe.is_success_for_output(),
2928 "Failure with retries: output is not success output"
2929 );
2930 }
2931
2932 #[test]
2933 fn abort_description_serialization() {
2934 let unix_with_name = AbortDescription::UnixSignal {
2936 signal: 15,
2937 name: Some("TERM".into()),
2938 };
2939 let json = serde_json::to_string_pretty(&unix_with_name).unwrap();
2940 insta::assert_snapshot!("abort_unix_signal_with_name", json);
2941 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2942 assert_eq!(unix_with_name, roundtrip);
2943
2944 let unix_no_name = AbortDescription::UnixSignal {
2946 signal: 42,
2947 name: None,
2948 };
2949 let json = serde_json::to_string_pretty(&unix_no_name).unwrap();
2950 insta::assert_snapshot!("abort_unix_signal_no_name", json);
2951 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2952 assert_eq!(unix_no_name, roundtrip);
2953
2954 let windows_nt = AbortDescription::WindowsNtStatus {
2956 code: -1073741510_i32,
2957 message: Some("The application terminated as a result of a CTRL+C.".into()),
2958 };
2959 let json = serde_json::to_string_pretty(&windows_nt).unwrap();
2960 insta::assert_snapshot!("abort_windows_nt_status", json);
2961 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2962 assert_eq!(windows_nt, roundtrip);
2963
2964 let windows_nt_no_msg = AbortDescription::WindowsNtStatus {
2966 code: -1073741819_i32,
2967 message: None,
2968 };
2969 let json = serde_json::to_string_pretty(&windows_nt_no_msg).unwrap();
2970 insta::assert_snapshot!("abort_windows_nt_status_no_message", json);
2971 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2972 assert_eq!(windows_nt_no_msg, roundtrip);
2973
2974 let job = AbortDescription::WindowsJobObject;
2976 let json = serde_json::to_string_pretty(&job).unwrap();
2977 insta::assert_snapshot!("abort_windows_job_object", json);
2978 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2979 assert_eq!(job, roundtrip);
2980 }
2981
2982 #[test]
2983 fn abort_description_cross_platform_deserialization() {
2984 let unix_json = r#"{"kind":"unix-signal","signal":11,"name":"SEGV"}"#;
2987 let unix_desc: AbortDescription = serde_json::from_str(unix_json).unwrap();
2988 assert_eq!(
2989 unix_desc,
2990 AbortDescription::UnixSignal {
2991 signal: 11,
2992 name: Some("SEGV".into()),
2993 }
2994 );
2995
2996 let windows_json = r#"{"kind":"windows-nt-status","code":-1073741510,"message":"CTRL+C"}"#;
2997 let windows_desc: AbortDescription = serde_json::from_str(windows_json).unwrap();
2998 assert_eq!(
2999 windows_desc,
3000 AbortDescription::WindowsNtStatus {
3001 code: -1073741510,
3002 message: Some("CTRL+C".into()),
3003 }
3004 );
3005
3006 let job_json = r#"{"kind":"windows-job-object"}"#;
3007 let job_desc: AbortDescription = serde_json::from_str(job_json).unwrap();
3008 assert_eq!(job_desc, AbortDescription::WindowsJobObject);
3009 }
3010
3011 #[test]
3012 fn abort_description_display() {
3013 let unix = AbortDescription::UnixSignal {
3015 signal: 15,
3016 name: Some("TERM".into()),
3017 };
3018 assert_eq!(unix.to_string(), "aborted with signal 15 (SIGTERM)");
3019
3020 let unix_no_name = AbortDescription::UnixSignal {
3022 signal: 42,
3023 name: None,
3024 };
3025 assert_eq!(unix_no_name.to_string(), "aborted with signal 42");
3026
3027 let windows = AbortDescription::WindowsNtStatus {
3029 code: -1073741510,
3030 message: Some("CTRL+C exit".into()),
3031 };
3032 assert_eq!(
3033 windows.to_string(),
3034 "aborted with code 0xc000013a: CTRL+C exit"
3035 );
3036
3037 let windows_no_msg = AbortDescription::WindowsNtStatus {
3039 code: -1073741510,
3040 message: None,
3041 };
3042 assert_eq!(windows_no_msg.to_string(), "aborted with code 0xc000013a");
3043
3044 let job = AbortDescription::WindowsJobObject;
3046 assert_eq!(job.to_string(), "terminated via job object");
3047 }
3048
3049 #[cfg(unix)]
3050 #[test]
3051 fn abort_description_from_abort_status() {
3052 let status = AbortStatus::UnixSignal(15);
3054 let description = AbortDescription::from(status);
3055
3056 assert_eq!(
3057 description,
3058 AbortDescription::UnixSignal {
3059 signal: 15,
3060 name: Some("TERM".into()),
3061 }
3062 );
3063
3064 let unknown_status = AbortStatus::UnixSignal(42);
3066 let unknown_description = AbortDescription::from(unknown_status);
3067 assert_eq!(
3068 unknown_description,
3069 AbortDescription::UnixSignal {
3070 signal: 42,
3071 name: None,
3072 }
3073 );
3074 }
3075
3076 #[test]
3077 fn execution_result_description_serialization() {
3078 let pass = ExecutionResultDescription::Pass;
3082 let json = serde_json::to_string_pretty(&pass).unwrap();
3083 insta::assert_snapshot!("pass", json);
3084 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3085 assert_eq!(pass, roundtrip);
3086
3087 let leak_pass = ExecutionResultDescription::Leak {
3089 result: LeakTimeoutResult::Pass,
3090 };
3091 let json = serde_json::to_string_pretty(&leak_pass).unwrap();
3092 insta::assert_snapshot!("leak_pass", json);
3093 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3094 assert_eq!(leak_pass, roundtrip);
3095
3096 let leak_fail = ExecutionResultDescription::Leak {
3098 result: LeakTimeoutResult::Fail,
3099 };
3100 let json = serde_json::to_string_pretty(&leak_fail).unwrap();
3101 insta::assert_snapshot!("leak_fail", json);
3102 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3103 assert_eq!(leak_fail, roundtrip);
3104
3105 let fail_exit_code = ExecutionResultDescription::Fail {
3107 failure: FailureDescription::ExitCode { code: 101 },
3108 leaked: false,
3109 };
3110 let json = serde_json::to_string_pretty(&fail_exit_code).unwrap();
3111 insta::assert_snapshot!("fail_exit_code", json);
3112 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3113 assert_eq!(fail_exit_code, roundtrip);
3114
3115 let fail_exit_code_leaked = ExecutionResultDescription::Fail {
3117 failure: FailureDescription::ExitCode { code: 1 },
3118 leaked: true,
3119 };
3120 let json = serde_json::to_string_pretty(&fail_exit_code_leaked).unwrap();
3121 insta::assert_snapshot!("fail_exit_code_leaked", json);
3122 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3123 assert_eq!(fail_exit_code_leaked, roundtrip);
3124
3125 let fail_unix_signal = ExecutionResultDescription::Fail {
3127 failure: FailureDescription::Abort {
3128 abort: AbortDescription::UnixSignal {
3129 signal: 11,
3130 name: Some("SEGV".into()),
3131 },
3132 },
3133 leaked: false,
3134 };
3135 let json = serde_json::to_string_pretty(&fail_unix_signal).unwrap();
3136 insta::assert_snapshot!("fail_unix_signal", json);
3137 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3138 assert_eq!(fail_unix_signal, roundtrip);
3139
3140 let fail_unix_signal_unknown = ExecutionResultDescription::Fail {
3142 failure: FailureDescription::Abort {
3143 abort: AbortDescription::UnixSignal {
3144 signal: 42,
3145 name: None,
3146 },
3147 },
3148 leaked: true,
3149 };
3150 let json = serde_json::to_string_pretty(&fail_unix_signal_unknown).unwrap();
3151 insta::assert_snapshot!("fail_unix_signal_unknown_leaked", json);
3152 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3153 assert_eq!(fail_unix_signal_unknown, roundtrip);
3154
3155 let fail_windows_nt = ExecutionResultDescription::Fail {
3157 failure: FailureDescription::Abort {
3158 abort: AbortDescription::WindowsNtStatus {
3159 code: -1073741510,
3160 message: Some("The application terminated as a result of a CTRL+C.".into()),
3161 },
3162 },
3163 leaked: false,
3164 };
3165 let json = serde_json::to_string_pretty(&fail_windows_nt).unwrap();
3166 insta::assert_snapshot!("fail_windows_nt_status", json);
3167 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3168 assert_eq!(fail_windows_nt, roundtrip);
3169
3170 let fail_windows_nt_no_msg = ExecutionResultDescription::Fail {
3172 failure: FailureDescription::Abort {
3173 abort: AbortDescription::WindowsNtStatus {
3174 code: -1073741819,
3175 message: None,
3176 },
3177 },
3178 leaked: false,
3179 };
3180 let json = serde_json::to_string_pretty(&fail_windows_nt_no_msg).unwrap();
3181 insta::assert_snapshot!("fail_windows_nt_status_no_message", json);
3182 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3183 assert_eq!(fail_windows_nt_no_msg, roundtrip);
3184
3185 let fail_job_object = ExecutionResultDescription::Fail {
3187 failure: FailureDescription::Abort {
3188 abort: AbortDescription::WindowsJobObject,
3189 },
3190 leaked: false,
3191 };
3192 let json = serde_json::to_string_pretty(&fail_job_object).unwrap();
3193 insta::assert_snapshot!("fail_windows_job_object", json);
3194 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3195 assert_eq!(fail_job_object, roundtrip);
3196
3197 let exec_fail = ExecutionResultDescription::ExecFail;
3199 let json = serde_json::to_string_pretty(&exec_fail).unwrap();
3200 insta::assert_snapshot!("exec_fail", json);
3201 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3202 assert_eq!(exec_fail, roundtrip);
3203
3204 let timeout_pass = ExecutionResultDescription::Timeout {
3206 result: SlowTimeoutResult::Pass,
3207 };
3208 let json = serde_json::to_string_pretty(&timeout_pass).unwrap();
3209 insta::assert_snapshot!("timeout_pass", json);
3210 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3211 assert_eq!(timeout_pass, roundtrip);
3212
3213 let timeout_fail = ExecutionResultDescription::Timeout {
3215 result: SlowTimeoutResult::Fail,
3216 };
3217 let json = serde_json::to_string_pretty(&timeout_fail).unwrap();
3218 insta::assert_snapshot!("timeout_fail", json);
3219 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3220 assert_eq!(timeout_fail, roundtrip);
3221 }
3222
3223 fn make_flaky_statuses(
3228 pass_result: ExecutionResultDescription,
3229 flaky_result: FlakyResult,
3230 is_slow: bool,
3231 ) -> ExecutionStatuses<LiveSpec> {
3232 let fail = make_execute_status(
3233 ExecutionResultDescription::Fail {
3234 failure: FailureDescription::ExitCode { code: 1 },
3235 leaked: false,
3236 },
3237 1,
3238 2,
3239 );
3240 let pass = make_execute_status_slow(pass_result, 2, 2, is_slow);
3241 ExecutionStatuses::new(vec![fail, pass], flaky_result)
3242 }
3243
3244 fn run_on_test_finished(statuses: &ExecutionStatuses<LiveSpec>) -> RunStats {
3246 let mut stats = RunStats {
3247 initial_run_count: 1,
3248 ..RunStats::default()
3249 };
3250 stats.on_test_finished(statuses);
3251 stats
3252 }
3253
3254 #[test]
3255 fn on_test_finished_pass_flaky() {
3256 let stats = run_on_test_finished(&make_flaky_statuses(
3258 ExecutionResultDescription::Pass,
3259 FlakyResult::Fail,
3260 false,
3261 ));
3262 assert_eq!(stats.finished_count, 1);
3263 assert_eq!(stats.failed, 1);
3264 assert_eq!(stats.failed_slow, 0, "not slow");
3265 assert_eq!(stats.passed, 0);
3266 assert_eq!(stats.flaky, 0);
3267
3268 let stats = run_on_test_finished(&make_flaky_statuses(
3270 ExecutionResultDescription::Pass,
3271 FlakyResult::Fail,
3272 true,
3273 ));
3274 assert_eq!(stats.failed, 1);
3275 assert_eq!(stats.failed_slow, 1);
3276 assert_eq!(stats.passed, 0);
3277 assert_eq!(stats.flaky, 0);
3278
3279 let stats = run_on_test_finished(&make_flaky_statuses(
3281 ExecutionResultDescription::Pass,
3282 FlakyResult::Pass,
3283 true,
3284 ));
3285 assert_eq!(stats.passed, 1);
3286 assert_eq!(stats.passed_slow, 1);
3287 assert_eq!(stats.flaky, 1);
3288 assert_eq!(stats.failed, 0);
3289 }
3290
3291 #[test]
3292 fn on_test_finished_leak_pass_flaky() {
3293 let stats = run_on_test_finished(&make_flaky_statuses(
3295 ExecutionResultDescription::Leak {
3296 result: LeakTimeoutResult::Pass,
3297 },
3298 FlakyResult::Fail,
3299 false,
3300 ));
3301 assert_eq!(stats.failed, 1);
3302 assert_eq!(stats.failed_slow, 0, "not slow");
3303 assert_eq!(stats.leaky, 1, "leak still tracked");
3304 assert_eq!(stats.passed, 0);
3305 assert_eq!(stats.flaky, 0);
3306
3307 let stats = run_on_test_finished(&make_flaky_statuses(
3309 ExecutionResultDescription::Leak {
3310 result: LeakTimeoutResult::Pass,
3311 },
3312 FlakyResult::Fail,
3313 true,
3314 ));
3315 assert_eq!(stats.failed, 1);
3316 assert_eq!(stats.failed_slow, 1);
3317 assert_eq!(stats.leaky, 1);
3318 assert_eq!(stats.passed, 0);
3319 assert_eq!(stats.flaky, 0);
3320
3321 let stats = run_on_test_finished(&make_flaky_statuses(
3323 ExecutionResultDescription::Leak {
3324 result: LeakTimeoutResult::Pass,
3325 },
3326 FlakyResult::Pass,
3327 true,
3328 ));
3329 assert_eq!(stats.passed, 1);
3330 assert_eq!(stats.passed_slow, 1);
3331 assert_eq!(stats.leaky, 1);
3332 assert_eq!(stats.flaky, 1);
3333 assert_eq!(stats.failed, 0);
3334 }
3335
3336 #[test]
3337 fn on_test_finished_timeout_pass_flaky() {
3338 let stats = run_on_test_finished(&make_flaky_statuses(
3341 ExecutionResultDescription::Timeout {
3342 result: SlowTimeoutResult::Pass,
3343 },
3344 FlakyResult::Fail,
3345 true,
3346 ));
3347 assert_eq!(stats.failed, 1);
3348 assert_eq!(stats.failed_slow, 1);
3349 assert_eq!(stats.passed, 0);
3350 assert_eq!(stats.passed_timed_out, 0);
3351 assert_eq!(stats.flaky, 0);
3352
3353 let stats = run_on_test_finished(&make_flaky_statuses(
3355 ExecutionResultDescription::Timeout {
3356 result: SlowTimeoutResult::Pass,
3357 },
3358 FlakyResult::Pass,
3359 false,
3360 ));
3361 assert_eq!(stats.passed, 1);
3362 assert_eq!(stats.passed_timed_out, 1);
3363 assert_eq!(stats.flaky, 1);
3364 assert_eq!(stats.failed, 0);
3365 }
3366
3367 #[test]
3368 fn on_test_finished_non_flaky() {
3369 let pass = make_execute_status_slow(ExecutionResultDescription::Pass, 1, 1, true);
3371 let stats = run_on_test_finished(&ExecutionStatuses::new(vec![pass], FlakyResult::Pass));
3372 assert_eq!(stats.passed, 1);
3373 assert_eq!(stats.passed_slow, 1);
3374 assert_eq!(stats.flaky, 0);
3375 assert_eq!(stats.failed, 0);
3376
3377 let fail = make_execute_status_slow(
3379 ExecutionResultDescription::Fail {
3380 failure: FailureDescription::ExitCode { code: 1 },
3381 leaked: false,
3382 },
3383 1,
3384 1,
3385 true,
3386 );
3387 let stats = run_on_test_finished(&ExecutionStatuses::new(vec![fail], FlakyResult::Pass));
3388 assert_eq!(stats.failed, 1);
3389 assert_eq!(stats.failed_slow, 1);
3390 assert_eq!(stats.passed, 0);
3391 assert_eq!(stats.flaky, 0);
3392 }
3393}