1use super::{FinalStatusLevel, StatusLevel, TestOutputDisplay};
10use crate::{
11 config::{
12 elements::{LeakTimeoutResult, SlowTimeoutResult},
13 scripts::ScriptId,
14 },
15 list::{TestInstanceId, TestList},
16 runner::{StressCondition, StressCount},
17 test_output::ChildExecutionOutput,
18};
19use chrono::{DateTime, FixedOffset};
20use nextest_metadata::MismatchReason;
21use quick_junit::ReportUuid;
22use serde::{Deserialize, Serialize};
23use smol_str::SmolStr;
24use std::{
25 collections::BTreeMap, ffi::c_int, fmt, num::NonZero, process::ExitStatus, time::Duration,
26};
27
28pub const SIGTERM: c_int = 15;
33
34#[derive(Clone, Debug)]
36pub enum ReporterEvent<'a> {
37 Tick,
39
40 Test(Box<TestEvent<'a>>),
42}
43#[derive(Clone, Debug)]
48pub struct TestEvent<'a> {
49 pub timestamp: DateTime<FixedOffset>,
51
52 pub elapsed: Duration,
54
55 pub kind: TestEventKind<'a>,
57}
58
59#[derive(Clone, Debug)]
63pub enum TestEventKind<'a> {
64 RunStarted {
66 test_list: &'a TestList<'a>,
70
71 run_id: ReportUuid,
73
74 profile_name: String,
76
77 cli_args: Vec<String>,
79
80 stress_condition: Option<StressCondition>,
82 },
83
84 StressSubRunStarted {
86 progress: StressProgress,
88 },
89
90 SetupScriptStarted {
92 stress_index: Option<StressIndex>,
94
95 index: usize,
97
98 total: usize,
100
101 script_id: ScriptId,
103
104 program: String,
106
107 args: &'a [String],
109
110 no_capture: bool,
112 },
113
114 SetupScriptSlow {
116 stress_index: Option<StressIndex>,
118
119 script_id: ScriptId,
121
122 program: String,
124
125 args: &'a [String],
127
128 elapsed: Duration,
130
131 will_terminate: bool,
133 },
134
135 SetupScriptFinished {
137 stress_index: Option<StressIndex>,
139
140 index: usize,
142
143 total: usize,
145
146 script_id: ScriptId,
148
149 program: String,
151
152 args: &'a [String],
154
155 junit_store_success_output: bool,
157
158 junit_store_failure_output: bool,
160
161 no_capture: bool,
163
164 run_status: SetupScriptExecuteStatus,
166 },
167
168 TestStarted {
173 stress_index: Option<StressIndex>,
175
176 test_instance: TestInstanceId<'a>,
178
179 current_stats: RunStats,
181
182 running: usize,
184
185 command_line: Vec<String>,
187 },
188
189 TestSlow {
191 stress_index: Option<StressIndex>,
193
194 test_instance: TestInstanceId<'a>,
196
197 retry_data: RetryData,
199
200 elapsed: Duration,
202
203 will_terminate: bool,
205 },
206
207 TestAttemptFailedWillRetry {
211 stress_index: Option<StressIndex>,
213
214 test_instance: TestInstanceId<'a>,
216
217 run_status: ExecuteStatus,
219
220 delay_before_next_attempt: Duration,
222
223 failure_output: TestOutputDisplay,
225
226 running: usize,
228 },
229
230 TestRetryStarted {
232 stress_index: Option<StressIndex>,
234
235 test_instance: TestInstanceId<'a>,
237
238 retry_data: RetryData,
240
241 running: usize,
243
244 command_line: Vec<String>,
246 },
247
248 TestFinished {
250 stress_index: Option<StressIndex>,
252
253 test_instance: TestInstanceId<'a>,
255
256 success_output: TestOutputDisplay,
258
259 failure_output: TestOutputDisplay,
261
262 junit_store_success_output: bool,
264
265 junit_store_failure_output: bool,
267
268 run_statuses: ExecutionStatuses,
270
271 current_stats: RunStats,
273
274 running: usize,
276 },
277
278 TestSkipped {
280 stress_index: Option<StressIndex>,
282
283 test_instance: TestInstanceId<'a>,
285
286 reason: MismatchReason,
288 },
289
290 InfoStarted {
292 total: usize,
295
296 run_stats: RunStats,
298 },
299
300 InfoResponse {
302 index: usize,
304
305 total: usize,
307
308 response: InfoResponse<'a>,
310 },
311
312 InfoFinished {
314 missing: usize,
317 },
318
319 InputEnter {
322 current_stats: RunStats,
324
325 running: usize,
327 },
328
329 RunBeginCancel {
331 setup_scripts_running: usize,
333
334 current_stats: RunStats,
338
339 running: usize,
341 },
342
343 RunBeginKill {
345 setup_scripts_running: usize,
347
348 current_stats: RunStats,
352
353 running: usize,
355 },
356
357 RunPaused {
359 setup_scripts_running: usize,
361
362 running: usize,
364 },
365
366 RunContinued {
368 setup_scripts_running: usize,
370
371 running: usize,
373 },
374
375 StressSubRunFinished {
377 progress: StressProgress,
379
380 sub_elapsed: Duration,
382
383 sub_stats: RunStats,
385 },
386
387 RunFinished {
389 run_id: ReportUuid,
391
392 start_time: DateTime<FixedOffset>,
394
395 elapsed: Duration,
397
398 run_stats: RunFinishedStats,
400 },
401}
402
403#[derive(Clone, Debug)]
405pub enum StressProgress {
406 Count {
408 total: StressCount,
410
411 elapsed: Duration,
413
414 completed: u32,
416 },
417
418 Time {
420 total: Duration,
422
423 elapsed: Duration,
425
426 completed: u32,
428 },
429}
430
431impl StressProgress {
432 pub fn remaining(&self) -> Option<StressRemaining> {
435 match self {
436 Self::Count {
437 total: StressCount::Count(total),
438 elapsed: _,
439 completed,
440 } => total
441 .get()
442 .checked_sub(*completed)
443 .and_then(|remaining| NonZero::try_from(remaining).ok())
444 .map(StressRemaining::Count),
445 Self::Count {
446 total: StressCount::Infinite,
447 ..
448 } => Some(StressRemaining::Infinite),
449 Self::Time {
450 total,
451 elapsed,
452 completed: _,
453 } => total.checked_sub(*elapsed).map(StressRemaining::Time),
454 }
455 }
456
457 pub fn unique_id(&self, run_id: ReportUuid) -> String {
459 let stress_current = match self {
460 Self::Count { completed, .. } | Self::Time { completed, .. } => *completed,
461 };
462 format!("{}:@stress-{}", run_id, stress_current)
463 }
464}
465
466#[derive(Clone, Debug)]
468pub enum StressRemaining {
469 Count(NonZero<u32>),
471
472 Infinite,
474
475 Time(Duration),
477}
478
479#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
481pub struct StressIndex {
482 pub current: u32,
484
485 pub total: Option<NonZero<u32>>,
487}
488
489#[derive(Clone, Debug)]
491pub enum RunFinishedStats {
492 Single(RunStats),
494
495 Stress(StressRunStats),
497}
498
499impl RunFinishedStats {
500 pub fn final_stats(&self) -> FinalRunStats {
503 match self {
504 Self::Single(stats) => stats.summarize_final(),
505 Self::Stress(stats) => stats.last_final_stats,
506 }
507 }
508}
509
510#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
512pub struct RunStats {
513 pub initial_run_count: usize,
517
518 pub finished_count: usize,
520
521 pub setup_scripts_initial_count: usize,
525
526 pub setup_scripts_finished_count: usize,
528
529 pub setup_scripts_passed: usize,
531
532 pub setup_scripts_failed: usize,
534
535 pub setup_scripts_exec_failed: usize,
537
538 pub setup_scripts_timed_out: usize,
540
541 pub passed: usize,
543
544 pub passed_slow: usize,
546
547 pub passed_timed_out: usize,
549
550 pub flaky: usize,
552
553 pub failed: usize,
555
556 pub failed_slow: usize,
558
559 pub failed_timed_out: usize,
561
562 pub leaky: usize,
564
565 pub leaky_failed: usize,
568
569 pub exec_failed: usize,
571
572 pub skipped: usize,
574
575 pub cancel_reason: Option<CancelReason>,
577}
578
579impl RunStats {
580 pub fn has_failures(&self) -> bool {
582 self.failed_setup_script_count() > 0 || self.failed_count() > 0
583 }
584
585 pub fn failed_setup_script_count(&self) -> usize {
587 self.setup_scripts_failed + self.setup_scripts_exec_failed + self.setup_scripts_timed_out
588 }
589
590 pub fn failed_count(&self) -> usize {
592 self.failed + self.exec_failed + self.failed_timed_out
593 }
594
595 pub fn summarize_final(&self) -> FinalRunStats {
597 if self.failed_setup_script_count() > 0 {
600 if self.cancel_reason > Some(CancelReason::TestFailure) {
603 FinalRunStats::Cancelled {
604 reason: self.cancel_reason,
605 kind: RunStatsFailureKind::SetupScript,
606 }
607 } else {
608 FinalRunStats::Failed(RunStatsFailureKind::SetupScript)
609 }
610 } else if self.setup_scripts_initial_count > self.setup_scripts_finished_count {
611 FinalRunStats::Cancelled {
612 reason: self.cancel_reason,
613 kind: RunStatsFailureKind::SetupScript,
614 }
615 } else if self.failed_count() > 0 {
616 let kind = RunStatsFailureKind::Test {
617 initial_run_count: self.initial_run_count,
618 not_run: self.initial_run_count.saturating_sub(self.finished_count),
619 };
620
621 if self.cancel_reason > Some(CancelReason::TestFailure) {
624 FinalRunStats::Cancelled {
625 reason: self.cancel_reason,
626 kind,
627 }
628 } else {
629 FinalRunStats::Failed(kind)
630 }
631 } else if self.initial_run_count > self.finished_count {
632 FinalRunStats::Cancelled {
633 reason: self.cancel_reason,
634 kind: RunStatsFailureKind::Test {
635 initial_run_count: self.initial_run_count,
636 not_run: self.initial_run_count.saturating_sub(self.finished_count),
637 },
638 }
639 } else if self.finished_count == 0 {
640 FinalRunStats::NoTestsRun
641 } else {
642 FinalRunStats::Success
643 }
644 }
645
646 pub(crate) fn on_setup_script_finished(&mut self, status: &SetupScriptExecuteStatus) {
647 self.setup_scripts_finished_count += 1;
648
649 match status.result {
650 ExecutionResultDescription::Pass
651 | ExecutionResultDescription::Leak {
652 result: LeakTimeoutResult::Pass,
653 } => {
654 self.setup_scripts_passed += 1;
655 }
656 ExecutionResultDescription::Fail { .. }
657 | ExecutionResultDescription::Leak {
658 result: LeakTimeoutResult::Fail,
659 } => {
660 self.setup_scripts_failed += 1;
661 }
662 ExecutionResultDescription::ExecFail => {
663 self.setup_scripts_exec_failed += 1;
664 }
665 ExecutionResultDescription::Timeout { .. } => {
667 self.setup_scripts_timed_out += 1;
668 }
669 }
670 }
671
672 pub(crate) fn on_test_finished(&mut self, run_statuses: &ExecutionStatuses) {
673 self.finished_count += 1;
674 let last_status = run_statuses.last_status();
683 match last_status.result {
684 ExecutionResultDescription::Pass => {
685 self.passed += 1;
686 if last_status.is_slow {
687 self.passed_slow += 1;
688 }
689 if run_statuses.len() > 1 {
690 self.flaky += 1;
691 }
692 }
693 ExecutionResultDescription::Leak {
694 result: LeakTimeoutResult::Pass,
695 } => {
696 self.passed += 1;
697 self.leaky += 1;
698 if last_status.is_slow {
699 self.passed_slow += 1;
700 }
701 if run_statuses.len() > 1 {
702 self.flaky += 1;
703 }
704 }
705 ExecutionResultDescription::Leak {
706 result: LeakTimeoutResult::Fail,
707 } => {
708 self.failed += 1;
709 self.leaky_failed += 1;
710 if last_status.is_slow {
711 self.failed_slow += 1;
712 }
713 }
714 ExecutionResultDescription::Fail { .. } => {
715 self.failed += 1;
716 if last_status.is_slow {
717 self.failed_slow += 1;
718 }
719 }
720 ExecutionResultDescription::Timeout {
721 result: SlowTimeoutResult::Pass,
722 } => {
723 self.passed += 1;
724 self.passed_timed_out += 1;
725 if run_statuses.len() > 1 {
726 self.flaky += 1;
727 }
728 }
729 ExecutionResultDescription::Timeout {
730 result: SlowTimeoutResult::Fail,
731 } => {
732 self.failed_timed_out += 1;
733 }
734 ExecutionResultDescription::ExecFail => self.exec_failed += 1,
735 }
736 }
737}
738
739#[derive(Copy, Clone, Debug, Eq, PartialEq)]
741pub enum FinalRunStats {
742 Success,
744
745 NoTestsRun,
747
748 Cancelled {
750 reason: Option<CancelReason>,
755
756 kind: RunStatsFailureKind,
758 },
759
760 Failed(RunStatsFailureKind),
762}
763
764#[derive(Clone, Debug)]
766pub struct StressRunStats {
767 pub completed: StressIndex,
769
770 pub success_count: u32,
772
773 pub failed_count: u32,
775
776 pub last_final_stats: FinalRunStats,
778}
779
780impl StressRunStats {
781 pub fn summarize_final(&self) -> StressFinalRunStats {
783 if self.failed_count > 0 {
784 StressFinalRunStats::Failed
785 } else if matches!(self.last_final_stats, FinalRunStats::Cancelled { .. }) {
786 StressFinalRunStats::Cancelled
787 } else if matches!(self.last_final_stats, FinalRunStats::NoTestsRun) {
788 StressFinalRunStats::NoTestsRun
789 } else {
790 StressFinalRunStats::Success
791 }
792 }
793}
794
795pub enum StressFinalRunStats {
797 Success,
799
800 NoTestsRun,
802
803 Cancelled,
805
806 Failed,
808}
809
810#[derive(Copy, Clone, Debug, Eq, PartialEq)]
812pub enum RunStatsFailureKind {
813 SetupScript,
815
816 Test {
818 initial_run_count: usize,
820
821 not_run: usize,
824 },
825}
826
827#[derive(Clone, Debug)]
829pub struct ExecutionStatuses {
830 statuses: Vec<ExecuteStatus>,
832}
833
834#[expect(clippy::len_without_is_empty)] impl ExecutionStatuses {
836 pub(crate) fn new(statuses: Vec<ExecuteStatus>) -> Self {
837 Self { statuses }
838 }
839
840 pub fn last_status(&self) -> &ExecuteStatus {
844 self.statuses
845 .last()
846 .expect("execution statuses is non-empty")
847 }
848
849 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &'_ ExecuteStatus> + '_ {
851 self.statuses.iter()
852 }
853
854 pub fn len(&self) -> usize {
856 self.statuses.len()
857 }
858
859 pub fn describe(&self) -> ExecutionDescription<'_> {
861 let last_status = self.last_status();
862 if last_status.result.is_success() {
863 if self.statuses.len() > 1 {
864 ExecutionDescription::Flaky {
865 last_status,
866 prior_statuses: &self.statuses[..self.statuses.len() - 1],
867 }
868 } else {
869 ExecutionDescription::Success {
870 single_status: last_status,
871 }
872 }
873 } else {
874 let first_status = self
875 .statuses
876 .first()
877 .expect("execution statuses is non-empty");
878 let retries = &self.statuses[1..];
879 ExecutionDescription::Failure {
880 first_status,
881 last_status,
882 retries,
883 }
884 }
885 }
886}
887
888#[derive(Copy, Clone, Debug)]
892pub enum ExecutionDescription<'a> {
893 Success {
895 single_status: &'a ExecuteStatus,
897 },
898
899 Flaky {
901 last_status: &'a ExecuteStatus,
903
904 prior_statuses: &'a [ExecuteStatus],
906 },
907
908 Failure {
910 first_status: &'a ExecuteStatus,
912
913 last_status: &'a ExecuteStatus,
915
916 retries: &'a [ExecuteStatus],
920 },
921}
922
923impl<'a> ExecutionDescription<'a> {
924 pub fn status_level(&self) -> StatusLevel {
926 match self {
927 ExecutionDescription::Success { single_status } => match single_status.result {
928 ExecutionResultDescription::Leak {
929 result: LeakTimeoutResult::Pass,
930 } => StatusLevel::Leak,
931 ExecutionResultDescription::Pass => StatusLevel::Pass,
932 ExecutionResultDescription::Timeout {
933 result: SlowTimeoutResult::Pass,
934 } => StatusLevel::Slow,
935 ref other => unreachable!(
936 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
937 ),
938 },
939 ExecutionDescription::Flaky { .. } => StatusLevel::Retry,
941 ExecutionDescription::Failure { .. } => StatusLevel::Fail,
942 }
943 }
944
945 pub fn final_status_level(&self) -> FinalStatusLevel {
947 match self {
948 ExecutionDescription::Success { single_status, .. } => {
949 if single_status.is_slow {
951 FinalStatusLevel::Slow
952 } else {
953 match single_status.result {
954 ExecutionResultDescription::Pass => FinalStatusLevel::Pass,
955 ExecutionResultDescription::Leak {
956 result: LeakTimeoutResult::Pass,
957 } => FinalStatusLevel::Leak,
958 ExecutionResultDescription::Timeout {
962 result: SlowTimeoutResult::Pass,
963 } => FinalStatusLevel::Slow,
964 ref other => unreachable!(
965 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
966 ),
967 }
968 }
969 }
970 ExecutionDescription::Flaky { .. } => FinalStatusLevel::Flaky,
972 ExecutionDescription::Failure { .. } => FinalStatusLevel::Fail,
973 }
974 }
975
976 pub fn last_status(&self) -> &'a ExecuteStatus {
978 match self {
979 ExecutionDescription::Success {
980 single_status: last_status,
981 }
982 | ExecutionDescription::Flaky { last_status, .. }
983 | ExecutionDescription::Failure { last_status, .. } => last_status,
984 }
985 }
986}
987
988#[derive(Clone, Debug)]
994pub struct ExecuteStatus {
995 pub retry_data: RetryData,
997 pub output: ChildExecutionOutput,
999 pub result: ExecutionResultDescription,
1001 pub start_time: DateTime<FixedOffset>,
1003 pub time_taken: Duration,
1005 pub is_slow: bool,
1007 pub delay_before_start: Duration,
1009}
1010
1011#[derive(Clone, Debug)]
1017pub struct SetupScriptExecuteStatus {
1018 pub output: ChildExecutionOutput,
1020
1021 pub result: ExecutionResultDescription,
1023
1024 pub start_time: DateTime<FixedOffset>,
1026
1027 pub time_taken: Duration,
1029
1030 pub is_slow: bool,
1032
1033 pub env_map: Option<SetupScriptEnvMap>,
1038}
1039
1040#[derive(Clone, Debug)]
1044pub struct SetupScriptEnvMap {
1045 pub env_map: BTreeMap<String, String>,
1047}
1048
1049#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
1051pub struct RetryData {
1052 pub attempt: u32,
1054
1055 pub total_attempts: u32,
1057}
1058
1059impl RetryData {
1060 pub fn is_last_attempt(&self) -> bool {
1062 self.attempt >= self.total_attempts
1063 }
1064}
1065
1066#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1068pub enum ExecutionResult {
1069 Pass,
1071 Leak {
1075 result: LeakTimeoutResult,
1082 },
1083 Fail {
1085 failure_status: FailureStatus,
1087
1088 leaked: bool,
1092 },
1093 ExecFail,
1095 Timeout {
1097 result: SlowTimeoutResult,
1099 },
1100}
1101
1102impl ExecutionResult {
1103 pub fn is_success(self) -> bool {
1105 match self {
1106 ExecutionResult::Pass
1107 | ExecutionResult::Timeout {
1108 result: SlowTimeoutResult::Pass,
1109 }
1110 | ExecutionResult::Leak {
1111 result: LeakTimeoutResult::Pass,
1112 } => true,
1113 ExecutionResult::Leak {
1114 result: LeakTimeoutResult::Fail,
1115 }
1116 | ExecutionResult::Fail { .. }
1117 | ExecutionResult::ExecFail
1118 | ExecutionResult::Timeout {
1119 result: SlowTimeoutResult::Fail,
1120 } => false,
1121 }
1122 }
1123
1124 pub fn as_static_str(&self) -> &'static str {
1126 match self {
1127 ExecutionResult::Pass => "pass",
1128 ExecutionResult::Leak { .. } => "leak",
1129 ExecutionResult::Fail { .. } => "fail",
1130 ExecutionResult::ExecFail => "exec-fail",
1131 ExecutionResult::Timeout { .. } => "timeout",
1132 }
1133 }
1134}
1135
1136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1138pub enum FailureStatus {
1139 ExitCode(i32),
1141
1142 Abort(AbortStatus),
1144}
1145
1146impl FailureStatus {
1147 pub fn extract(exit_status: ExitStatus) -> Self {
1149 if let Some(abort_status) = AbortStatus::extract(exit_status) {
1150 FailureStatus::Abort(abort_status)
1151 } else {
1152 FailureStatus::ExitCode(
1153 exit_status
1154 .code()
1155 .expect("if abort_status is None, then code must be present"),
1156 )
1157 }
1158 }
1159}
1160
1161#[derive(Copy, Clone, Eq, PartialEq)]
1165pub enum AbortStatus {
1166 #[cfg(unix)]
1168 UnixSignal(i32),
1169
1170 #[cfg(windows)]
1172 WindowsNtStatus(windows_sys::Win32::Foundation::NTSTATUS),
1173
1174 #[cfg(windows)]
1176 JobObject,
1177}
1178
1179impl AbortStatus {
1180 pub fn extract(exit_status: ExitStatus) -> Option<Self> {
1182 cfg_if::cfg_if! {
1183 if #[cfg(unix)] {
1184 use std::os::unix::process::ExitStatusExt;
1186 exit_status.signal().map(AbortStatus::UnixSignal)
1187 } else if #[cfg(windows)] {
1188 exit_status.code().and_then(|code| {
1189 (code < 0).then_some(AbortStatus::WindowsNtStatus(code))
1190 })
1191 } else {
1192 None
1193 }
1194 }
1195 }
1196}
1197
1198impl fmt::Debug for AbortStatus {
1199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1200 match self {
1201 #[cfg(unix)]
1202 AbortStatus::UnixSignal(signal) => write!(f, "UnixSignal({signal})"),
1203 #[cfg(windows)]
1204 AbortStatus::WindowsNtStatus(status) => write!(f, "WindowsNtStatus({status:x})"),
1205 #[cfg(windows)]
1206 AbortStatus::JobObject => write!(f, "JobObject"),
1207 }
1208 }
1209}
1210
1211#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1217#[serde(tag = "kind", rename_all = "kebab-case")]
1218#[non_exhaustive]
1219pub enum AbortDescription {
1220 UnixSignal {
1222 signal: i32,
1224 name: Option<SmolStr>,
1227 },
1228
1229 WindowsNtStatus {
1231 code: i32,
1233 message: Option<SmolStr>,
1235 },
1236
1237 WindowsJobObject,
1239}
1240
1241impl fmt::Display for AbortDescription {
1242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1243 match self {
1244 Self::UnixSignal { signal, name } => {
1245 write!(f, "aborted with signal {signal}")?;
1246 if let Some(name) = name {
1247 write!(f, " (SIG{name})")?;
1248 }
1249 Ok(())
1250 }
1251 Self::WindowsNtStatus { code, message } => {
1252 write!(f, "aborted with code {code:#010x}")?;
1253 if let Some(message) = message {
1254 write!(f, ": {message}")?;
1255 }
1256 Ok(())
1257 }
1258 Self::WindowsJobObject => {
1259 write!(f, "terminated via job object")
1260 }
1261 }
1262 }
1263}
1264
1265impl From<AbortStatus> for AbortDescription {
1266 fn from(status: AbortStatus) -> Self {
1267 cfg_if::cfg_if! {
1268 if #[cfg(unix)] {
1269 match status {
1270 AbortStatus::UnixSignal(signal) => Self::UnixSignal {
1271 signal,
1272 name: crate::helpers::signal_str(signal).map(SmolStr::new_static),
1273 },
1274 }
1275 } else if #[cfg(windows)] {
1276 match status {
1277 AbortStatus::WindowsNtStatus(code) => Self::WindowsNtStatus {
1278 code,
1279 message: crate::helpers::windows_nt_status_message(code),
1280 },
1281 AbortStatus::JobObject => Self::WindowsJobObject,
1282 }
1283 } else {
1284 match status {}
1285 }
1286 }
1287 }
1288}
1289
1290#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1294#[serde(tag = "kind", rename_all = "kebab-case")]
1295#[non_exhaustive]
1296pub enum FailureDescription {
1297 ExitCode {
1299 code: i32,
1301 },
1302
1303 Abort {
1310 abort: AbortDescription,
1312 },
1313}
1314
1315impl From<FailureStatus> for FailureDescription {
1316 fn from(status: FailureStatus) -> Self {
1317 match status {
1318 FailureStatus::ExitCode(code) => Self::ExitCode { code },
1319 FailureStatus::Abort(abort) => Self::Abort {
1320 abort: AbortDescription::from(abort),
1321 },
1322 }
1323 }
1324}
1325
1326impl fmt::Display for FailureDescription {
1327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1328 match self {
1329 Self::ExitCode { code } => write!(f, "exited with code {code}"),
1330 Self::Abort { abort } => write!(f, "{abort}"),
1331 }
1332 }
1333}
1334
1335#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1340#[serde(tag = "status", rename_all = "kebab-case")]
1341#[non_exhaustive]
1342pub enum ExecutionResultDescription {
1343 Pass,
1345
1346 Leak {
1348 result: LeakTimeoutResult,
1350 },
1351
1352 Fail {
1354 failure: FailureDescription,
1356
1357 leaked: bool,
1359 },
1360
1361 ExecFail,
1363
1364 Timeout {
1366 result: SlowTimeoutResult,
1368 },
1369}
1370
1371impl ExecutionResultDescription {
1372 pub fn is_success(&self) -> bool {
1374 match self {
1375 Self::Pass
1376 | Self::Timeout {
1377 result: SlowTimeoutResult::Pass,
1378 }
1379 | Self::Leak {
1380 result: LeakTimeoutResult::Pass,
1381 } => true,
1382 Self::Leak {
1383 result: LeakTimeoutResult::Fail,
1384 }
1385 | Self::Fail { .. }
1386 | Self::ExecFail
1387 | Self::Timeout {
1388 result: SlowTimeoutResult::Fail,
1389 } => false,
1390 }
1391 }
1392
1393 pub fn as_static_str(&self) -> &'static str {
1395 match self {
1396 Self::Pass => "pass",
1397 Self::Leak { .. } => "leak",
1398 Self::Fail { .. } => "fail",
1399 Self::ExecFail => "exec-fail",
1400 Self::Timeout { .. } => "timeout",
1401 }
1402 }
1403
1404 pub fn is_termination_failure(&self) -> bool {
1416 matches!(
1417 self,
1418 Self::Fail {
1419 failure: FailureDescription::Abort {
1420 abort: AbortDescription::UnixSignal {
1421 signal: SIGTERM,
1422 ..
1423 },
1424 },
1425 ..
1426 } | Self::Fail {
1427 failure: FailureDescription::Abort {
1428 abort: AbortDescription::WindowsJobObject,
1429 },
1430 ..
1431 }
1432 )
1433 }
1434}
1435
1436impl From<ExecutionResult> for ExecutionResultDescription {
1437 fn from(result: ExecutionResult) -> Self {
1438 match result {
1439 ExecutionResult::Pass => Self::Pass,
1440 ExecutionResult::Leak { result } => Self::Leak { result },
1441 ExecutionResult::Fail {
1442 failure_status,
1443 leaked,
1444 } => Self::Fail {
1445 failure: FailureDescription::from(failure_status),
1446 leaked,
1447 },
1448 ExecutionResult::ExecFail => Self::ExecFail,
1449 ExecutionResult::Timeout { result } => Self::Timeout { result },
1450 }
1451 }
1452}
1453
1454#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1457#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1458pub enum CancelReason {
1459 SetupScriptFailure,
1461
1462 TestFailure,
1464
1465 ReportError,
1467
1468 GlobalTimeout,
1470
1471 TestFailureImmediate,
1473
1474 Signal,
1476
1477 Interrupt,
1479
1480 SecondSignal,
1482}
1483
1484impl CancelReason {
1485 pub(crate) fn to_static_str(self) -> &'static str {
1486 match self {
1487 CancelReason::SetupScriptFailure => "setup script failure",
1488 CancelReason::TestFailure => "test failure",
1489 CancelReason::ReportError => "reporting error",
1490 CancelReason::GlobalTimeout => "global timeout",
1491 CancelReason::TestFailureImmediate => "test failure",
1492 CancelReason::Signal => "signal",
1493 CancelReason::Interrupt => "interrupt",
1494 CancelReason::SecondSignal => "second signal",
1495 }
1496 }
1497}
1498#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1500pub enum UnitKind {
1501 Test,
1503
1504 Script,
1506}
1507
1508impl UnitKind {
1509 pub(crate) const WAITING_ON_TEST_MESSAGE: &str = "waiting on test process";
1510 pub(crate) const WAITING_ON_SCRIPT_MESSAGE: &str = "waiting on script process";
1511
1512 pub(crate) const EXECUTING_TEST_MESSAGE: &str = "executing test";
1513 pub(crate) const EXECUTING_SCRIPT_MESSAGE: &str = "executing script";
1514
1515 pub(crate) fn waiting_on_message(&self) -> &'static str {
1516 match self {
1517 UnitKind::Test => Self::WAITING_ON_TEST_MESSAGE,
1518 UnitKind::Script => Self::WAITING_ON_SCRIPT_MESSAGE,
1519 }
1520 }
1521
1522 pub(crate) fn executing_message(&self) -> &'static str {
1523 match self {
1524 UnitKind::Test => Self::EXECUTING_TEST_MESSAGE,
1525 UnitKind::Script => Self::EXECUTING_SCRIPT_MESSAGE,
1526 }
1527 }
1528}
1529
1530#[derive(Clone, Debug)]
1532pub enum InfoResponse<'a> {
1533 SetupScript(SetupScriptInfoResponse<'a>),
1535
1536 Test(TestInfoResponse<'a>),
1538}
1539
1540#[derive(Clone, Debug)]
1542pub struct SetupScriptInfoResponse<'a> {
1543 pub stress_index: Option<StressIndex>,
1545
1546 pub script_id: ScriptId,
1548
1549 pub program: String,
1551
1552 pub args: &'a [String],
1554
1555 pub state: UnitState,
1557
1558 pub output: ChildExecutionOutput,
1560}
1561
1562#[derive(Clone, Debug)]
1564pub struct TestInfoResponse<'a> {
1565 pub stress_index: Option<StressIndex>,
1567
1568 pub test_instance: TestInstanceId<'a>,
1570
1571 pub retry_data: RetryData,
1573
1574 pub state: UnitState,
1576
1577 pub output: ChildExecutionOutput,
1579}
1580
1581#[derive(Clone, Debug)]
1586pub enum UnitState {
1587 Running {
1589 pid: u32,
1591
1592 time_taken: Duration,
1594
1595 slow_after: Option<Duration>,
1598 },
1599
1600 Exiting {
1603 pid: u32,
1605
1606 time_taken: Duration,
1608
1609 slow_after: Option<Duration>,
1612
1613 tentative_result: Option<ExecutionResult>,
1618
1619 waiting_duration: Duration,
1621
1622 remaining: Duration,
1624 },
1625
1626 Terminating(UnitTerminatingState),
1628
1629 Exited {
1631 result: ExecutionResult,
1633
1634 time_taken: Duration,
1636
1637 slow_after: Option<Duration>,
1640 },
1641
1642 DelayBeforeNextAttempt {
1645 previous_result: ExecutionResult,
1647
1648 previous_slow: bool,
1650
1651 waiting_duration: Duration,
1653
1654 remaining: Duration,
1656 },
1657}
1658
1659impl UnitState {
1660 pub fn has_valid_output(&self) -> bool {
1662 match self {
1663 UnitState::Running { .. }
1664 | UnitState::Exiting { .. }
1665 | UnitState::Terminating(_)
1666 | UnitState::Exited { .. } => true,
1667 UnitState::DelayBeforeNextAttempt { .. } => false,
1668 }
1669 }
1670}
1671
1672#[derive(Clone, Debug)]
1676pub struct UnitTerminatingState {
1677 pub pid: u32,
1679
1680 pub time_taken: Duration,
1682
1683 pub reason: UnitTerminateReason,
1685
1686 pub method: UnitTerminateMethod,
1688
1689 pub waiting_duration: Duration,
1691
1692 pub remaining: Duration,
1694}
1695
1696#[derive(Clone, Copy, Debug)]
1700pub enum UnitTerminateReason {
1701 Timeout,
1703
1704 Signal,
1706
1707 Interrupt,
1709}
1710
1711impl fmt::Display for UnitTerminateReason {
1712 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1713 match self {
1714 UnitTerminateReason::Timeout => write!(f, "timeout"),
1715 UnitTerminateReason::Signal => write!(f, "signal"),
1716 UnitTerminateReason::Interrupt => write!(f, "interrupt"),
1717 }
1718 }
1719}
1720
1721#[derive(Clone, Copy, Debug)]
1723pub enum UnitTerminateMethod {
1724 #[cfg(unix)]
1726 Signal(UnitTerminateSignal),
1727
1728 #[cfg(windows)]
1730 JobObject,
1731
1732 #[cfg(windows)]
1740 Wait,
1741
1742 #[cfg(test)]
1744 Fake,
1745}
1746
1747#[cfg(unix)]
1748#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1750pub enum UnitTerminateSignal {
1751 Interrupt,
1753
1754 Term,
1756
1757 Hangup,
1759
1760 Quit,
1762
1763 Kill,
1765}
1766
1767#[cfg(unix)]
1768impl fmt::Display for UnitTerminateSignal {
1769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1770 match self {
1771 UnitTerminateSignal::Interrupt => write!(f, "SIGINT"),
1772 UnitTerminateSignal::Term => write!(f, "SIGTERM"),
1773 UnitTerminateSignal::Hangup => write!(f, "SIGHUP"),
1774 UnitTerminateSignal::Quit => write!(f, "SIGQUIT"),
1775 UnitTerminateSignal::Kill => write!(f, "SIGKILL"),
1776 }
1777 }
1778}
1779
1780#[cfg(test)]
1781mod tests {
1782 use super::*;
1783
1784 #[test]
1785 fn test_is_success() {
1786 assert_eq!(
1787 RunStats::default().summarize_final(),
1788 FinalRunStats::NoTestsRun,
1789 "empty run => no tests run"
1790 );
1791 assert_eq!(
1792 RunStats {
1793 initial_run_count: 42,
1794 finished_count: 42,
1795 ..RunStats::default()
1796 }
1797 .summarize_final(),
1798 FinalRunStats::Success,
1799 "initial run count = final run count => success"
1800 );
1801 assert_eq!(
1802 RunStats {
1803 initial_run_count: 42,
1804 finished_count: 41,
1805 ..RunStats::default()
1806 }
1807 .summarize_final(),
1808 FinalRunStats::Cancelled {
1809 reason: None,
1810 kind: RunStatsFailureKind::Test {
1811 initial_run_count: 42,
1812 not_run: 1
1813 }
1814 },
1815 "initial run count > final run count => cancelled"
1816 );
1817 assert_eq!(
1818 RunStats {
1819 initial_run_count: 42,
1820 finished_count: 42,
1821 failed: 1,
1822 ..RunStats::default()
1823 }
1824 .summarize_final(),
1825 FinalRunStats::Failed(RunStatsFailureKind::Test {
1826 initial_run_count: 42,
1827 not_run: 0
1828 }),
1829 "failed => failure"
1830 );
1831 assert_eq!(
1832 RunStats {
1833 initial_run_count: 42,
1834 finished_count: 42,
1835 exec_failed: 1,
1836 ..RunStats::default()
1837 }
1838 .summarize_final(),
1839 FinalRunStats::Failed(RunStatsFailureKind::Test {
1840 initial_run_count: 42,
1841 not_run: 0
1842 }),
1843 "exec failed => failure"
1844 );
1845 assert_eq!(
1846 RunStats {
1847 initial_run_count: 42,
1848 finished_count: 42,
1849 failed_timed_out: 1,
1850 ..RunStats::default()
1851 }
1852 .summarize_final(),
1853 FinalRunStats::Failed(RunStatsFailureKind::Test {
1854 initial_run_count: 42,
1855 not_run: 0
1856 }),
1857 "timed out => failure {:?} {:?}",
1858 RunStats {
1859 initial_run_count: 42,
1860 finished_count: 42,
1861 failed_timed_out: 1,
1862 ..RunStats::default()
1863 }
1864 .summarize_final(),
1865 FinalRunStats::Failed(RunStatsFailureKind::Test {
1866 initial_run_count: 42,
1867 not_run: 0
1868 }),
1869 );
1870 assert_eq!(
1871 RunStats {
1872 initial_run_count: 42,
1873 finished_count: 42,
1874 skipped: 1,
1875 ..RunStats::default()
1876 }
1877 .summarize_final(),
1878 FinalRunStats::Success,
1879 "skipped => not considered a failure"
1880 );
1881
1882 assert_eq!(
1883 RunStats {
1884 setup_scripts_initial_count: 2,
1885 setup_scripts_finished_count: 1,
1886 ..RunStats::default()
1887 }
1888 .summarize_final(),
1889 FinalRunStats::Cancelled {
1890 reason: None,
1891 kind: RunStatsFailureKind::SetupScript,
1892 },
1893 "setup script failed => failure"
1894 );
1895
1896 assert_eq!(
1897 RunStats {
1898 setup_scripts_initial_count: 2,
1899 setup_scripts_finished_count: 2,
1900 setup_scripts_failed: 1,
1901 ..RunStats::default()
1902 }
1903 .summarize_final(),
1904 FinalRunStats::Failed(RunStatsFailureKind::SetupScript),
1905 "setup script failed => failure"
1906 );
1907 assert_eq!(
1908 RunStats {
1909 setup_scripts_initial_count: 2,
1910 setup_scripts_finished_count: 2,
1911 setup_scripts_exec_failed: 1,
1912 ..RunStats::default()
1913 }
1914 .summarize_final(),
1915 FinalRunStats::Failed(RunStatsFailureKind::SetupScript),
1916 "setup script exec failed => failure"
1917 );
1918 assert_eq!(
1919 RunStats {
1920 setup_scripts_initial_count: 2,
1921 setup_scripts_finished_count: 2,
1922 setup_scripts_timed_out: 1,
1923 ..RunStats::default()
1924 }
1925 .summarize_final(),
1926 FinalRunStats::Failed(RunStatsFailureKind::SetupScript),
1927 "setup script timed out => failure"
1928 );
1929 assert_eq!(
1930 RunStats {
1931 setup_scripts_initial_count: 2,
1932 setup_scripts_finished_count: 2,
1933 setup_scripts_passed: 2,
1934 ..RunStats::default()
1935 }
1936 .summarize_final(),
1937 FinalRunStats::NoTestsRun,
1938 "setup scripts passed => success, but no tests run"
1939 );
1940 }
1941
1942 #[test]
1943 fn abort_description_serialization() {
1944 let unix_with_name = AbortDescription::UnixSignal {
1946 signal: 15,
1947 name: Some("TERM".into()),
1948 };
1949 let json = serde_json::to_string_pretty(&unix_with_name).unwrap();
1950 insta::assert_snapshot!("abort_unix_signal_with_name", json);
1951 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
1952 assert_eq!(unix_with_name, roundtrip);
1953
1954 let unix_no_name = AbortDescription::UnixSignal {
1956 signal: 42,
1957 name: None,
1958 };
1959 let json = serde_json::to_string_pretty(&unix_no_name).unwrap();
1960 insta::assert_snapshot!("abort_unix_signal_no_name", json);
1961 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
1962 assert_eq!(unix_no_name, roundtrip);
1963
1964 let windows_nt = AbortDescription::WindowsNtStatus {
1966 code: -1073741510_i32,
1967 message: Some("The application terminated as a result of a CTRL+C.".into()),
1968 };
1969 let json = serde_json::to_string_pretty(&windows_nt).unwrap();
1970 insta::assert_snapshot!("abort_windows_nt_status", json);
1971 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
1972 assert_eq!(windows_nt, roundtrip);
1973
1974 let windows_nt_no_msg = AbortDescription::WindowsNtStatus {
1976 code: -1073741819_i32,
1977 message: None,
1978 };
1979 let json = serde_json::to_string_pretty(&windows_nt_no_msg).unwrap();
1980 insta::assert_snapshot!("abort_windows_nt_status_no_message", json);
1981 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
1982 assert_eq!(windows_nt_no_msg, roundtrip);
1983
1984 let job = AbortDescription::WindowsJobObject;
1986 let json = serde_json::to_string_pretty(&job).unwrap();
1987 insta::assert_snapshot!("abort_windows_job_object", json);
1988 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
1989 assert_eq!(job, roundtrip);
1990 }
1991
1992 #[test]
1993 fn abort_description_cross_platform_deserialization() {
1994 let unix_json = r#"{"kind":"unix-signal","signal":11,"name":"SEGV"}"#;
1997 let unix_desc: AbortDescription = serde_json::from_str(unix_json).unwrap();
1998 assert_eq!(
1999 unix_desc,
2000 AbortDescription::UnixSignal {
2001 signal: 11,
2002 name: Some("SEGV".into()),
2003 }
2004 );
2005
2006 let windows_json = r#"{"kind":"windows-nt-status","code":-1073741510,"message":"CTRL+C"}"#;
2007 let windows_desc: AbortDescription = serde_json::from_str(windows_json).unwrap();
2008 assert_eq!(
2009 windows_desc,
2010 AbortDescription::WindowsNtStatus {
2011 code: -1073741510,
2012 message: Some("CTRL+C".into()),
2013 }
2014 );
2015
2016 let job_json = r#"{"kind":"windows-job-object"}"#;
2017 let job_desc: AbortDescription = serde_json::from_str(job_json).unwrap();
2018 assert_eq!(job_desc, AbortDescription::WindowsJobObject);
2019 }
2020
2021 #[test]
2022 fn abort_description_display() {
2023 let unix = AbortDescription::UnixSignal {
2025 signal: 15,
2026 name: Some("TERM".into()),
2027 };
2028 assert_eq!(unix.to_string(), "aborted with signal 15 (SIGTERM)");
2029
2030 let unix_no_name = AbortDescription::UnixSignal {
2032 signal: 42,
2033 name: None,
2034 };
2035 assert_eq!(unix_no_name.to_string(), "aborted with signal 42");
2036
2037 let windows = AbortDescription::WindowsNtStatus {
2039 code: -1073741510,
2040 message: Some("CTRL+C exit".into()),
2041 };
2042 assert_eq!(
2043 windows.to_string(),
2044 "aborted with code 0xc000013a: CTRL+C exit"
2045 );
2046
2047 let windows_no_msg = AbortDescription::WindowsNtStatus {
2049 code: -1073741510,
2050 message: None,
2051 };
2052 assert_eq!(windows_no_msg.to_string(), "aborted with code 0xc000013a");
2053
2054 let job = AbortDescription::WindowsJobObject;
2056 assert_eq!(job.to_string(), "terminated via job object");
2057 }
2058
2059 #[cfg(unix)]
2060 #[test]
2061 fn abort_description_from_abort_status() {
2062 let status = AbortStatus::UnixSignal(15);
2064 let description = AbortDescription::from(status);
2065
2066 assert_eq!(
2067 description,
2068 AbortDescription::UnixSignal {
2069 signal: 15,
2070 name: Some("TERM".into()),
2071 }
2072 );
2073
2074 let unknown_status = AbortStatus::UnixSignal(42);
2076 let unknown_description = AbortDescription::from(unknown_status);
2077 assert_eq!(
2078 unknown_description,
2079 AbortDescription::UnixSignal {
2080 signal: 42,
2081 name: None,
2082 }
2083 );
2084 }
2085
2086 #[test]
2087 fn execution_result_description_serialization() {
2088 let pass = ExecutionResultDescription::Pass;
2092 let json = serde_json::to_string_pretty(&pass).unwrap();
2093 insta::assert_snapshot!("pass", json);
2094 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2095 assert_eq!(pass, roundtrip);
2096
2097 let leak_pass = ExecutionResultDescription::Leak {
2099 result: LeakTimeoutResult::Pass,
2100 };
2101 let json = serde_json::to_string_pretty(&leak_pass).unwrap();
2102 insta::assert_snapshot!("leak_pass", json);
2103 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2104 assert_eq!(leak_pass, roundtrip);
2105
2106 let leak_fail = ExecutionResultDescription::Leak {
2108 result: LeakTimeoutResult::Fail,
2109 };
2110 let json = serde_json::to_string_pretty(&leak_fail).unwrap();
2111 insta::assert_snapshot!("leak_fail", json);
2112 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2113 assert_eq!(leak_fail, roundtrip);
2114
2115 let fail_exit_code = ExecutionResultDescription::Fail {
2117 failure: FailureDescription::ExitCode { code: 101 },
2118 leaked: false,
2119 };
2120 let json = serde_json::to_string_pretty(&fail_exit_code).unwrap();
2121 insta::assert_snapshot!("fail_exit_code", json);
2122 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2123 assert_eq!(fail_exit_code, roundtrip);
2124
2125 let fail_exit_code_leaked = ExecutionResultDescription::Fail {
2127 failure: FailureDescription::ExitCode { code: 1 },
2128 leaked: true,
2129 };
2130 let json = serde_json::to_string_pretty(&fail_exit_code_leaked).unwrap();
2131 insta::assert_snapshot!("fail_exit_code_leaked", json);
2132 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2133 assert_eq!(fail_exit_code_leaked, roundtrip);
2134
2135 let fail_unix_signal = ExecutionResultDescription::Fail {
2137 failure: FailureDescription::Abort {
2138 abort: AbortDescription::UnixSignal {
2139 signal: 11,
2140 name: Some("SEGV".into()),
2141 },
2142 },
2143 leaked: false,
2144 };
2145 let json = serde_json::to_string_pretty(&fail_unix_signal).unwrap();
2146 insta::assert_snapshot!("fail_unix_signal", json);
2147 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2148 assert_eq!(fail_unix_signal, roundtrip);
2149
2150 let fail_unix_signal_unknown = ExecutionResultDescription::Fail {
2152 failure: FailureDescription::Abort {
2153 abort: AbortDescription::UnixSignal {
2154 signal: 42,
2155 name: None,
2156 },
2157 },
2158 leaked: true,
2159 };
2160 let json = serde_json::to_string_pretty(&fail_unix_signal_unknown).unwrap();
2161 insta::assert_snapshot!("fail_unix_signal_unknown_leaked", json);
2162 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2163 assert_eq!(fail_unix_signal_unknown, roundtrip);
2164
2165 let fail_windows_nt = ExecutionResultDescription::Fail {
2167 failure: FailureDescription::Abort {
2168 abort: AbortDescription::WindowsNtStatus {
2169 code: -1073741510,
2170 message: Some("The application terminated as a result of a CTRL+C.".into()),
2171 },
2172 },
2173 leaked: false,
2174 };
2175 let json = serde_json::to_string_pretty(&fail_windows_nt).unwrap();
2176 insta::assert_snapshot!("fail_windows_nt_status", json);
2177 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2178 assert_eq!(fail_windows_nt, roundtrip);
2179
2180 let fail_windows_nt_no_msg = ExecutionResultDescription::Fail {
2182 failure: FailureDescription::Abort {
2183 abort: AbortDescription::WindowsNtStatus {
2184 code: -1073741819,
2185 message: None,
2186 },
2187 },
2188 leaked: false,
2189 };
2190 let json = serde_json::to_string_pretty(&fail_windows_nt_no_msg).unwrap();
2191 insta::assert_snapshot!("fail_windows_nt_status_no_message", json);
2192 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2193 assert_eq!(fail_windows_nt_no_msg, roundtrip);
2194
2195 let fail_job_object = ExecutionResultDescription::Fail {
2197 failure: FailureDescription::Abort {
2198 abort: AbortDescription::WindowsJobObject,
2199 },
2200 leaked: false,
2201 };
2202 let json = serde_json::to_string_pretty(&fail_job_object).unwrap();
2203 insta::assert_snapshot!("fail_windows_job_object", json);
2204 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2205 assert_eq!(fail_job_object, roundtrip);
2206
2207 let exec_fail = ExecutionResultDescription::ExecFail;
2209 let json = serde_json::to_string_pretty(&exec_fail).unwrap();
2210 insta::assert_snapshot!("exec_fail", json);
2211 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2212 assert_eq!(exec_fail, roundtrip);
2213
2214 let timeout_pass = ExecutionResultDescription::Timeout {
2216 result: SlowTimeoutResult::Pass,
2217 };
2218 let json = serde_json::to_string_pretty(&timeout_pass).unwrap();
2219 insta::assert_snapshot!("timeout_pass", json);
2220 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2221 assert_eq!(timeout_pass, roundtrip);
2222
2223 let timeout_fail = ExecutionResultDescription::Timeout {
2225 result: SlowTimeoutResult::Fail,
2226 };
2227 let json = serde_json::to_string_pretty(&timeout_fail).unwrap();
2228 insta::assert_snapshot!("timeout_fail", json);
2229 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2230 assert_eq!(timeout_fail, roundtrip);
2231 }
2232}