1use super::{FinalStatusLevel, StatusLevel, TestOutputDisplay};
10#[cfg(test)]
11use crate::output_spec::ArbitraryOutputSpec;
12use crate::{
13 config::{
14 elements::{
15 FlakyResult, JunitFlakyFailStatus, LeakTimeoutResult, SlowTimeoutResult, TestGroup,
16 },
17 scripts::ScriptId,
18 },
19 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
20 list::{OwnedTestInstanceId, TestInstanceId, TestList},
21 output_spec::{LiveSpec, OutputSpec, SerializableOutputSpec},
22 runner::{StressCondition, StressCount},
23 test_output::{ChildExecutionOutput, ChildOutput, ChildSingleOutput},
24};
25use chrono::{DateTime, FixedOffset};
26use nextest_metadata::MismatchReason;
27use quick_junit::ReportUuid;
28use serde::{Deserialize, Serialize};
29use smol_str::SmolStr;
30use std::{
31 collections::BTreeMap, ffi::c_int, fmt, num::NonZero, process::ExitStatus, time::Duration,
32};
33
34pub const SIGTERM: c_int = 15;
39
40#[derive(Clone, Debug)]
42pub enum ReporterEvent<'a> {
43 Tick,
45
46 Test(Box<TestEvent<'a>>),
48}
49#[derive(Clone, Debug)]
54pub struct TestEvent<'a> {
55 pub timestamp: DateTime<FixedOffset>,
57
58 pub elapsed: Duration,
60
61 pub kind: TestEventKind<'a>,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "kebab-case")]
71pub struct TestSlotAssignment {
72 pub global_slot: u64,
75
76 pub group_slot: Option<u64>,
79
80 pub test_group: TestGroup,
82}
83
84#[derive(Clone, Debug)]
88pub enum TestEventKind<'a> {
89 RunStarted {
91 test_list: &'a TestList<'a>,
95
96 run_id: ReportUuid,
98
99 profile_name: String,
101
102 cli_args: Vec<String>,
104
105 stress_condition: Option<StressCondition>,
107 },
108
109 StressSubRunStarted {
111 progress: StressProgress,
113 },
114
115 SetupScriptStarted {
117 stress_index: Option<StressIndex>,
119
120 index: usize,
122
123 total: usize,
125
126 script_id: ScriptId,
128
129 program: String,
131
132 args: Vec<String>,
134
135 no_capture: bool,
137 },
138
139 SetupScriptSlow {
141 stress_index: Option<StressIndex>,
143
144 script_id: ScriptId,
146
147 program: String,
149
150 args: Vec<String>,
152
153 elapsed: Duration,
155
156 will_terminate: bool,
158 },
159
160 SetupScriptFinished {
162 stress_index: Option<StressIndex>,
164
165 index: usize,
167
168 total: usize,
170
171 script_id: ScriptId,
173
174 program: String,
176
177 args: Vec<String>,
179
180 junit_store_success_output: bool,
182
183 junit_store_failure_output: bool,
185
186 no_capture: bool,
188
189 run_status: SetupScriptExecuteStatus<LiveSpec>,
191 },
192
193 TestStarted {
198 stress_index: Option<StressIndex>,
200
201 test_instance: TestInstanceId<'a>,
203
204 slot_assignment: TestSlotAssignment,
206
207 current_stats: RunStats,
209
210 running: usize,
212
213 command_line: Vec<String>,
215 },
216
217 TestSlow {
219 stress_index: Option<StressIndex>,
221
222 test_instance: TestInstanceId<'a>,
224
225 retry_data: RetryData,
227
228 elapsed: Duration,
230
231 will_terminate: bool,
233 },
234
235 TestAttemptFailedWillRetry {
239 stress_index: Option<StressIndex>,
241
242 test_instance: TestInstanceId<'a>,
244
245 run_status: ExecuteStatus<LiveSpec>,
247
248 delay_before_next_attempt: Duration,
250
251 failure_output: TestOutputDisplay,
253
254 running: usize,
256 },
257
258 TestRetryStarted {
260 stress_index: Option<StressIndex>,
262
263 test_instance: TestInstanceId<'a>,
265
266 slot_assignment: TestSlotAssignment,
269
270 retry_data: RetryData,
272
273 running: usize,
275
276 command_line: Vec<String>,
278 },
279
280 TestFinished {
282 stress_index: Option<StressIndex>,
284
285 test_instance: TestInstanceId<'a>,
287
288 success_output: TestOutputDisplay,
290
291 failure_output: TestOutputDisplay,
293
294 junit_store_success_output: bool,
296
297 junit_store_failure_output: bool,
299
300 junit_flaky_fail_status: JunitFlakyFailStatus,
302
303 run_statuses: ExecutionStatuses<LiveSpec>,
305
306 current_stats: RunStats,
308
309 running: usize,
311 },
312
313 TestSkipped {
315 stress_index: Option<StressIndex>,
317
318 test_instance: TestInstanceId<'a>,
320
321 reason: MismatchReason,
323 },
324
325 InfoStarted {
327 total: usize,
330
331 run_stats: RunStats,
333 },
334
335 InfoResponse {
337 index: usize,
339
340 total: usize,
342
343 response: InfoResponse<'a>,
345 },
346
347 InfoFinished {
349 missing: usize,
352 },
353
354 InputEnter {
357 current_stats: RunStats,
359
360 running: usize,
362 },
363
364 RunBeginCancel {
366 setup_scripts_running: usize,
368
369 current_stats: RunStats,
373
374 running: usize,
376 },
377
378 RunBeginKill {
380 setup_scripts_running: usize,
382
383 current_stats: RunStats,
387
388 running: usize,
390 },
391
392 RunPaused {
394 setup_scripts_running: usize,
396
397 running: usize,
399 },
400
401 RunContinued {
403 setup_scripts_running: usize,
405
406 running: usize,
408 },
409
410 StressSubRunFinished {
412 progress: StressProgress,
414
415 sub_elapsed: Duration,
417
418 sub_stats: RunStats,
420 },
421
422 RunFinished {
424 run_id: ReportUuid,
426
427 start_time: DateTime<FixedOffset>,
429
430 elapsed: Duration,
432
433 run_stats: RunFinishedStats,
435
436 outstanding_not_seen: Option<TestsNotSeen>,
441 },
442}
443
444#[derive(Clone, Debug)]
446pub struct TestsNotSeen {
447 pub not_seen: Vec<OwnedTestInstanceId>,
454
455 pub total_not_seen: usize,
457}
458
459#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(tag = "progress-type", rename_all = "kebab-case")]
462#[cfg_attr(test, derive(test_strategy::Arbitrary))]
463pub enum StressProgress {
464 Count {
466 total: StressCount,
468
469 elapsed: Duration,
471
472 completed: u32,
474 },
475
476 Time {
478 total: Duration,
480
481 elapsed: Duration,
483
484 completed: u32,
486 },
487}
488
489impl StressProgress {
490 pub fn remaining(&self) -> Option<StressRemaining> {
493 match self {
494 Self::Count {
495 total: StressCount::Count { count },
496 elapsed: _,
497 completed,
498 } => count
499 .get()
500 .checked_sub(*completed)
501 .and_then(|remaining| NonZero::try_from(remaining).ok())
502 .map(StressRemaining::Count),
503 Self::Count {
504 total: StressCount::Infinite,
505 ..
506 } => Some(StressRemaining::Infinite),
507 Self::Time {
508 total,
509 elapsed,
510 completed: _,
511 } => total.checked_sub(*elapsed).map(StressRemaining::Time),
512 }
513 }
514
515 pub fn unique_id(&self, run_id: ReportUuid) -> String {
517 let stress_current = match self {
518 Self::Count { completed, .. } | Self::Time { completed, .. } => *completed,
519 };
520 format!("{}:@stress-{}", run_id, stress_current)
521 }
522}
523
524#[derive(Clone, Debug)]
526pub enum StressRemaining {
527 Count(NonZero<u32>),
529
530 Infinite,
532
533 Time(Duration),
535}
536
537#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
539#[serde(rename_all = "kebab-case")]
540#[cfg_attr(test, derive(test_strategy::Arbitrary))]
541pub struct StressIndex {
542 pub current: u32,
544
545 pub total: Option<NonZero<u32>>,
547}
548
549impl StressIndex {
550 pub fn total_get(&self) -> Option<u32> {
552 self.total.map(|t| t.get())
553 }
554}
555
556#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
558#[serde(tag = "type", rename_all = "kebab-case")]
559#[cfg_attr(test, derive(test_strategy::Arbitrary))]
560pub enum RunFinishedStats {
561 Single(RunStats),
563
564 Stress(StressRunStats),
566}
567
568impl RunFinishedStats {
569 pub fn final_stats(&self) -> FinalRunStats {
572 match self {
573 Self::Single(stats) => stats.summarize_final(),
574 Self::Stress(stats) => stats.last_final_stats,
575 }
576 }
577}
578
579#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
581#[serde(rename_all = "kebab-case")]
582#[cfg_attr(test, derive(test_strategy::Arbitrary))]
583pub struct RunStats {
584 pub initial_run_count: usize,
588
589 pub finished_count: usize,
591
592 pub setup_scripts_initial_count: usize,
596
597 pub setup_scripts_finished_count: usize,
599
600 pub setup_scripts_passed: usize,
602
603 pub setup_scripts_failed: usize,
605
606 pub setup_scripts_exec_failed: usize,
608
609 pub setup_scripts_timed_out: usize,
611
612 pub passed: usize,
615
616 pub passed_slow: usize,
618
619 pub passed_timed_out: usize,
621
622 pub flaky: usize,
624
625 pub failed: usize,
628
629 pub failed_slow: usize,
631
632 pub failed_timed_out: usize,
634
635 pub leaky: usize,
637
638 pub leaky_failed: usize,
643
644 pub exec_failed: usize,
646
647 pub skipped: usize,
649
650 pub cancel_reason: Option<CancelReason>,
652}
653
654impl RunStats {
655 pub fn has_failures(&self) -> bool {
657 self.failed_setup_script_count() > 0 || self.failed_count() > 0
658 }
659
660 pub fn failed_setup_script_count(&self) -> usize {
662 self.setup_scripts_failed + self.setup_scripts_exec_failed + self.setup_scripts_timed_out
663 }
664
665 pub fn failed_count(&self) -> usize {
667 self.failed + self.exec_failed + self.failed_timed_out
668 }
669
670 pub fn summarize_final(&self) -> FinalRunStats {
672 if self.failed_setup_script_count() > 0 {
675 if self.cancel_reason > Some(CancelReason::TestFailure) {
678 FinalRunStats::Cancelled {
679 reason: self.cancel_reason,
680 kind: RunStatsFailureKind::SetupScript,
681 }
682 } else {
683 FinalRunStats::Failed {
684 kind: RunStatsFailureKind::SetupScript,
685 }
686 }
687 } else if self.setup_scripts_initial_count > self.setup_scripts_finished_count {
688 FinalRunStats::Cancelled {
689 reason: self.cancel_reason,
690 kind: RunStatsFailureKind::SetupScript,
691 }
692 } else if self.failed_count() > 0 {
693 let kind = RunStatsFailureKind::Test {
694 initial_run_count: self.initial_run_count,
695 not_run: self.initial_run_count.saturating_sub(self.finished_count),
696 };
697
698 if self.cancel_reason > Some(CancelReason::TestFailure) {
701 FinalRunStats::Cancelled {
702 reason: self.cancel_reason,
703 kind,
704 }
705 } else {
706 FinalRunStats::Failed { kind }
707 }
708 } else if self.initial_run_count > self.finished_count {
709 FinalRunStats::Cancelled {
710 reason: self.cancel_reason,
711 kind: RunStatsFailureKind::Test {
712 initial_run_count: self.initial_run_count,
713 not_run: self.initial_run_count.saturating_sub(self.finished_count),
714 },
715 }
716 } else if self.finished_count == 0 {
717 FinalRunStats::NoTestsRun
718 } else {
719 FinalRunStats::Success
720 }
721 }
722
723 pub(crate) fn on_setup_script_finished(&mut self, status: &SetupScriptExecuteStatus<LiveSpec>) {
724 self.setup_scripts_finished_count += 1;
725
726 match status.result {
727 ExecutionResultDescription::Pass
728 | ExecutionResultDescription::Leak {
729 result: LeakTimeoutResult::Pass,
730 } => {
731 self.setup_scripts_passed += 1;
732 }
733 ExecutionResultDescription::Fail { .. }
734 | ExecutionResultDescription::Leak {
735 result: LeakTimeoutResult::Fail,
736 } => {
737 self.setup_scripts_failed += 1;
738 }
739 ExecutionResultDescription::ExecFail => {
740 self.setup_scripts_exec_failed += 1;
741 }
742 ExecutionResultDescription::Timeout { .. } => {
744 self.setup_scripts_timed_out += 1;
745 }
746 }
747 }
748
749 pub(crate) fn on_test_finished(&mut self, run_statuses: &ExecutionStatuses<LiveSpec>) {
750 self.finished_count += 1;
751 let last_status = run_statuses.last_status();
760 match last_status.result {
761 ExecutionResultDescription::Pass => {
762 let is_flaky = run_statuses.len() > 1;
766 if is_flaky {
767 match run_statuses.flaky_result() {
768 FlakyResult::Fail => {
769 self.failed += 1;
770 if last_status.is_slow {
771 self.failed_slow += 1;
772 }
773 }
774 FlakyResult::Pass => {
775 self.passed += 1;
776 if last_status.is_slow {
777 self.passed_slow += 1;
778 }
779 self.flaky += 1;
780 }
781 }
782 } else {
783 self.passed += 1;
784 if last_status.is_slow {
785 self.passed_slow += 1;
786 }
787 }
788 }
789 ExecutionResultDescription::Leak {
790 result: LeakTimeoutResult::Pass,
791 } => {
792 let is_flaky = run_statuses.len() > 1;
793 if is_flaky {
794 match run_statuses.flaky_result() {
795 FlakyResult::Fail => {
796 self.failed += 1;
797 if last_status.is_slow {
798 self.failed_slow += 1;
799 }
800 self.leaky += 1;
802 }
803 FlakyResult::Pass => {
804 self.passed += 1;
805 self.leaky += 1;
806 if last_status.is_slow {
807 self.passed_slow += 1;
808 }
809 self.flaky += 1;
810 }
811 }
812 } else {
813 self.passed += 1;
814 self.leaky += 1;
815 if last_status.is_slow {
816 self.passed_slow += 1;
817 }
818 }
819 }
820 ExecutionResultDescription::Leak {
821 result: LeakTimeoutResult::Fail,
822 } => {
823 self.failed += 1;
824 self.leaky_failed += 1;
825 if last_status.is_slow {
826 self.failed_slow += 1;
827 }
828 }
829 ExecutionResultDescription::Fail { .. } => {
830 self.failed += 1;
831 if last_status.is_slow {
832 self.failed_slow += 1;
833 }
834 }
835 ExecutionResultDescription::Timeout {
836 result: SlowTimeoutResult::Pass,
837 } => {
838 let is_flaky = run_statuses.len() > 1;
839 if is_flaky {
840 match run_statuses.flaky_result() {
841 FlakyResult::Fail => {
842 self.failed += 1;
843 if last_status.is_slow {
846 self.failed_slow += 1;
847 }
848 }
849 FlakyResult::Pass => {
850 self.passed += 1;
851 self.passed_timed_out += 1;
852 self.flaky += 1;
853 }
854 }
855 } else {
856 self.passed += 1;
857 self.passed_timed_out += 1;
858 }
859 }
860 ExecutionResultDescription::Timeout {
861 result: SlowTimeoutResult::Fail,
862 } => {
863 self.failed_timed_out += 1;
864 }
865 ExecutionResultDescription::ExecFail => self.exec_failed += 1,
866 }
867 }
868}
869
870#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
872#[serde(tag = "outcome", rename_all = "kebab-case")]
873#[cfg_attr(test, derive(test_strategy::Arbitrary))]
874pub enum FinalRunStats {
875 Success,
877
878 NoTestsRun,
880
881 Cancelled {
883 reason: Option<CancelReason>,
888
889 kind: RunStatsFailureKind,
891 },
892
893 Failed {
895 kind: RunStatsFailureKind,
897 },
898}
899
900#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
902#[serde(rename_all = "kebab-case")]
903#[cfg_attr(test, derive(test_strategy::Arbitrary))]
904pub struct StressRunStats {
905 pub completed: StressIndex,
907
908 pub success_count: u32,
910
911 pub failed_count: u32,
913
914 pub last_final_stats: FinalRunStats,
916}
917
918impl StressRunStats {
919 pub fn summarize_final(&self) -> StressFinalRunStats {
921 if self.failed_count > 0 {
922 StressFinalRunStats::Failed
923 } else if matches!(self.last_final_stats, FinalRunStats::Cancelled { .. }) {
924 StressFinalRunStats::Cancelled
925 } else if matches!(self.last_final_stats, FinalRunStats::NoTestsRun) {
926 StressFinalRunStats::NoTestsRun
927 } else {
928 StressFinalRunStats::Success
929 }
930 }
931}
932
933pub enum StressFinalRunStats {
935 Success,
937
938 NoTestsRun,
940
941 Cancelled,
943
944 Failed,
946}
947
948#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
950#[serde(tag = "step", rename_all = "kebab-case")]
951#[cfg_attr(test, derive(test_strategy::Arbitrary))]
952pub enum RunStatsFailureKind {
953 SetupScript,
955
956 Test {
958 initial_run_count: usize,
960
961 not_run: usize,
964 },
965}
966
967#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
972#[derive(Serialize)]
973#[serde(
974 rename_all = "kebab-case",
975 bound(serialize = "S: SerializableOutputSpec")
976)]
977#[cfg_attr(
978 test,
979 derive(test_strategy::Arbitrary),
980 arbitrary(bound(S: ArbitraryOutputSpec))
981)]
982pub struct ExecutionStatuses<S: OutputSpec> {
983 #[cfg_attr(test, strategy(proptest::collection::vec(proptest::arbitrary::any::<ExecuteStatus<S>>(), 1..=3)))]
985 statuses: Vec<ExecuteStatus<S>>,
986
987 #[serde(default)]
989 flaky_result: FlakyResult,
990}
991
992impl<'de, S: SerializableOutputSpec> Deserialize<'de> for ExecutionStatuses<S> {
993 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
994 #[derive(Deserialize)]
997 #[serde(
998 rename_all = "kebab-case",
999 bound(deserialize = "S: SerializableOutputSpec")
1000 )]
1001 struct Helper<S: OutputSpec> {
1002 statuses: Vec<ExecuteStatus<S>>,
1003 #[serde(default)]
1004 flaky_result: FlakyResult,
1005 }
1006
1007 let helper = Helper::<S>::deserialize(deserializer)?;
1008 if helper.statuses.is_empty() {
1009 return Err(serde::de::Error::custom("expected non-empty statuses"));
1010 }
1011 Ok(Self {
1012 statuses: helper.statuses,
1013 flaky_result: helper.flaky_result,
1014 })
1015 }
1016}
1017
1018#[expect(clippy::len_without_is_empty)] impl<S: OutputSpec> ExecutionStatuses<S> {
1020 pub(crate) fn new(statuses: Vec<ExecuteStatus<S>>, flaky_result: FlakyResult) -> Self {
1021 debug_assert!(!statuses.is_empty(), "ExecutionStatuses must be non-empty");
1022 Self {
1023 statuses,
1024 flaky_result,
1025 }
1026 }
1027
1028 pub fn flaky_result(&self) -> FlakyResult {
1030 self.flaky_result
1031 }
1032
1033 pub fn last_status(&self) -> &ExecuteStatus<S> {
1037 self.statuses
1038 .last()
1039 .expect("execution statuses is non-empty")
1040 }
1041
1042 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &'_ ExecuteStatus<S>> + '_ {
1044 self.statuses.iter()
1045 }
1046
1047 pub fn len(&self) -> usize {
1049 self.statuses.len()
1050 }
1051
1052 pub fn describe(&self) -> ExecutionDescription<'_, S> {
1054 let last_status = self.last_status();
1055 if last_status.result.is_success() {
1056 if self.statuses.len() > 1 {
1057 ExecutionDescription::Flaky {
1058 last_status,
1059 prior_statuses: &self.statuses[..self.statuses.len() - 1],
1060 result: self.flaky_result,
1061 }
1062 } else {
1063 ExecutionDescription::Success {
1064 single_status: last_status,
1065 }
1066 }
1067 } else {
1068 let first_status = self
1069 .statuses
1070 .first()
1071 .expect("execution statuses is non-empty");
1072 let retries = &self.statuses[1..];
1073 ExecutionDescription::Failure {
1074 first_status,
1075 last_status,
1076 retries,
1077 }
1078 }
1079 }
1080}
1081
1082impl<S: OutputSpec> IntoIterator for ExecutionStatuses<S> {
1083 type Item = ExecuteStatus<S>;
1084 type IntoIter = std::vec::IntoIter<ExecuteStatus<S>>;
1085
1086 fn into_iter(self) -> Self::IntoIter {
1087 self.statuses.into_iter()
1088 }
1089}
1090
1091#[derive_where::derive_where(Debug; S::ChildOutputDesc)]
1098pub enum ExecutionDescription<'a, S: OutputSpec> {
1099 Success {
1101 single_status: &'a ExecuteStatus<S>,
1103 },
1104
1105 Flaky {
1107 last_status: &'a ExecuteStatus<S>,
1109
1110 prior_statuses: &'a [ExecuteStatus<S>],
1112
1113 result: FlakyResult,
1115 },
1116
1117 Failure {
1119 first_status: &'a ExecuteStatus<S>,
1121
1122 last_status: &'a ExecuteStatus<S>,
1124
1125 retries: &'a [ExecuteStatus<S>],
1129 },
1130}
1131
1132impl<S: OutputSpec> Clone for ExecutionDescription<'_, S> {
1135 fn clone(&self) -> Self {
1136 *self
1137 }
1138}
1139
1140impl<S: OutputSpec> Copy for ExecutionDescription<'_, S> {}
1141
1142impl<'a, S: OutputSpec> ExecutionDescription<'a, S> {
1143 pub fn status_level(&self) -> StatusLevel {
1145 match self {
1146 ExecutionDescription::Success { single_status } => match single_status.result {
1147 ExecutionResultDescription::Leak {
1148 result: LeakTimeoutResult::Pass,
1149 } => StatusLevel::Leak,
1150 ExecutionResultDescription::Pass => StatusLevel::Pass,
1151 ExecutionResultDescription::Timeout {
1152 result: SlowTimeoutResult::Pass,
1153 } => StatusLevel::Slow,
1154 ref other => unreachable!(
1155 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
1156 ),
1157 },
1158 ExecutionDescription::Flaky {
1160 result: FlakyResult::Pass,
1161 ..
1162 } => StatusLevel::Retry,
1163 ExecutionDescription::Flaky {
1164 result: FlakyResult::Fail,
1165 ..
1166 } => StatusLevel::Fail,
1167 ExecutionDescription::Failure { .. } => StatusLevel::Fail,
1168 }
1169 }
1170
1171 pub fn final_status_level(&self) -> FinalStatusLevel {
1173 match self {
1174 ExecutionDescription::Success { single_status, .. } => {
1175 if single_status.is_slow {
1177 FinalStatusLevel::Slow
1178 } else {
1179 match single_status.result {
1180 ExecutionResultDescription::Pass => FinalStatusLevel::Pass,
1181 ExecutionResultDescription::Leak {
1182 result: LeakTimeoutResult::Pass,
1183 } => FinalStatusLevel::Leak,
1184 ExecutionResultDescription::Timeout {
1188 result: SlowTimeoutResult::Pass,
1189 } => FinalStatusLevel::Slow,
1190 ref other => unreachable!(
1191 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
1192 ),
1193 }
1194 }
1195 }
1196 ExecutionDescription::Flaky {
1198 result: FlakyResult::Pass,
1199 ..
1200 } => FinalStatusLevel::Flaky,
1201 ExecutionDescription::Flaky {
1203 result: FlakyResult::Fail,
1204 ..
1205 } => FinalStatusLevel::Fail,
1206 ExecutionDescription::Failure { .. } => FinalStatusLevel::Fail,
1207 }
1208 }
1209
1210 pub fn is_success_for_output(&self) -> bool {
1228 match self {
1229 ExecutionDescription::Success { .. } => true,
1230 ExecutionDescription::Flaky { .. } => true,
1234 ExecutionDescription::Failure { .. } => false,
1235 }
1236 }
1237
1238 pub fn last_status(&self) -> &'a ExecuteStatus<S> {
1240 match self {
1241 ExecutionDescription::Success {
1242 single_status: last_status,
1243 }
1244 | ExecutionDescription::Flaky { last_status, .. }
1245 | ExecutionDescription::Failure { last_status, .. } => last_status,
1246 }
1247 }
1248}
1249
1250#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1256#[serde(rename_all = "kebab-case")]
1257#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1258pub struct ErrorSummary {
1259 pub short_message: String,
1261
1262 pub description: String,
1264}
1265
1266#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1271#[serde(rename_all = "kebab-case")]
1272#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1273pub struct OutputErrorSlice {
1274 pub slice: String,
1276
1277 pub start: usize,
1279}
1280
1281#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1290#[derive(Serialize, Deserialize)]
1291#[serde(
1292 rename_all = "kebab-case",
1293 bound(
1294 serialize = "S: SerializableOutputSpec",
1295 deserialize = "S: SerializableOutputSpec"
1296 )
1297)]
1298#[cfg_attr(
1299 test,
1300 derive(test_strategy::Arbitrary),
1301 arbitrary(bound(S: ArbitraryOutputSpec))
1302)]
1303pub struct ExecuteStatus<S: OutputSpec> {
1304 pub retry_data: RetryData,
1306 pub output: ChildExecutionOutputDescription<S>,
1308 pub result: ExecutionResultDescription,
1310 #[cfg_attr(
1312 test,
1313 strategy(crate::reporter::test_helpers::arb_datetime_fixed_offset())
1314 )]
1315 pub start_time: DateTime<FixedOffset>,
1316 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1318 pub time_taken: Duration,
1319 pub is_slow: bool,
1321 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1323 pub delay_before_start: Duration,
1324 pub error_summary: Option<ErrorSummary>,
1329 pub output_error_slice: Option<OutputErrorSlice>,
1334}
1335
1336#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1345#[derive(Serialize, Deserialize)]
1346#[serde(
1347 rename_all = "kebab-case",
1348 bound(
1349 serialize = "S: SerializableOutputSpec",
1350 deserialize = "S: SerializableOutputSpec"
1351 )
1352)]
1353#[cfg_attr(
1354 test,
1355 derive(test_strategy::Arbitrary),
1356 arbitrary(bound(S: ArbitraryOutputSpec))
1357)]
1358pub struct SetupScriptExecuteStatus<S: OutputSpec> {
1359 pub output: ChildExecutionOutputDescription<S>,
1361
1362 pub result: ExecutionResultDescription,
1364
1365 #[cfg_attr(
1367 test,
1368 strategy(crate::reporter::test_helpers::arb_datetime_fixed_offset())
1369 )]
1370 pub start_time: DateTime<FixedOffset>,
1371
1372 #[cfg_attr(test, strategy(crate::reporter::test_helpers::arb_duration()))]
1374 pub time_taken: Duration,
1375
1376 pub is_slow: bool,
1378
1379 pub env_map: Option<SetupScriptEnvMap>,
1384
1385 pub error_summary: Option<ErrorSummary>,
1390}
1391
1392#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1396#[serde(rename_all = "kebab-case")]
1397#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1398pub struct SetupScriptEnvMap {
1399 pub env_map: BTreeMap<String, String>,
1401}
1402
1403#[derive_where::derive_where(Clone, Debug, PartialEq, Eq; S::ChildOutputDesc)]
1412#[derive(Serialize, Deserialize)]
1413#[serde(
1414 tag = "type",
1415 rename_all = "kebab-case",
1416 bound(
1417 serialize = "S: SerializableOutputSpec",
1418 deserialize = "S: SerializableOutputSpec"
1419 )
1420)]
1421#[cfg_attr(
1422 test,
1423 derive(test_strategy::Arbitrary),
1424 arbitrary(bound(S: ArbitraryOutputSpec))
1425)]
1426pub enum ChildExecutionOutputDescription<S: OutputSpec> {
1427 Output {
1429 result: Option<ExecutionResultDescription>,
1433
1434 output: S::ChildOutputDesc,
1436
1437 errors: Option<ErrorList<ChildErrorDescription>>,
1440 },
1441
1442 StartError(ChildStartErrorDescription),
1444}
1445
1446impl<S: OutputSpec> ChildExecutionOutputDescription<S> {
1447 pub fn has_errors(&self) -> bool {
1449 match self {
1450 Self::Output { errors, result, .. } => {
1451 if errors.is_some() {
1452 return true;
1453 }
1454 if let Some(result) = result {
1455 return !result.is_success();
1456 }
1457 false
1458 }
1459 Self::StartError(_) => true,
1460 }
1461 }
1462}
1463
1464#[derive(Clone, Debug)]
1476pub enum ChildOutputDescription {
1477 Split {
1479 stdout: Option<ChildSingleOutput>,
1481 stderr: Option<ChildSingleOutput>,
1483 },
1484
1485 Combined {
1487 output: ChildSingleOutput,
1489 },
1490
1491 NotLoaded,
1497}
1498
1499impl ChildOutputDescription {
1500 pub fn stdout_stderr_len(&self) -> (Option<u64>, Option<u64>) {
1504 match self {
1505 Self::Split { stdout, stderr } => (
1506 stdout.as_ref().map(|s| s.buf().len() as u64),
1507 stderr.as_ref().map(|s| s.buf().len() as u64),
1508 ),
1509 Self::Combined { output } => (Some(output.buf().len() as u64), None),
1510 Self::NotLoaded => {
1511 unreachable!(
1512 "attempted to get output lengths from output that was not loaded \
1513 (this method is only called from the live runner, where NotLoaded \
1514 is never produced)"
1515 );
1516 }
1517 }
1518 }
1519}
1520
1521#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1525#[serde(tag = "kind", rename_all = "kebab-case")]
1526#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1527pub enum ChildStartErrorDescription {
1528 TempPath {
1530 source: SerializableError,
1532 },
1533
1534 Spawn {
1536 source: SerializableError,
1538 },
1539}
1540
1541impl fmt::Display for ChildStartErrorDescription {
1542 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1543 match self {
1544 Self::TempPath { .. } => {
1545 write!(f, "error creating temporary path for setup script")
1546 }
1547 Self::Spawn { .. } => write!(f, "error spawning child process"),
1548 }
1549 }
1550}
1551
1552impl std::error::Error for ChildStartErrorDescription {
1553 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1554 match self {
1555 Self::TempPath { source } | Self::Spawn { source } => Some(source),
1556 }
1557 }
1558}
1559
1560#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1564#[serde(tag = "kind", rename_all = "kebab-case")]
1565#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1566pub enum ChildErrorDescription {
1567 ReadStdout {
1569 source: SerializableError,
1571 },
1572
1573 ReadStderr {
1575 source: SerializableError,
1577 },
1578
1579 ReadCombined {
1581 source: SerializableError,
1583 },
1584
1585 Wait {
1587 source: SerializableError,
1589 },
1590
1591 SetupScriptOutput {
1593 source: SerializableError,
1595 },
1596}
1597
1598impl fmt::Display for ChildErrorDescription {
1599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1600 match self {
1601 Self::ReadStdout { .. } => write!(f, "error reading standard output"),
1602 Self::ReadStderr { .. } => write!(f, "error reading standard error"),
1603 Self::ReadCombined { .. } => {
1604 write!(f, "error reading combined stream")
1605 }
1606 Self::Wait { .. } => {
1607 write!(f, "error waiting for child process to exit")
1608 }
1609 Self::SetupScriptOutput { .. } => {
1610 write!(f, "error reading setup script output")
1611 }
1612 }
1613 }
1614}
1615
1616impl std::error::Error for ChildErrorDescription {
1617 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1618 match self {
1619 Self::ReadStdout { source }
1620 | Self::ReadStderr { source }
1621 | Self::ReadCombined { source }
1622 | Self::Wait { source }
1623 | Self::SetupScriptOutput { source } => Some(source),
1624 }
1625 }
1626}
1627
1628#[derive(Clone, Debug, PartialEq, Eq)]
1633pub struct SerializableError {
1634 message: String,
1635 source: Option<Box<SerializableError>>,
1636}
1637
1638impl SerializableError {
1639 pub fn new(error: &dyn std::error::Error) -> Self {
1642 let message = error.to_string();
1643 let mut causes = Vec::new();
1644 let mut source = error.source();
1645 while let Some(err) = source {
1646 causes.push(err.to_string());
1647 source = err.source();
1648 }
1649 Self::from_message_and_causes(message, causes)
1650 }
1651
1652 pub fn from_message_and_causes(message: String, causes: Vec<String>) -> Self {
1655 let mut next = None;
1659 for cause in causes.into_iter().rev() {
1660 let error = Self {
1661 message: cause,
1662 source: next.map(Box::new),
1663 };
1664 next = Some(error);
1665 }
1666 Self {
1667 message,
1668 source: next.map(Box::new),
1669 }
1670 }
1671
1672 pub fn message(&self) -> &str {
1674 &self.message
1675 }
1676
1677 pub fn sources(&self) -> SerializableErrorSources<'_> {
1679 SerializableErrorSources {
1680 current: self.source.as_deref(),
1681 }
1682 }
1683}
1684
1685impl fmt::Display for SerializableError {
1686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1687 f.write_str(&self.message)
1688 }
1689}
1690
1691impl std::error::Error for SerializableError {
1692 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1693 self.source
1694 .as_deref()
1695 .map(|s| s as &(dyn std::error::Error + 'static))
1696 }
1697}
1698
1699#[derive(Debug)]
1701pub struct SerializableErrorSources<'a> {
1702 current: Option<&'a SerializableError>,
1703}
1704
1705impl<'a> Iterator for SerializableErrorSources<'a> {
1706 type Item = &'a SerializableError;
1707
1708 fn next(&mut self) -> Option<Self::Item> {
1709 let current = self.current?;
1710 self.current = current.source.as_deref();
1711 Some(current)
1712 }
1713}
1714
1715mod serializable_error_serde {
1716 use super::*;
1717
1718 #[derive(Serialize, Deserialize)]
1719 struct Ser {
1720 message: String,
1721 #[serde(default)]
1724 causes: Vec<String>,
1725 }
1726
1727 impl Serialize for SerializableError {
1728 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1729 let mut causes = Vec::new();
1730 let mut cause = self.source.as_ref();
1731 while let Some(c) = cause {
1732 causes.push(c.message.clone());
1733 cause = c.source.as_ref();
1734 }
1735
1736 let ser = Ser {
1737 message: self.message.clone(),
1738 causes,
1739 };
1740 ser.serialize(serializer)
1741 }
1742 }
1743
1744 impl<'de> Deserialize<'de> for SerializableError {
1745 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1746 let ser = Ser::deserialize(deserializer)?;
1747 Ok(SerializableError::from_message_and_causes(
1748 ser.message,
1749 ser.causes,
1750 ))
1751 }
1752 }
1753}
1754
1755#[cfg(test)]
1756mod serializable_error_arbitrary {
1757 use super::*;
1758 use proptest::prelude::*;
1759
1760 impl Arbitrary for SerializableError {
1761 type Parameters = ();
1762 type Strategy = BoxedStrategy<Self>;
1763
1764 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1765 (
1766 any::<String>(),
1767 proptest::collection::vec(any::<String>(), 0..3),
1768 )
1769 .prop_map(|(message, causes)| {
1770 SerializableError::from_message_and_causes(message, causes)
1771 })
1772 .boxed()
1773 }
1774 }
1775}
1776
1777impl From<ChildExecutionOutput> for ChildExecutionOutputDescription<LiveSpec> {
1778 fn from(output: ChildExecutionOutput) -> Self {
1779 match output {
1780 ChildExecutionOutput::Output {
1781 result,
1782 output,
1783 errors,
1784 } => Self::Output {
1785 result: result.map(ExecutionResultDescription::from),
1786 output: ChildOutputDescription::from(output),
1787 errors: errors.map(|e| e.map(ChildErrorDescription::from)),
1788 },
1789 ChildExecutionOutput::StartError(error) => {
1790 Self::StartError(ChildStartErrorDescription::from(error))
1791 }
1792 }
1793 }
1794}
1795
1796impl From<ChildOutput> for ChildOutputDescription {
1797 fn from(output: ChildOutput) -> Self {
1798 match output {
1799 ChildOutput::Split(split) => Self::Split {
1800 stdout: split.stdout,
1801 stderr: split.stderr,
1802 },
1803 ChildOutput::Combined { output } => Self::Combined { output },
1804 }
1805 }
1806}
1807
1808impl From<ChildStartError> for ChildStartErrorDescription {
1809 fn from(error: ChildStartError) -> Self {
1810 match error {
1811 ChildStartError::TempPath(e) => Self::TempPath {
1812 source: SerializableError::new(&*e),
1813 },
1814 ChildStartError::Spawn(e) => Self::Spawn {
1815 source: SerializableError::new(&*e),
1816 },
1817 }
1818 }
1819}
1820
1821impl From<ChildError> for ChildErrorDescription {
1822 fn from(error: ChildError) -> Self {
1823 match error {
1824 ChildError::Fd(ChildFdError::ReadStdout(e)) => Self::ReadStdout {
1825 source: SerializableError::new(&*e),
1826 },
1827 ChildError::Fd(ChildFdError::ReadStderr(e)) => Self::ReadStderr {
1828 source: SerializableError::new(&*e),
1829 },
1830 ChildError::Fd(ChildFdError::ReadCombined(e)) => Self::ReadCombined {
1831 source: SerializableError::new(&*e),
1832 },
1833 ChildError::Fd(ChildFdError::Wait(e)) => Self::Wait {
1834 source: SerializableError::new(&*e),
1835 },
1836 ChildError::SetupScriptOutput(e) => Self::SetupScriptOutput {
1837 source: SerializableError::new(&e),
1838 },
1839 }
1840 }
1841}
1842
1843#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
1845#[serde(rename_all = "kebab-case")]
1846#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1847pub struct RetryData {
1848 pub attempt: u32,
1850
1851 pub total_attempts: u32,
1853}
1854
1855impl RetryData {
1856 pub fn is_last_attempt(&self) -> bool {
1858 self.attempt >= self.total_attempts
1859 }
1860}
1861
1862#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1864pub enum ExecutionResult {
1865 Pass,
1867 Leak {
1871 result: LeakTimeoutResult,
1878 },
1879 Fail {
1881 failure_status: FailureStatus,
1883
1884 leaked: bool,
1888 },
1889 ExecFail,
1891 Timeout {
1893 result: SlowTimeoutResult,
1895 },
1896}
1897
1898impl ExecutionResult {
1899 pub fn is_success(self) -> bool {
1901 match self {
1902 ExecutionResult::Pass
1903 | ExecutionResult::Timeout {
1904 result: SlowTimeoutResult::Pass,
1905 }
1906 | ExecutionResult::Leak {
1907 result: LeakTimeoutResult::Pass,
1908 } => true,
1909 ExecutionResult::Leak {
1910 result: LeakTimeoutResult::Fail,
1911 }
1912 | ExecutionResult::Fail { .. }
1913 | ExecutionResult::ExecFail
1914 | ExecutionResult::Timeout {
1915 result: SlowTimeoutResult::Fail,
1916 } => false,
1917 }
1918 }
1919
1920 pub fn as_static_str(&self) -> &'static str {
1922 match self {
1923 ExecutionResult::Pass => "pass",
1924 ExecutionResult::Leak { .. } => "leak",
1925 ExecutionResult::Fail { .. } => "fail",
1926 ExecutionResult::ExecFail => "exec-fail",
1927 ExecutionResult::Timeout { .. } => "timeout",
1928 }
1929 }
1930}
1931
1932#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1934pub enum FailureStatus {
1935 ExitCode(i32),
1937
1938 Abort(AbortStatus),
1940}
1941
1942impl FailureStatus {
1943 pub fn extract(exit_status: ExitStatus) -> Self {
1945 if let Some(abort_status) = AbortStatus::extract(exit_status) {
1946 FailureStatus::Abort(abort_status)
1947 } else {
1948 FailureStatus::ExitCode(
1949 exit_status
1950 .code()
1951 .expect("if abort_status is None, then code must be present"),
1952 )
1953 }
1954 }
1955}
1956
1957#[derive(Copy, Clone, Eq, PartialEq)]
1961pub enum AbortStatus {
1962 #[cfg(unix)]
1964 UnixSignal(i32),
1965
1966 #[cfg(windows)]
1968 WindowsNtStatus(windows_sys::Win32::Foundation::NTSTATUS),
1969
1970 #[cfg(windows)]
1972 JobObject,
1973}
1974
1975impl AbortStatus {
1976 pub fn extract(exit_status: ExitStatus) -> Option<Self> {
1978 cfg_if::cfg_if! {
1979 if #[cfg(unix)] {
1980 use std::os::unix::process::ExitStatusExt;
1982 exit_status.signal().map(AbortStatus::UnixSignal)
1983 } else if #[cfg(windows)] {
1984 exit_status.code().and_then(|code| {
1985 (code < 0).then_some(AbortStatus::WindowsNtStatus(code))
1986 })
1987 } else {
1988 None
1989 }
1990 }
1991 }
1992}
1993
1994impl fmt::Debug for AbortStatus {
1995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1996 match self {
1997 #[cfg(unix)]
1998 AbortStatus::UnixSignal(signal) => write!(f, "UnixSignal({signal})"),
1999 #[cfg(windows)]
2000 AbortStatus::WindowsNtStatus(status) => write!(f, "WindowsNtStatus({status:x})"),
2001 #[cfg(windows)]
2002 AbortStatus::JobObject => write!(f, "JobObject"),
2003 }
2004 }
2005}
2006
2007#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2013#[serde(tag = "kind", rename_all = "kebab-case")]
2014#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2015#[non_exhaustive]
2016pub enum AbortDescription {
2017 UnixSignal {
2019 signal: i32,
2021 #[cfg_attr(
2024 test,
2025 strategy(proptest::option::of(crate::reporter::test_helpers::arb_smol_str()))
2026 )]
2027 name: Option<SmolStr>,
2028 },
2029
2030 WindowsNtStatus {
2032 code: i32,
2034 #[cfg_attr(
2036 test,
2037 strategy(proptest::option::of(crate::reporter::test_helpers::arb_smol_str()))
2038 )]
2039 message: Option<SmolStr>,
2040 },
2041
2042 WindowsJobObject,
2044}
2045
2046impl fmt::Display for AbortDescription {
2047 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2048 match self {
2049 Self::UnixSignal { signal, name } => {
2050 write!(f, "aborted with signal {signal}")?;
2051 if let Some(name) = name {
2052 write!(f, " (SIG{name})")?;
2053 }
2054 Ok(())
2055 }
2056 Self::WindowsNtStatus { code, message } => {
2057 write!(f, "aborted with code {code:#010x}")?;
2058 if let Some(message) = message {
2059 write!(f, ": {message}")?;
2060 }
2061 Ok(())
2062 }
2063 Self::WindowsJobObject => {
2064 write!(f, "terminated via job object")
2065 }
2066 }
2067 }
2068}
2069
2070impl From<AbortStatus> for AbortDescription {
2071 fn from(status: AbortStatus) -> Self {
2072 cfg_if::cfg_if! {
2073 if #[cfg(unix)] {
2074 match status {
2075 AbortStatus::UnixSignal(signal) => Self::UnixSignal {
2076 signal,
2077 name: crate::helpers::signal_str(signal).map(SmolStr::new_static),
2078 },
2079 }
2080 } else if #[cfg(windows)] {
2081 match status {
2082 AbortStatus::WindowsNtStatus(code) => Self::WindowsNtStatus {
2083 code,
2084 message: crate::helpers::windows_nt_status_message(code),
2085 },
2086 AbortStatus::JobObject => Self::WindowsJobObject,
2087 }
2088 } else {
2089 match status {}
2090 }
2091 }
2092 }
2093}
2094
2095#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2099#[serde(tag = "kind", rename_all = "kebab-case")]
2100#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2101#[non_exhaustive]
2102pub enum FailureDescription {
2103 ExitCode {
2105 code: i32,
2107 },
2108
2109 Abort {
2116 abort: AbortDescription,
2118 },
2119}
2120
2121impl From<FailureStatus> for FailureDescription {
2122 fn from(status: FailureStatus) -> Self {
2123 match status {
2124 FailureStatus::ExitCode(code) => Self::ExitCode { code },
2125 FailureStatus::Abort(abort) => Self::Abort {
2126 abort: AbortDescription::from(abort),
2127 },
2128 }
2129 }
2130}
2131
2132impl fmt::Display for FailureDescription {
2133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2134 match self {
2135 Self::ExitCode { code } => write!(f, "exited with code {code}"),
2136 Self::Abort { abort } => write!(f, "{abort}"),
2137 }
2138 }
2139}
2140
2141#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2146#[serde(tag = "status", rename_all = "kebab-case")]
2147#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2148#[non_exhaustive]
2149pub enum ExecutionResultDescription {
2150 Pass,
2152
2153 Leak {
2155 result: LeakTimeoutResult,
2157 },
2158
2159 Fail {
2161 failure: FailureDescription,
2163
2164 leaked: bool,
2166 },
2167
2168 ExecFail,
2170
2171 Timeout {
2173 result: SlowTimeoutResult,
2175 },
2176}
2177
2178impl ExecutionResultDescription {
2179 pub fn is_success(&self) -> bool {
2181 match self {
2182 Self::Pass
2183 | Self::Timeout {
2184 result: SlowTimeoutResult::Pass,
2185 }
2186 | Self::Leak {
2187 result: LeakTimeoutResult::Pass,
2188 } => true,
2189 Self::Leak {
2190 result: LeakTimeoutResult::Fail,
2191 }
2192 | Self::Fail { .. }
2193 | Self::ExecFail
2194 | Self::Timeout {
2195 result: SlowTimeoutResult::Fail,
2196 } => false,
2197 }
2198 }
2199
2200 pub fn as_static_str(&self) -> &'static str {
2202 match self {
2203 Self::Pass => "pass",
2204 Self::Leak { .. } => "leak",
2205 Self::Fail { .. } => "fail",
2206 Self::ExecFail => "exec-fail",
2207 Self::Timeout { .. } => "timeout",
2208 }
2209 }
2210
2211 pub fn is_termination_failure(&self) -> bool {
2223 matches!(
2224 self,
2225 Self::Fail {
2226 failure: FailureDescription::Abort {
2227 abort: AbortDescription::UnixSignal {
2228 signal: SIGTERM,
2229 ..
2230 },
2231 },
2232 ..
2233 } | Self::Fail {
2234 failure: FailureDescription::Abort {
2235 abort: AbortDescription::WindowsJobObject,
2236 },
2237 ..
2238 }
2239 )
2240 }
2241}
2242
2243impl From<ExecutionResult> for ExecutionResultDescription {
2244 fn from(result: ExecutionResult) -> Self {
2245 match result {
2246 ExecutionResult::Pass => Self::Pass,
2247 ExecutionResult::Leak { result } => Self::Leak { result },
2248 ExecutionResult::Fail {
2249 failure_status,
2250 leaked,
2251 } => Self::Fail {
2252 failure: FailureDescription::from(failure_status),
2253 leaked,
2254 },
2255 ExecutionResult::ExecFail => Self::ExecFail,
2256 ExecutionResult::Timeout { result } => Self::Timeout { result },
2257 }
2258 }
2259}
2260
2261#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
2264#[serde(rename_all = "kebab-case")]
2265#[cfg_attr(test, derive(test_strategy::Arbitrary))]
2266pub enum CancelReason {
2267 SetupScriptFailure,
2269
2270 TestFailure,
2272
2273 ReportError,
2275
2276 GlobalTimeout,
2278
2279 TestFailureImmediate,
2281
2282 Signal,
2284
2285 Interrupt,
2287
2288 SecondSignal,
2290}
2291
2292impl CancelReason {
2293 pub(crate) fn to_static_str(self) -> &'static str {
2294 match self {
2295 CancelReason::SetupScriptFailure => "setup script failure",
2296 CancelReason::TestFailure => "test failure",
2297 CancelReason::ReportError => "reporting error",
2298 CancelReason::GlobalTimeout => "global timeout",
2299 CancelReason::TestFailureImmediate => "test failure",
2300 CancelReason::Signal => "signal",
2301 CancelReason::Interrupt => "interrupt",
2302 CancelReason::SecondSignal => "second signal",
2303 }
2304 }
2305}
2306#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2308pub enum UnitKind {
2309 Test,
2311
2312 Script,
2314}
2315
2316impl UnitKind {
2317 pub(crate) const WAITING_ON_TEST_MESSAGE: &str = "waiting on test process";
2318 pub(crate) const WAITING_ON_SCRIPT_MESSAGE: &str = "waiting on script process";
2319
2320 pub(crate) const EXECUTING_TEST_MESSAGE: &str = "executing test";
2321 pub(crate) const EXECUTING_SCRIPT_MESSAGE: &str = "executing script";
2322
2323 pub(crate) fn waiting_on_message(&self) -> &'static str {
2324 match self {
2325 UnitKind::Test => Self::WAITING_ON_TEST_MESSAGE,
2326 UnitKind::Script => Self::WAITING_ON_SCRIPT_MESSAGE,
2327 }
2328 }
2329
2330 pub(crate) fn executing_message(&self) -> &'static str {
2331 match self {
2332 UnitKind::Test => Self::EXECUTING_TEST_MESSAGE,
2333 UnitKind::Script => Self::EXECUTING_SCRIPT_MESSAGE,
2334 }
2335 }
2336}
2337
2338#[derive(Clone, Debug)]
2340pub enum InfoResponse<'a> {
2341 SetupScript(SetupScriptInfoResponse),
2343
2344 Test(TestInfoResponse<'a>),
2346}
2347
2348#[derive(Clone, Debug)]
2350pub struct SetupScriptInfoResponse {
2351 pub stress_index: Option<StressIndex>,
2353
2354 pub script_id: ScriptId,
2356
2357 pub program: String,
2359
2360 pub args: Vec<String>,
2362
2363 pub state: UnitState,
2365
2366 pub output: ChildExecutionOutputDescription<LiveSpec>,
2368}
2369
2370#[derive(Clone, Debug)]
2372pub struct TestInfoResponse<'a> {
2373 pub stress_index: Option<StressIndex>,
2375
2376 pub test_instance: TestInstanceId<'a>,
2378
2379 pub retry_data: RetryData,
2381
2382 pub state: UnitState,
2384
2385 pub output: ChildExecutionOutputDescription<LiveSpec>,
2387}
2388
2389#[derive(Clone, Debug)]
2394pub enum UnitState {
2395 Running {
2397 pid: u32,
2399
2400 time_taken: Duration,
2402
2403 slow_after: Option<Duration>,
2406 },
2407
2408 Exiting {
2411 pid: u32,
2413
2414 time_taken: Duration,
2416
2417 slow_after: Option<Duration>,
2420
2421 tentative_result: Option<ExecutionResultDescription>,
2426
2427 waiting_duration: Duration,
2429
2430 remaining: Duration,
2432 },
2433
2434 Terminating(UnitTerminatingState),
2436
2437 Exited {
2439 result: ExecutionResultDescription,
2441
2442 time_taken: Duration,
2444
2445 slow_after: Option<Duration>,
2448 },
2449
2450 DelayBeforeNextAttempt {
2453 previous_result: ExecutionResultDescription,
2455
2456 previous_slow: bool,
2458
2459 waiting_duration: Duration,
2461
2462 remaining: Duration,
2464 },
2465}
2466
2467impl UnitState {
2468 pub fn has_valid_output(&self) -> bool {
2470 match self {
2471 UnitState::Running { .. }
2472 | UnitState::Exiting { .. }
2473 | UnitState::Terminating(_)
2474 | UnitState::Exited { .. } => true,
2475 UnitState::DelayBeforeNextAttempt { .. } => false,
2476 }
2477 }
2478}
2479
2480#[derive(Clone, Debug)]
2484pub struct UnitTerminatingState {
2485 pub pid: u32,
2487
2488 pub time_taken: Duration,
2490
2491 pub reason: UnitTerminateReason,
2493
2494 pub method: UnitTerminateMethod,
2496
2497 pub waiting_duration: Duration,
2499
2500 pub remaining: Duration,
2502}
2503
2504#[derive(Clone, Copy, Debug)]
2508pub enum UnitTerminateReason {
2509 Timeout,
2511
2512 Signal,
2514
2515 Interrupt,
2517}
2518
2519impl fmt::Display for UnitTerminateReason {
2520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2521 match self {
2522 UnitTerminateReason::Timeout => write!(f, "timeout"),
2523 UnitTerminateReason::Signal => write!(f, "signal"),
2524 UnitTerminateReason::Interrupt => write!(f, "interrupt"),
2525 }
2526 }
2527}
2528
2529#[derive(Clone, Copy, Debug)]
2531pub enum UnitTerminateMethod {
2532 #[cfg(unix)]
2534 Signal(UnitTerminateSignal),
2535
2536 #[cfg(windows)]
2538 JobObject,
2539
2540 #[cfg(windows)]
2548 Wait,
2549
2550 #[cfg(test)]
2552 Fake,
2553}
2554
2555#[cfg(unix)]
2556#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2558pub enum UnitTerminateSignal {
2559 Interrupt,
2561
2562 Term,
2564
2565 Hangup,
2567
2568 Quit,
2570
2571 Kill,
2573}
2574
2575#[cfg(unix)]
2576impl fmt::Display for UnitTerminateSignal {
2577 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2578 match self {
2579 UnitTerminateSignal::Interrupt => write!(f, "SIGINT"),
2580 UnitTerminateSignal::Term => write!(f, "SIGTERM"),
2581 UnitTerminateSignal::Hangup => write!(f, "SIGHUP"),
2582 UnitTerminateSignal::Quit => write!(f, "SIGQUIT"),
2583 UnitTerminateSignal::Kill => write!(f, "SIGKILL"),
2584 }
2585 }
2586}
2587
2588#[cfg(test)]
2589mod tests {
2590 use super::*;
2591
2592 #[test]
2593 fn test_is_success() {
2594 assert_eq!(
2595 RunStats::default().summarize_final(),
2596 FinalRunStats::NoTestsRun,
2597 "empty run => no tests run"
2598 );
2599 assert_eq!(
2600 RunStats {
2601 initial_run_count: 42,
2602 finished_count: 42,
2603 ..RunStats::default()
2604 }
2605 .summarize_final(),
2606 FinalRunStats::Success,
2607 "initial run count = final run count => success"
2608 );
2609 assert_eq!(
2610 RunStats {
2611 initial_run_count: 42,
2612 finished_count: 41,
2613 ..RunStats::default()
2614 }
2615 .summarize_final(),
2616 FinalRunStats::Cancelled {
2617 reason: None,
2618 kind: RunStatsFailureKind::Test {
2619 initial_run_count: 42,
2620 not_run: 1
2621 }
2622 },
2623 "initial run count > final run count => cancelled"
2624 );
2625 assert_eq!(
2626 RunStats {
2627 initial_run_count: 42,
2628 finished_count: 42,
2629 failed: 1,
2630 ..RunStats::default()
2631 }
2632 .summarize_final(),
2633 FinalRunStats::Failed {
2634 kind: RunStatsFailureKind::Test {
2635 initial_run_count: 42,
2636 not_run: 0,
2637 },
2638 },
2639 "failed => failure"
2640 );
2641 assert_eq!(
2642 RunStats {
2643 initial_run_count: 42,
2644 finished_count: 42,
2645 exec_failed: 1,
2646 ..RunStats::default()
2647 }
2648 .summarize_final(),
2649 FinalRunStats::Failed {
2650 kind: RunStatsFailureKind::Test {
2651 initial_run_count: 42,
2652 not_run: 0,
2653 },
2654 },
2655 "exec failed => failure"
2656 );
2657 assert_eq!(
2658 RunStats {
2659 initial_run_count: 42,
2660 finished_count: 42,
2661 failed_timed_out: 1,
2662 ..RunStats::default()
2663 }
2664 .summarize_final(),
2665 FinalRunStats::Failed {
2666 kind: RunStatsFailureKind::Test {
2667 initial_run_count: 42,
2668 not_run: 0,
2669 },
2670 },
2671 "timed out => failure {:?} {:?}",
2672 RunStats {
2673 initial_run_count: 42,
2674 finished_count: 42,
2675 failed_timed_out: 1,
2676 ..RunStats::default()
2677 }
2678 .summarize_final(),
2679 FinalRunStats::Failed {
2680 kind: RunStatsFailureKind::Test {
2681 initial_run_count: 42,
2682 not_run: 0,
2683 },
2684 },
2685 );
2686 assert_eq!(
2687 RunStats {
2688 initial_run_count: 42,
2689 finished_count: 42,
2690 skipped: 1,
2691 ..RunStats::default()
2692 }
2693 .summarize_final(),
2694 FinalRunStats::Success,
2695 "skipped => not considered a failure"
2696 );
2697
2698 assert_eq!(
2699 RunStats {
2700 setup_scripts_initial_count: 2,
2701 setup_scripts_finished_count: 1,
2702 ..RunStats::default()
2703 }
2704 .summarize_final(),
2705 FinalRunStats::Cancelled {
2706 reason: None,
2707 kind: RunStatsFailureKind::SetupScript,
2708 },
2709 "setup script failed => failure"
2710 );
2711
2712 assert_eq!(
2713 RunStats {
2714 setup_scripts_initial_count: 2,
2715 setup_scripts_finished_count: 2,
2716 setup_scripts_failed: 1,
2717 ..RunStats::default()
2718 }
2719 .summarize_final(),
2720 FinalRunStats::Failed {
2721 kind: RunStatsFailureKind::SetupScript,
2722 },
2723 "setup script failed => failure"
2724 );
2725 assert_eq!(
2726 RunStats {
2727 setup_scripts_initial_count: 2,
2728 setup_scripts_finished_count: 2,
2729 setup_scripts_exec_failed: 1,
2730 ..RunStats::default()
2731 }
2732 .summarize_final(),
2733 FinalRunStats::Failed {
2734 kind: RunStatsFailureKind::SetupScript,
2735 },
2736 "setup script exec failed => failure"
2737 );
2738 assert_eq!(
2739 RunStats {
2740 setup_scripts_initial_count: 2,
2741 setup_scripts_finished_count: 2,
2742 setup_scripts_timed_out: 1,
2743 ..RunStats::default()
2744 }
2745 .summarize_final(),
2746 FinalRunStats::Failed {
2747 kind: RunStatsFailureKind::SetupScript,
2748 },
2749 "setup script timed out => failure"
2750 );
2751 assert_eq!(
2752 RunStats {
2753 setup_scripts_initial_count: 2,
2754 setup_scripts_finished_count: 2,
2755 setup_scripts_passed: 2,
2756 ..RunStats::default()
2757 }
2758 .summarize_final(),
2759 FinalRunStats::NoTestsRun,
2760 "setup scripts passed => success, but no tests run"
2761 );
2762
2763 }
2766
2767 fn make_execute_status(
2769 result: ExecutionResultDescription,
2770 attempt: u32,
2771 total_attempts: u32,
2772 ) -> ExecuteStatus<LiveSpec> {
2773 make_execute_status_slow(result, attempt, total_attempts, false)
2774 }
2775
2776 fn make_execute_status_slow(
2779 result: ExecutionResultDescription,
2780 attempt: u32,
2781 total_attempts: u32,
2782 is_slow: bool,
2783 ) -> ExecuteStatus<LiveSpec> {
2784 ExecuteStatus {
2785 retry_data: RetryData {
2786 attempt,
2787 total_attempts,
2788 },
2789 output: ChildExecutionOutputDescription::Output {
2790 result: Some(result.clone()),
2791 output: ChildOutputDescription::Split {
2792 stdout: None,
2793 stderr: None,
2794 },
2795 errors: None,
2796 },
2797 result,
2798 start_time: chrono::Utc::now().into(),
2799 time_taken: Duration::from_millis(100),
2800 is_slow,
2801 delay_before_start: Duration::ZERO,
2802 error_summary: None,
2803 output_error_slice: None,
2804 }
2805 }
2806
2807 #[test]
2808 fn is_success_for_output_by_variant() {
2809 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 1, 1);
2811 let success_statuses = ExecutionStatuses::new(vec![pass_status], FlakyResult::Pass);
2812 let describe = success_statuses.describe();
2813 assert!(
2814 matches!(describe, ExecutionDescription::Success { .. }),
2815 "single pass is Success"
2816 );
2817 assert!(
2818 describe.is_success_for_output(),
2819 "Success: output is success output"
2820 );
2821
2822 let fail_status = make_execute_status(
2824 ExecutionResultDescription::Fail {
2825 failure: FailureDescription::ExitCode { code: 1 },
2826 leaked: false,
2827 },
2828 1,
2829 2,
2830 );
2831 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 2, 2);
2832 let flaky_pass_statuses =
2833 ExecutionStatuses::new(vec![fail_status, pass_status], FlakyResult::Pass);
2834 let describe = flaky_pass_statuses.describe();
2835 assert!(
2836 matches!(
2837 describe,
2838 ExecutionDescription::Flaky {
2839 result: FlakyResult::Pass,
2840 ..
2841 }
2842 ),
2843 "fail then pass with FlakyResult::Pass is Flaky Pass"
2844 );
2845 assert!(
2846 describe.is_success_for_output(),
2847 "Flaky pass: output is success output"
2848 );
2849
2850 let fail_status = make_execute_status(
2852 ExecutionResultDescription::Fail {
2853 failure: FailureDescription::ExitCode { code: 1 },
2854 leaked: false,
2855 },
2856 1,
2857 2,
2858 );
2859 let pass_status = make_execute_status(ExecutionResultDescription::Pass, 2, 2);
2860 let flaky_fail_statuses =
2861 ExecutionStatuses::new(vec![fail_status, pass_status], FlakyResult::Fail);
2862 let describe = flaky_fail_statuses.describe();
2863 assert!(
2864 matches!(
2865 describe,
2866 ExecutionDescription::Flaky {
2867 result: FlakyResult::Fail,
2868 ..
2869 }
2870 ),
2871 "fail then pass with FlakyResult::Fail is Flaky Fail"
2872 );
2873 assert!(
2874 describe.is_success_for_output(),
2875 "Flaky fail: output is still success output (last attempt passed)"
2876 );
2877
2878 let fail_status = make_execute_status(
2880 ExecutionResultDescription::Fail {
2881 failure: FailureDescription::ExitCode { code: 1 },
2882 leaked: false,
2883 },
2884 1,
2885 1,
2886 );
2887 let failure_statuses = ExecutionStatuses::new(vec![fail_status], FlakyResult::Pass);
2888 let describe = failure_statuses.describe();
2889 assert!(
2890 matches!(describe, ExecutionDescription::Failure { .. }),
2891 "single fail is Failure"
2892 );
2893 assert!(
2894 !describe.is_success_for_output(),
2895 "Failure: output is not success output"
2896 );
2897
2898 let fail1 = make_execute_status(
2900 ExecutionResultDescription::Fail {
2901 failure: FailureDescription::ExitCode { code: 1 },
2902 leaked: false,
2903 },
2904 1,
2905 2,
2906 );
2907 let fail2 = make_execute_status(
2908 ExecutionResultDescription::Fail {
2909 failure: FailureDescription::ExitCode { code: 1 },
2910 leaked: false,
2911 },
2912 2,
2913 2,
2914 );
2915 let failure_retry_statuses = ExecutionStatuses::new(vec![fail1, fail2], FlakyResult::Pass);
2916 let describe = failure_retry_statuses.describe();
2917 assert!(
2918 matches!(describe, ExecutionDescription::Failure { .. }),
2919 "all-fail with retries is Failure"
2920 );
2921 assert!(
2922 !describe.is_success_for_output(),
2923 "Failure with retries: output is not success output"
2924 );
2925 }
2926
2927 #[test]
2928 fn abort_description_serialization() {
2929 let unix_with_name = AbortDescription::UnixSignal {
2931 signal: 15,
2932 name: Some("TERM".into()),
2933 };
2934 let json = serde_json::to_string_pretty(&unix_with_name).unwrap();
2935 insta::assert_snapshot!("abort_unix_signal_with_name", json);
2936 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2937 assert_eq!(unix_with_name, roundtrip);
2938
2939 let unix_no_name = AbortDescription::UnixSignal {
2941 signal: 42,
2942 name: None,
2943 };
2944 let json = serde_json::to_string_pretty(&unix_no_name).unwrap();
2945 insta::assert_snapshot!("abort_unix_signal_no_name", json);
2946 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2947 assert_eq!(unix_no_name, roundtrip);
2948
2949 let windows_nt = AbortDescription::WindowsNtStatus {
2951 code: -1073741510_i32,
2952 message: Some("The application terminated as a result of a CTRL+C.".into()),
2953 };
2954 let json = serde_json::to_string_pretty(&windows_nt).unwrap();
2955 insta::assert_snapshot!("abort_windows_nt_status", json);
2956 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2957 assert_eq!(windows_nt, roundtrip);
2958
2959 let windows_nt_no_msg = AbortDescription::WindowsNtStatus {
2961 code: -1073741819_i32,
2962 message: None,
2963 };
2964 let json = serde_json::to_string_pretty(&windows_nt_no_msg).unwrap();
2965 insta::assert_snapshot!("abort_windows_nt_status_no_message", json);
2966 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2967 assert_eq!(windows_nt_no_msg, roundtrip);
2968
2969 let job = AbortDescription::WindowsJobObject;
2971 let json = serde_json::to_string_pretty(&job).unwrap();
2972 insta::assert_snapshot!("abort_windows_job_object", json);
2973 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2974 assert_eq!(job, roundtrip);
2975 }
2976
2977 #[test]
2978 fn abort_description_cross_platform_deserialization() {
2979 let unix_json = r#"{"kind":"unix-signal","signal":11,"name":"SEGV"}"#;
2982 let unix_desc: AbortDescription = serde_json::from_str(unix_json).unwrap();
2983 assert_eq!(
2984 unix_desc,
2985 AbortDescription::UnixSignal {
2986 signal: 11,
2987 name: Some("SEGV".into()),
2988 }
2989 );
2990
2991 let windows_json = r#"{"kind":"windows-nt-status","code":-1073741510,"message":"CTRL+C"}"#;
2992 let windows_desc: AbortDescription = serde_json::from_str(windows_json).unwrap();
2993 assert_eq!(
2994 windows_desc,
2995 AbortDescription::WindowsNtStatus {
2996 code: -1073741510,
2997 message: Some("CTRL+C".into()),
2998 }
2999 );
3000
3001 let job_json = r#"{"kind":"windows-job-object"}"#;
3002 let job_desc: AbortDescription = serde_json::from_str(job_json).unwrap();
3003 assert_eq!(job_desc, AbortDescription::WindowsJobObject);
3004 }
3005
3006 #[test]
3007 fn abort_description_display() {
3008 let unix = AbortDescription::UnixSignal {
3010 signal: 15,
3011 name: Some("TERM".into()),
3012 };
3013 assert_eq!(unix.to_string(), "aborted with signal 15 (SIGTERM)");
3014
3015 let unix_no_name = AbortDescription::UnixSignal {
3017 signal: 42,
3018 name: None,
3019 };
3020 assert_eq!(unix_no_name.to_string(), "aborted with signal 42");
3021
3022 let windows = AbortDescription::WindowsNtStatus {
3024 code: -1073741510,
3025 message: Some("CTRL+C exit".into()),
3026 };
3027 assert_eq!(
3028 windows.to_string(),
3029 "aborted with code 0xc000013a: CTRL+C exit"
3030 );
3031
3032 let windows_no_msg = AbortDescription::WindowsNtStatus {
3034 code: -1073741510,
3035 message: None,
3036 };
3037 assert_eq!(windows_no_msg.to_string(), "aborted with code 0xc000013a");
3038
3039 let job = AbortDescription::WindowsJobObject;
3041 assert_eq!(job.to_string(), "terminated via job object");
3042 }
3043
3044 #[cfg(unix)]
3045 #[test]
3046 fn abort_description_from_abort_status() {
3047 let status = AbortStatus::UnixSignal(15);
3049 let description = AbortDescription::from(status);
3050
3051 assert_eq!(
3052 description,
3053 AbortDescription::UnixSignal {
3054 signal: 15,
3055 name: Some("TERM".into()),
3056 }
3057 );
3058
3059 let unknown_status = AbortStatus::UnixSignal(42);
3061 let unknown_description = AbortDescription::from(unknown_status);
3062 assert_eq!(
3063 unknown_description,
3064 AbortDescription::UnixSignal {
3065 signal: 42,
3066 name: None,
3067 }
3068 );
3069 }
3070
3071 #[test]
3072 fn execution_result_description_serialization() {
3073 let pass = ExecutionResultDescription::Pass;
3077 let json = serde_json::to_string_pretty(&pass).unwrap();
3078 insta::assert_snapshot!("pass", json);
3079 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3080 assert_eq!(pass, roundtrip);
3081
3082 let leak_pass = ExecutionResultDescription::Leak {
3084 result: LeakTimeoutResult::Pass,
3085 };
3086 let json = serde_json::to_string_pretty(&leak_pass).unwrap();
3087 insta::assert_snapshot!("leak_pass", json);
3088 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3089 assert_eq!(leak_pass, roundtrip);
3090
3091 let leak_fail = ExecutionResultDescription::Leak {
3093 result: LeakTimeoutResult::Fail,
3094 };
3095 let json = serde_json::to_string_pretty(&leak_fail).unwrap();
3096 insta::assert_snapshot!("leak_fail", json);
3097 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3098 assert_eq!(leak_fail, roundtrip);
3099
3100 let fail_exit_code = ExecutionResultDescription::Fail {
3102 failure: FailureDescription::ExitCode { code: 101 },
3103 leaked: false,
3104 };
3105 let json = serde_json::to_string_pretty(&fail_exit_code).unwrap();
3106 insta::assert_snapshot!("fail_exit_code", json);
3107 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3108 assert_eq!(fail_exit_code, roundtrip);
3109
3110 let fail_exit_code_leaked = ExecutionResultDescription::Fail {
3112 failure: FailureDescription::ExitCode { code: 1 },
3113 leaked: true,
3114 };
3115 let json = serde_json::to_string_pretty(&fail_exit_code_leaked).unwrap();
3116 insta::assert_snapshot!("fail_exit_code_leaked", json);
3117 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3118 assert_eq!(fail_exit_code_leaked, roundtrip);
3119
3120 let fail_unix_signal = ExecutionResultDescription::Fail {
3122 failure: FailureDescription::Abort {
3123 abort: AbortDescription::UnixSignal {
3124 signal: 11,
3125 name: Some("SEGV".into()),
3126 },
3127 },
3128 leaked: false,
3129 };
3130 let json = serde_json::to_string_pretty(&fail_unix_signal).unwrap();
3131 insta::assert_snapshot!("fail_unix_signal", json);
3132 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3133 assert_eq!(fail_unix_signal, roundtrip);
3134
3135 let fail_unix_signal_unknown = ExecutionResultDescription::Fail {
3137 failure: FailureDescription::Abort {
3138 abort: AbortDescription::UnixSignal {
3139 signal: 42,
3140 name: None,
3141 },
3142 },
3143 leaked: true,
3144 };
3145 let json = serde_json::to_string_pretty(&fail_unix_signal_unknown).unwrap();
3146 insta::assert_snapshot!("fail_unix_signal_unknown_leaked", json);
3147 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3148 assert_eq!(fail_unix_signal_unknown, roundtrip);
3149
3150 let fail_windows_nt = ExecutionResultDescription::Fail {
3152 failure: FailureDescription::Abort {
3153 abort: AbortDescription::WindowsNtStatus {
3154 code: -1073741510,
3155 message: Some("The application terminated as a result of a CTRL+C.".into()),
3156 },
3157 },
3158 leaked: false,
3159 };
3160 let json = serde_json::to_string_pretty(&fail_windows_nt).unwrap();
3161 insta::assert_snapshot!("fail_windows_nt_status", json);
3162 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3163 assert_eq!(fail_windows_nt, roundtrip);
3164
3165 let fail_windows_nt_no_msg = ExecutionResultDescription::Fail {
3167 failure: FailureDescription::Abort {
3168 abort: AbortDescription::WindowsNtStatus {
3169 code: -1073741819,
3170 message: None,
3171 },
3172 },
3173 leaked: false,
3174 };
3175 let json = serde_json::to_string_pretty(&fail_windows_nt_no_msg).unwrap();
3176 insta::assert_snapshot!("fail_windows_nt_status_no_message", json);
3177 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3178 assert_eq!(fail_windows_nt_no_msg, roundtrip);
3179
3180 let fail_job_object = ExecutionResultDescription::Fail {
3182 failure: FailureDescription::Abort {
3183 abort: AbortDescription::WindowsJobObject,
3184 },
3185 leaked: false,
3186 };
3187 let json = serde_json::to_string_pretty(&fail_job_object).unwrap();
3188 insta::assert_snapshot!("fail_windows_job_object", json);
3189 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3190 assert_eq!(fail_job_object, roundtrip);
3191
3192 let exec_fail = ExecutionResultDescription::ExecFail;
3194 let json = serde_json::to_string_pretty(&exec_fail).unwrap();
3195 insta::assert_snapshot!("exec_fail", json);
3196 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3197 assert_eq!(exec_fail, roundtrip);
3198
3199 let timeout_pass = ExecutionResultDescription::Timeout {
3201 result: SlowTimeoutResult::Pass,
3202 };
3203 let json = serde_json::to_string_pretty(&timeout_pass).unwrap();
3204 insta::assert_snapshot!("timeout_pass", json);
3205 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3206 assert_eq!(timeout_pass, roundtrip);
3207
3208 let timeout_fail = ExecutionResultDescription::Timeout {
3210 result: SlowTimeoutResult::Fail,
3211 };
3212 let json = serde_json::to_string_pretty(&timeout_fail).unwrap();
3213 insta::assert_snapshot!("timeout_fail", json);
3214 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
3215 assert_eq!(timeout_fail, roundtrip);
3216 }
3217
3218 fn make_flaky_statuses(
3223 pass_result: ExecutionResultDescription,
3224 flaky_result: FlakyResult,
3225 is_slow: bool,
3226 ) -> ExecutionStatuses<LiveSpec> {
3227 let fail = make_execute_status(
3228 ExecutionResultDescription::Fail {
3229 failure: FailureDescription::ExitCode { code: 1 },
3230 leaked: false,
3231 },
3232 1,
3233 2,
3234 );
3235 let pass = make_execute_status_slow(pass_result, 2, 2, is_slow);
3236 ExecutionStatuses::new(vec![fail, pass], flaky_result)
3237 }
3238
3239 fn run_on_test_finished(statuses: &ExecutionStatuses<LiveSpec>) -> RunStats {
3241 let mut stats = RunStats {
3242 initial_run_count: 1,
3243 ..RunStats::default()
3244 };
3245 stats.on_test_finished(statuses);
3246 stats
3247 }
3248
3249 #[test]
3250 fn on_test_finished_pass_flaky() {
3251 let stats = run_on_test_finished(&make_flaky_statuses(
3253 ExecutionResultDescription::Pass,
3254 FlakyResult::Fail,
3255 false,
3256 ));
3257 assert_eq!(stats.finished_count, 1);
3258 assert_eq!(stats.failed, 1);
3259 assert_eq!(stats.failed_slow, 0, "not slow");
3260 assert_eq!(stats.passed, 0);
3261 assert_eq!(stats.flaky, 0);
3262
3263 let stats = run_on_test_finished(&make_flaky_statuses(
3265 ExecutionResultDescription::Pass,
3266 FlakyResult::Fail,
3267 true,
3268 ));
3269 assert_eq!(stats.failed, 1);
3270 assert_eq!(stats.failed_slow, 1);
3271 assert_eq!(stats.passed, 0);
3272 assert_eq!(stats.flaky, 0);
3273
3274 let stats = run_on_test_finished(&make_flaky_statuses(
3276 ExecutionResultDescription::Pass,
3277 FlakyResult::Pass,
3278 true,
3279 ));
3280 assert_eq!(stats.passed, 1);
3281 assert_eq!(stats.passed_slow, 1);
3282 assert_eq!(stats.flaky, 1);
3283 assert_eq!(stats.failed, 0);
3284 }
3285
3286 #[test]
3287 fn on_test_finished_leak_pass_flaky() {
3288 let stats = run_on_test_finished(&make_flaky_statuses(
3290 ExecutionResultDescription::Leak {
3291 result: LeakTimeoutResult::Pass,
3292 },
3293 FlakyResult::Fail,
3294 false,
3295 ));
3296 assert_eq!(stats.failed, 1);
3297 assert_eq!(stats.failed_slow, 0, "not slow");
3298 assert_eq!(stats.leaky, 1, "leak still tracked");
3299 assert_eq!(stats.passed, 0);
3300 assert_eq!(stats.flaky, 0);
3301
3302 let stats = run_on_test_finished(&make_flaky_statuses(
3304 ExecutionResultDescription::Leak {
3305 result: LeakTimeoutResult::Pass,
3306 },
3307 FlakyResult::Fail,
3308 true,
3309 ));
3310 assert_eq!(stats.failed, 1);
3311 assert_eq!(stats.failed_slow, 1);
3312 assert_eq!(stats.leaky, 1);
3313 assert_eq!(stats.passed, 0);
3314 assert_eq!(stats.flaky, 0);
3315
3316 let stats = run_on_test_finished(&make_flaky_statuses(
3318 ExecutionResultDescription::Leak {
3319 result: LeakTimeoutResult::Pass,
3320 },
3321 FlakyResult::Pass,
3322 true,
3323 ));
3324 assert_eq!(stats.passed, 1);
3325 assert_eq!(stats.passed_slow, 1);
3326 assert_eq!(stats.leaky, 1);
3327 assert_eq!(stats.flaky, 1);
3328 assert_eq!(stats.failed, 0);
3329 }
3330
3331 #[test]
3332 fn on_test_finished_timeout_pass_flaky() {
3333 let stats = run_on_test_finished(&make_flaky_statuses(
3336 ExecutionResultDescription::Timeout {
3337 result: SlowTimeoutResult::Pass,
3338 },
3339 FlakyResult::Fail,
3340 true,
3341 ));
3342 assert_eq!(stats.failed, 1);
3343 assert_eq!(stats.failed_slow, 1);
3344 assert_eq!(stats.passed, 0);
3345 assert_eq!(stats.passed_timed_out, 0);
3346 assert_eq!(stats.flaky, 0);
3347
3348 let stats = run_on_test_finished(&make_flaky_statuses(
3350 ExecutionResultDescription::Timeout {
3351 result: SlowTimeoutResult::Pass,
3352 },
3353 FlakyResult::Pass,
3354 false,
3355 ));
3356 assert_eq!(stats.passed, 1);
3357 assert_eq!(stats.passed_timed_out, 1);
3358 assert_eq!(stats.flaky, 1);
3359 assert_eq!(stats.failed, 0);
3360 }
3361
3362 #[test]
3363 fn on_test_finished_non_flaky() {
3364 let pass = make_execute_status_slow(ExecutionResultDescription::Pass, 1, 1, true);
3366 let stats = run_on_test_finished(&ExecutionStatuses::new(vec![pass], FlakyResult::Pass));
3367 assert_eq!(stats.passed, 1);
3368 assert_eq!(stats.passed_slow, 1);
3369 assert_eq!(stats.flaky, 0);
3370 assert_eq!(stats.failed, 0);
3371
3372 let fail = make_execute_status_slow(
3374 ExecutionResultDescription::Fail {
3375 failure: FailureDescription::ExitCode { code: 1 },
3376 leaked: false,
3377 },
3378 1,
3379 1,
3380 true,
3381 );
3382 let stats = run_on_test_finished(&ExecutionStatuses::new(vec![fail], FlakyResult::Pass));
3383 assert_eq!(stats.failed, 1);
3384 assert_eq!(stats.failed_slow, 1);
3385 assert_eq!(stats.passed, 0);
3386 assert_eq!(stats.flaky, 0);
3387 }
3388}