1use super::{FinalStatusLevel, StatusLevel, TestOutputDisplay};
10use crate::{
11 config::{
12 elements::{LeakTimeoutResult, SlowTimeoutResult},
13 scripts::ScriptId,
14 },
15 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
16 list::{TestInstanceId, TestList},
17 runner::{StressCondition, StressCount},
18 test_output::{ChildExecutionOutput, ChildOutput, ChildSingleOutput},
19};
20use chrono::{DateTime, FixedOffset};
21use nextest_metadata::MismatchReason;
22use quick_junit::ReportUuid;
23use serde::{Deserialize, Serialize};
24use smol_str::SmolStr;
25use std::{
26 collections::BTreeMap, ffi::c_int, fmt, num::NonZero, process::ExitStatus, time::Duration,
27};
28
29pub const SIGTERM: c_int = 15;
34
35#[derive(Clone, Debug)]
37pub enum ReporterEvent<'a> {
38 Tick,
40
41 Test(Box<TestEvent<'a>>),
43}
44#[derive(Clone, Debug)]
49pub struct TestEvent<'a> {
50 pub timestamp: DateTime<FixedOffset>,
52
53 pub elapsed: Duration,
55
56 pub kind: TestEventKind<'a>,
58}
59
60#[derive(Clone, Debug)]
64pub enum TestEventKind<'a> {
65 RunStarted {
67 test_list: &'a TestList<'a>,
71
72 run_id: ReportUuid,
74
75 profile_name: String,
77
78 cli_args: Vec<String>,
80
81 stress_condition: Option<StressCondition>,
83 },
84
85 StressSubRunStarted {
87 progress: StressProgress,
89 },
90
91 SetupScriptStarted {
93 stress_index: Option<StressIndex>,
95
96 index: usize,
98
99 total: usize,
101
102 script_id: ScriptId,
104
105 program: String,
107
108 args: &'a [String],
110
111 no_capture: bool,
113 },
114
115 SetupScriptSlow {
117 stress_index: Option<StressIndex>,
119
120 script_id: ScriptId,
122
123 program: String,
125
126 args: &'a [String],
128
129 elapsed: Duration,
131
132 will_terminate: bool,
134 },
135
136 SetupScriptFinished {
138 stress_index: Option<StressIndex>,
140
141 index: usize,
143
144 total: usize,
146
147 script_id: ScriptId,
149
150 program: String,
152
153 args: &'a [String],
155
156 junit_store_success_output: bool,
158
159 junit_store_failure_output: bool,
161
162 no_capture: bool,
164
165 run_status: SetupScriptExecuteStatus<ChildSingleOutput>,
167 },
168
169 TestStarted {
174 stress_index: Option<StressIndex>,
176
177 test_instance: TestInstanceId<'a>,
179
180 current_stats: RunStats,
182
183 running: usize,
185
186 command_line: Vec<String>,
188 },
189
190 TestSlow {
192 stress_index: Option<StressIndex>,
194
195 test_instance: TestInstanceId<'a>,
197
198 retry_data: RetryData,
200
201 elapsed: Duration,
203
204 will_terminate: bool,
206 },
207
208 TestAttemptFailedWillRetry {
212 stress_index: Option<StressIndex>,
214
215 test_instance: TestInstanceId<'a>,
217
218 run_status: ExecuteStatus<ChildSingleOutput>,
220
221 delay_before_next_attempt: Duration,
223
224 failure_output: TestOutputDisplay,
226
227 running: usize,
229 },
230
231 TestRetryStarted {
233 stress_index: Option<StressIndex>,
235
236 test_instance: TestInstanceId<'a>,
238
239 retry_data: RetryData,
241
242 running: usize,
244
245 command_line: Vec<String>,
247 },
248
249 TestFinished {
251 stress_index: Option<StressIndex>,
253
254 test_instance: TestInstanceId<'a>,
256
257 success_output: TestOutputDisplay,
259
260 failure_output: TestOutputDisplay,
262
263 junit_store_success_output: bool,
265
266 junit_store_failure_output: bool,
268
269 run_statuses: ExecutionStatuses<ChildSingleOutput>,
271
272 current_stats: RunStats,
274
275 running: usize,
277 },
278
279 TestSkipped {
281 stress_index: Option<StressIndex>,
283
284 test_instance: TestInstanceId<'a>,
286
287 reason: MismatchReason,
289 },
290
291 InfoStarted {
293 total: usize,
296
297 run_stats: RunStats,
299 },
300
301 InfoResponse {
303 index: usize,
305
306 total: usize,
308
309 response: InfoResponse<'a>,
311 },
312
313 InfoFinished {
315 missing: usize,
318 },
319
320 InputEnter {
323 current_stats: RunStats,
325
326 running: usize,
328 },
329
330 RunBeginCancel {
332 setup_scripts_running: usize,
334
335 current_stats: RunStats,
339
340 running: usize,
342 },
343
344 RunBeginKill {
346 setup_scripts_running: usize,
348
349 current_stats: RunStats,
353
354 running: usize,
356 },
357
358 RunPaused {
360 setup_scripts_running: usize,
362
363 running: usize,
365 },
366
367 RunContinued {
369 setup_scripts_running: usize,
371
372 running: usize,
374 },
375
376 StressSubRunFinished {
378 progress: StressProgress,
380
381 sub_elapsed: Duration,
383
384 sub_stats: RunStats,
386 },
387
388 RunFinished {
390 run_id: ReportUuid,
392
393 start_time: DateTime<FixedOffset>,
395
396 elapsed: Duration,
398
399 run_stats: RunFinishedStats,
401 },
402}
403
404#[derive(Clone, Debug)]
406pub enum StressProgress {
407 Count {
409 total: StressCount,
411
412 elapsed: Duration,
414
415 completed: u32,
417 },
418
419 Time {
421 total: Duration,
423
424 elapsed: Duration,
426
427 completed: u32,
429 },
430}
431
432impl StressProgress {
433 pub fn remaining(&self) -> Option<StressRemaining> {
436 match self {
437 Self::Count {
438 total: StressCount::Count { count },
439 elapsed: _,
440 completed,
441 } => count
442 .get()
443 .checked_sub(*completed)
444 .and_then(|remaining| NonZero::try_from(remaining).ok())
445 .map(StressRemaining::Count),
446 Self::Count {
447 total: StressCount::Infinite,
448 ..
449 } => Some(StressRemaining::Infinite),
450 Self::Time {
451 total,
452 elapsed,
453 completed: _,
454 } => total.checked_sub(*elapsed).map(StressRemaining::Time),
455 }
456 }
457
458 pub fn unique_id(&self, run_id: ReportUuid) -> String {
460 let stress_current = match self {
461 Self::Count { completed, .. } | Self::Time { completed, .. } => *completed,
462 };
463 format!("{}:@stress-{}", run_id, stress_current)
464 }
465}
466
467#[derive(Clone, Debug)]
469pub enum StressRemaining {
470 Count(NonZero<u32>),
472
473 Infinite,
475
476 Time(Duration),
478}
479
480#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
482pub struct StressIndex {
483 pub current: u32,
485
486 pub total: Option<NonZero<u32>>,
488}
489
490#[derive(Clone, Debug)]
492pub enum RunFinishedStats {
493 Single(RunStats),
495
496 Stress(StressRunStats),
498}
499
500impl RunFinishedStats {
501 pub fn final_stats(&self) -> FinalRunStats {
504 match self {
505 Self::Single(stats) => stats.summarize_final(),
506 Self::Stress(stats) => stats.last_final_stats,
507 }
508 }
509}
510
511#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
513pub struct RunStats {
514 pub initial_run_count: usize,
518
519 pub finished_count: usize,
521
522 pub setup_scripts_initial_count: usize,
526
527 pub setup_scripts_finished_count: usize,
529
530 pub setup_scripts_passed: usize,
532
533 pub setup_scripts_failed: usize,
535
536 pub setup_scripts_exec_failed: usize,
538
539 pub setup_scripts_timed_out: usize,
541
542 pub passed: usize,
544
545 pub passed_slow: usize,
547
548 pub passed_timed_out: usize,
550
551 pub flaky: usize,
553
554 pub failed: usize,
556
557 pub failed_slow: usize,
559
560 pub failed_timed_out: usize,
562
563 pub leaky: usize,
565
566 pub leaky_failed: usize,
569
570 pub exec_failed: usize,
572
573 pub skipped: usize,
575
576 pub cancel_reason: Option<CancelReason>,
578}
579
580impl RunStats {
581 pub fn has_failures(&self) -> bool {
583 self.failed_setup_script_count() > 0 || self.failed_count() > 0
584 }
585
586 pub fn failed_setup_script_count(&self) -> usize {
588 self.setup_scripts_failed + self.setup_scripts_exec_failed + self.setup_scripts_timed_out
589 }
590
591 pub fn failed_count(&self) -> usize {
593 self.failed + self.exec_failed + self.failed_timed_out
594 }
595
596 pub fn summarize_final(&self) -> FinalRunStats {
598 if self.failed_setup_script_count() > 0 {
601 if self.cancel_reason > Some(CancelReason::TestFailure) {
604 FinalRunStats::Cancelled {
605 reason: self.cancel_reason,
606 kind: RunStatsFailureKind::SetupScript,
607 }
608 } else {
609 FinalRunStats::Failed {
610 kind: RunStatsFailureKind::SetupScript,
611 }
612 }
613 } else if self.setup_scripts_initial_count > self.setup_scripts_finished_count {
614 FinalRunStats::Cancelled {
615 reason: self.cancel_reason,
616 kind: RunStatsFailureKind::SetupScript,
617 }
618 } else if self.failed_count() > 0 {
619 let kind = RunStatsFailureKind::Test {
620 initial_run_count: self.initial_run_count,
621 not_run: self.initial_run_count.saturating_sub(self.finished_count),
622 };
623
624 if self.cancel_reason > Some(CancelReason::TestFailure) {
627 FinalRunStats::Cancelled {
628 reason: self.cancel_reason,
629 kind,
630 }
631 } else {
632 FinalRunStats::Failed { kind }
633 }
634 } else if self.initial_run_count > self.finished_count {
635 FinalRunStats::Cancelled {
636 reason: self.cancel_reason,
637 kind: RunStatsFailureKind::Test {
638 initial_run_count: self.initial_run_count,
639 not_run: self.initial_run_count.saturating_sub(self.finished_count),
640 },
641 }
642 } else if self.finished_count == 0 {
643 FinalRunStats::NoTestsRun
644 } else {
645 FinalRunStats::Success
646 }
647 }
648
649 pub(crate) fn on_setup_script_finished(
650 &mut self,
651 status: &SetupScriptExecuteStatus<ChildSingleOutput>,
652 ) {
653 self.setup_scripts_finished_count += 1;
654
655 match status.result {
656 ExecutionResultDescription::Pass
657 | ExecutionResultDescription::Leak {
658 result: LeakTimeoutResult::Pass,
659 } => {
660 self.setup_scripts_passed += 1;
661 }
662 ExecutionResultDescription::Fail { .. }
663 | ExecutionResultDescription::Leak {
664 result: LeakTimeoutResult::Fail,
665 } => {
666 self.setup_scripts_failed += 1;
667 }
668 ExecutionResultDescription::ExecFail => {
669 self.setup_scripts_exec_failed += 1;
670 }
671 ExecutionResultDescription::Timeout { .. } => {
673 self.setup_scripts_timed_out += 1;
674 }
675 }
676 }
677
678 pub(crate) fn on_test_finished(&mut self, run_statuses: &ExecutionStatuses<ChildSingleOutput>) {
679 self.finished_count += 1;
680 let last_status = run_statuses.last_status();
689 match last_status.result {
690 ExecutionResultDescription::Pass => {
691 self.passed += 1;
692 if last_status.is_slow {
693 self.passed_slow += 1;
694 }
695 if run_statuses.len() > 1 {
696 self.flaky += 1;
697 }
698 }
699 ExecutionResultDescription::Leak {
700 result: LeakTimeoutResult::Pass,
701 } => {
702 self.passed += 1;
703 self.leaky += 1;
704 if last_status.is_slow {
705 self.passed_slow += 1;
706 }
707 if run_statuses.len() > 1 {
708 self.flaky += 1;
709 }
710 }
711 ExecutionResultDescription::Leak {
712 result: LeakTimeoutResult::Fail,
713 } => {
714 self.failed += 1;
715 self.leaky_failed += 1;
716 if last_status.is_slow {
717 self.failed_slow += 1;
718 }
719 }
720 ExecutionResultDescription::Fail { .. } => {
721 self.failed += 1;
722 if last_status.is_slow {
723 self.failed_slow += 1;
724 }
725 }
726 ExecutionResultDescription::Timeout {
727 result: SlowTimeoutResult::Pass,
728 } => {
729 self.passed += 1;
730 self.passed_timed_out += 1;
731 if run_statuses.len() > 1 {
732 self.flaky += 1;
733 }
734 }
735 ExecutionResultDescription::Timeout {
736 result: SlowTimeoutResult::Fail,
737 } => {
738 self.failed_timed_out += 1;
739 }
740 ExecutionResultDescription::ExecFail => self.exec_failed += 1,
741 }
742 }
743}
744
745#[derive(Copy, Clone, Debug, Eq, PartialEq)]
747pub enum FinalRunStats {
748 Success,
750
751 NoTestsRun,
753
754 Cancelled {
756 reason: Option<CancelReason>,
761
762 kind: RunStatsFailureKind,
764 },
765
766 Failed {
768 kind: RunStatsFailureKind,
770 },
771}
772
773#[derive(Clone, Debug)]
775pub struct StressRunStats {
776 pub completed: StressIndex,
778
779 pub success_count: u32,
781
782 pub failed_count: u32,
784
785 pub last_final_stats: FinalRunStats,
787}
788
789impl StressRunStats {
790 pub fn summarize_final(&self) -> StressFinalRunStats {
792 if self.failed_count > 0 {
793 StressFinalRunStats::Failed
794 } else if matches!(self.last_final_stats, FinalRunStats::Cancelled { .. }) {
795 StressFinalRunStats::Cancelled
796 } else if matches!(self.last_final_stats, FinalRunStats::NoTestsRun) {
797 StressFinalRunStats::NoTestsRun
798 } else {
799 StressFinalRunStats::Success
800 }
801 }
802}
803
804pub enum StressFinalRunStats {
806 Success,
808
809 NoTestsRun,
811
812 Cancelled,
814
815 Failed,
817}
818
819#[derive(Copy, Clone, Debug, Eq, PartialEq)]
821pub enum RunStatsFailureKind {
822 SetupScript,
824
825 Test {
827 initial_run_count: usize,
829
830 not_run: usize,
833 },
834}
835
836#[derive(Clone, Debug)]
840pub struct ExecutionStatuses<O> {
841 statuses: Vec<ExecuteStatus<O>>,
843}
844
845#[expect(clippy::len_without_is_empty)] impl<O> ExecutionStatuses<O> {
847 pub(crate) fn new(statuses: Vec<ExecuteStatus<O>>) -> Self {
848 Self { statuses }
849 }
850
851 pub fn last_status(&self) -> &ExecuteStatus<O> {
855 self.statuses
856 .last()
857 .expect("execution statuses is non-empty")
858 }
859
860 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &'_ ExecuteStatus<O>> + '_ {
862 self.statuses.iter()
863 }
864
865 pub fn len(&self) -> usize {
867 self.statuses.len()
868 }
869
870 pub fn describe(&self) -> ExecutionDescription<'_, O> {
872 let last_status = self.last_status();
873 if last_status.result.is_success() {
874 if self.statuses.len() > 1 {
875 ExecutionDescription::Flaky {
876 last_status,
877 prior_statuses: &self.statuses[..self.statuses.len() - 1],
878 }
879 } else {
880 ExecutionDescription::Success {
881 single_status: last_status,
882 }
883 }
884 } else {
885 let first_status = self
886 .statuses
887 .first()
888 .expect("execution statuses is non-empty");
889 let retries = &self.statuses[1..];
890 ExecutionDescription::Failure {
891 first_status,
892 last_status,
893 retries,
894 }
895 }
896 }
897}
898
899#[derive(Debug)]
905pub enum ExecutionDescription<'a, O> {
906 Success {
908 single_status: &'a ExecuteStatus<O>,
910 },
911
912 Flaky {
914 last_status: &'a ExecuteStatus<O>,
916
917 prior_statuses: &'a [ExecuteStatus<O>],
919 },
920
921 Failure {
923 first_status: &'a ExecuteStatus<O>,
925
926 last_status: &'a ExecuteStatus<O>,
928
929 retries: &'a [ExecuteStatus<O>],
933 },
934}
935
936impl<O> Clone for ExecutionDescription<'_, O> {
939 fn clone(&self) -> Self {
940 *self
941 }
942}
943
944impl<O> Copy for ExecutionDescription<'_, O> {}
945
946impl<'a, O> ExecutionDescription<'a, O> {
947 pub fn status_level(&self) -> StatusLevel {
949 match self {
950 ExecutionDescription::Success { single_status } => match single_status.result {
951 ExecutionResultDescription::Leak {
952 result: LeakTimeoutResult::Pass,
953 } => StatusLevel::Leak,
954 ExecutionResultDescription::Pass => StatusLevel::Pass,
955 ExecutionResultDescription::Timeout {
956 result: SlowTimeoutResult::Pass,
957 } => StatusLevel::Slow,
958 ref other => unreachable!(
959 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
960 ),
961 },
962 ExecutionDescription::Flaky { .. } => StatusLevel::Retry,
964 ExecutionDescription::Failure { .. } => StatusLevel::Fail,
965 }
966 }
967
968 pub fn final_status_level(&self) -> FinalStatusLevel {
970 match self {
971 ExecutionDescription::Success { single_status, .. } => {
972 if single_status.is_slow {
974 FinalStatusLevel::Slow
975 } else {
976 match single_status.result {
977 ExecutionResultDescription::Pass => FinalStatusLevel::Pass,
978 ExecutionResultDescription::Leak {
979 result: LeakTimeoutResult::Pass,
980 } => FinalStatusLevel::Leak,
981 ExecutionResultDescription::Timeout {
985 result: SlowTimeoutResult::Pass,
986 } => FinalStatusLevel::Slow,
987 ref other => unreachable!(
988 "Success only permits Pass, Leak Pass, or Timeout Pass, found {other:?}"
989 ),
990 }
991 }
992 }
993 ExecutionDescription::Flaky { .. } => FinalStatusLevel::Flaky,
995 ExecutionDescription::Failure { .. } => FinalStatusLevel::Fail,
996 }
997 }
998
999 pub fn last_status(&self) -> &'a ExecuteStatus<O> {
1001 match self {
1002 ExecutionDescription::Success {
1003 single_status: last_status,
1004 }
1005 | ExecutionDescription::Flaky { last_status, .. }
1006 | ExecutionDescription::Failure { last_status, .. } => last_status,
1007 }
1008 }
1009}
1010
1011#[derive(Clone, Debug)]
1017pub struct ErrorSummary {
1018 pub short_message: String,
1020
1021 pub description: String,
1023}
1024
1025#[derive(Clone, Debug)]
1030pub struct OutputErrorSlice {
1031 pub slice: String,
1033
1034 pub start: usize,
1036}
1037
1038#[derive(Clone, Debug)]
1048pub struct ExecuteStatus<O> {
1049 pub retry_data: RetryData,
1051 pub output: ChildExecutionOutputDescription<O>,
1053 pub result: ExecutionResultDescription,
1055 pub start_time: DateTime<FixedOffset>,
1057 pub time_taken: Duration,
1059 pub is_slow: bool,
1061 pub delay_before_start: Duration,
1063 pub error_summary: Option<ErrorSummary>,
1068 pub output_error_slice: Option<OutputErrorSlice>,
1073}
1074
1075#[derive(Clone, Debug)]
1083pub struct SetupScriptExecuteStatus<O> {
1084 pub output: ChildExecutionOutputDescription<O>,
1086
1087 pub result: ExecutionResultDescription,
1089
1090 pub start_time: DateTime<FixedOffset>,
1092
1093 pub time_taken: Duration,
1095
1096 pub is_slow: bool,
1098
1099 pub env_map: Option<SetupScriptEnvMap>,
1104
1105 pub error_summary: Option<ErrorSummary>,
1110}
1111
1112#[derive(Clone, Debug)]
1116pub struct SetupScriptEnvMap {
1117 pub env_map: BTreeMap<String, String>,
1119}
1120
1121#[derive(Clone, Debug)]
1133pub enum ChildExecutionOutputDescription<O> {
1134 Output {
1136 result: Option<ExecutionResultDescription>,
1140
1141 output: ChildOutputDescription<O>,
1143
1144 errors: Option<ErrorList<ChildErrorDescription>>,
1147 },
1148
1149 StartError(ChildStartErrorDescription),
1151}
1152
1153impl<O> ChildExecutionOutputDescription<O> {
1154 pub fn has_errors(&self) -> bool {
1156 match self {
1157 Self::Output { errors, result, .. } => {
1158 if errors.is_some() {
1159 return true;
1160 }
1161 if let Some(result) = result {
1162 return !result.is_success();
1163 }
1164 false
1165 }
1166 Self::StartError(_) => true,
1167 }
1168 }
1169}
1170
1171#[derive(Clone, Debug, Serialize, Deserialize)]
1177#[serde(tag = "kind", rename_all = "kebab-case")]
1178pub enum ChildOutputDescription<O> {
1179 Split {
1181 stdout: Option<O>,
1183 stderr: Option<O>,
1185 },
1186
1187 Combined {
1189 output: O,
1191 },
1192}
1193
1194impl ChildOutputDescription<ChildSingleOutput> {
1195 pub fn stdout_stderr_len(&self) -> (Option<u64>, Option<u64>) {
1199 match self {
1200 Self::Split { stdout, stderr } => (
1201 stdout.as_ref().map(|s| s.buf.len() as u64),
1202 stderr.as_ref().map(|s| s.buf.len() as u64),
1203 ),
1204 Self::Combined { output } => (Some(output.buf.len() as u64), None),
1205 }
1206 }
1207}
1208
1209#[derive(Clone, Debug)]
1213pub enum ChildStartErrorDescription {
1214 TempPath {
1216 source: IoErrorDescription,
1218 },
1219
1220 Spawn {
1222 source: IoErrorDescription,
1224 },
1225}
1226
1227impl fmt::Display for ChildStartErrorDescription {
1228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1229 match self {
1230 Self::TempPath { .. } => {
1231 write!(f, "error creating temporary path for setup script")
1232 }
1233 Self::Spawn { .. } => write!(f, "error spawning child process"),
1234 }
1235 }
1236}
1237
1238impl std::error::Error for ChildStartErrorDescription {
1239 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1240 match self {
1241 Self::TempPath { source } | Self::Spawn { source } => Some(source),
1242 }
1243 }
1244}
1245
1246#[derive(Clone, Debug)]
1250pub enum ChildErrorDescription {
1251 ReadStdout {
1253 source: IoErrorDescription,
1255 },
1256
1257 ReadStderr {
1259 source: IoErrorDescription,
1261 },
1262
1263 ReadCombined {
1265 source: IoErrorDescription,
1267 },
1268
1269 Wait {
1271 source: IoErrorDescription,
1273 },
1274
1275 SetupScriptOutput {
1277 source: IoErrorDescription,
1279 },
1280}
1281
1282impl fmt::Display for ChildErrorDescription {
1283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1284 match self {
1285 Self::ReadStdout { .. } => write!(f, "error reading standard output"),
1286 Self::ReadStderr { .. } => write!(f, "error reading standard error"),
1287 Self::ReadCombined { .. } => {
1288 write!(f, "error reading combined stream")
1289 }
1290 Self::Wait { .. } => {
1291 write!(f, "error waiting for child process to exit")
1292 }
1293 Self::SetupScriptOutput { .. } => {
1294 write!(f, "error reading setup script output")
1295 }
1296 }
1297 }
1298}
1299
1300impl std::error::Error for ChildErrorDescription {
1301 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1302 match self {
1303 Self::ReadStdout { source }
1304 | Self::ReadStderr { source }
1305 | Self::ReadCombined { source }
1306 | Self::Wait { source }
1307 | Self::SetupScriptOutput { source } => Some(source),
1308 }
1309 }
1310}
1311
1312#[derive(Clone, Debug)]
1316pub struct IoErrorDescription {
1317 message: String,
1318}
1319
1320impl fmt::Display for IoErrorDescription {
1321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1322 write!(f, "{}", self.message)
1323 }
1324}
1325
1326impl std::error::Error for IoErrorDescription {}
1327
1328impl From<ChildExecutionOutput> for ChildExecutionOutputDescription<ChildSingleOutput> {
1329 fn from(output: ChildExecutionOutput) -> Self {
1330 match output {
1331 ChildExecutionOutput::Output {
1332 result,
1333 output,
1334 errors,
1335 } => Self::Output {
1336 result: result.map(ExecutionResultDescription::from),
1337 output: ChildOutputDescription::from(output),
1338 errors: errors.map(|e| e.map(ChildErrorDescription::from)),
1339 },
1340 ChildExecutionOutput::StartError(error) => {
1341 Self::StartError(ChildStartErrorDescription::from(error))
1342 }
1343 }
1344 }
1345}
1346
1347impl From<ChildOutput> for ChildOutputDescription<ChildSingleOutput> {
1348 fn from(output: ChildOutput) -> Self {
1349 match output {
1350 ChildOutput::Split(split) => Self::Split {
1351 stdout: split.stdout,
1352 stderr: split.stderr,
1353 },
1354 ChildOutput::Combined { output } => Self::Combined { output },
1355 }
1356 }
1357}
1358
1359impl From<ChildStartError> for ChildStartErrorDescription {
1360 fn from(error: ChildStartError) -> Self {
1361 match error {
1362 ChildStartError::TempPath(e) => Self::TempPath {
1363 source: IoErrorDescription {
1364 message: e.to_string(),
1365 },
1366 },
1367 ChildStartError::Spawn(e) => Self::Spawn {
1368 source: IoErrorDescription {
1369 message: e.to_string(),
1370 },
1371 },
1372 }
1373 }
1374}
1375
1376impl From<ChildError> for ChildErrorDescription {
1377 fn from(error: ChildError) -> Self {
1378 match error {
1379 ChildError::Fd(ChildFdError::ReadStdout(e)) => Self::ReadStdout {
1380 source: IoErrorDescription {
1381 message: e.to_string(),
1382 },
1383 },
1384 ChildError::Fd(ChildFdError::ReadStderr(e)) => Self::ReadStderr {
1385 source: IoErrorDescription {
1386 message: e.to_string(),
1387 },
1388 },
1389 ChildError::Fd(ChildFdError::ReadCombined(e)) => Self::ReadCombined {
1390 source: IoErrorDescription {
1391 message: e.to_string(),
1392 },
1393 },
1394 ChildError::Fd(ChildFdError::Wait(e)) => Self::Wait {
1395 source: IoErrorDescription {
1396 message: e.to_string(),
1397 },
1398 },
1399 ChildError::SetupScriptOutput(e) => Self::SetupScriptOutput {
1400 source: IoErrorDescription {
1401 message: e.to_string(),
1402 },
1403 },
1404 }
1405 }
1406}
1407
1408#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
1410pub struct RetryData {
1411 pub attempt: u32,
1413
1414 pub total_attempts: u32,
1416}
1417
1418impl RetryData {
1419 pub fn is_last_attempt(&self) -> bool {
1421 self.attempt >= self.total_attempts
1422 }
1423}
1424
1425#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1427pub enum ExecutionResult {
1428 Pass,
1430 Leak {
1434 result: LeakTimeoutResult,
1441 },
1442 Fail {
1444 failure_status: FailureStatus,
1446
1447 leaked: bool,
1451 },
1452 ExecFail,
1454 Timeout {
1456 result: SlowTimeoutResult,
1458 },
1459}
1460
1461impl ExecutionResult {
1462 pub fn is_success(self) -> bool {
1464 match self {
1465 ExecutionResult::Pass
1466 | ExecutionResult::Timeout {
1467 result: SlowTimeoutResult::Pass,
1468 }
1469 | ExecutionResult::Leak {
1470 result: LeakTimeoutResult::Pass,
1471 } => true,
1472 ExecutionResult::Leak {
1473 result: LeakTimeoutResult::Fail,
1474 }
1475 | ExecutionResult::Fail { .. }
1476 | ExecutionResult::ExecFail
1477 | ExecutionResult::Timeout {
1478 result: SlowTimeoutResult::Fail,
1479 } => false,
1480 }
1481 }
1482
1483 pub fn as_static_str(&self) -> &'static str {
1485 match self {
1486 ExecutionResult::Pass => "pass",
1487 ExecutionResult::Leak { .. } => "leak",
1488 ExecutionResult::Fail { .. } => "fail",
1489 ExecutionResult::ExecFail => "exec-fail",
1490 ExecutionResult::Timeout { .. } => "timeout",
1491 }
1492 }
1493}
1494
1495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1497pub enum FailureStatus {
1498 ExitCode(i32),
1500
1501 Abort(AbortStatus),
1503}
1504
1505impl FailureStatus {
1506 pub fn extract(exit_status: ExitStatus) -> Self {
1508 if let Some(abort_status) = AbortStatus::extract(exit_status) {
1509 FailureStatus::Abort(abort_status)
1510 } else {
1511 FailureStatus::ExitCode(
1512 exit_status
1513 .code()
1514 .expect("if abort_status is None, then code must be present"),
1515 )
1516 }
1517 }
1518}
1519
1520#[derive(Copy, Clone, Eq, PartialEq)]
1524pub enum AbortStatus {
1525 #[cfg(unix)]
1527 UnixSignal(i32),
1528
1529 #[cfg(windows)]
1531 WindowsNtStatus(windows_sys::Win32::Foundation::NTSTATUS),
1532
1533 #[cfg(windows)]
1535 JobObject,
1536}
1537
1538impl AbortStatus {
1539 pub fn extract(exit_status: ExitStatus) -> Option<Self> {
1541 cfg_if::cfg_if! {
1542 if #[cfg(unix)] {
1543 use std::os::unix::process::ExitStatusExt;
1545 exit_status.signal().map(AbortStatus::UnixSignal)
1546 } else if #[cfg(windows)] {
1547 exit_status.code().and_then(|code| {
1548 (code < 0).then_some(AbortStatus::WindowsNtStatus(code))
1549 })
1550 } else {
1551 None
1552 }
1553 }
1554 }
1555}
1556
1557impl fmt::Debug for AbortStatus {
1558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1559 match self {
1560 #[cfg(unix)]
1561 AbortStatus::UnixSignal(signal) => write!(f, "UnixSignal({signal})"),
1562 #[cfg(windows)]
1563 AbortStatus::WindowsNtStatus(status) => write!(f, "WindowsNtStatus({status:x})"),
1564 #[cfg(windows)]
1565 AbortStatus::JobObject => write!(f, "JobObject"),
1566 }
1567 }
1568}
1569
1570#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1576#[serde(tag = "kind", rename_all = "kebab-case")]
1577#[non_exhaustive]
1578pub enum AbortDescription {
1579 UnixSignal {
1581 signal: i32,
1583 name: Option<SmolStr>,
1586 },
1587
1588 WindowsNtStatus {
1590 code: i32,
1592 message: Option<SmolStr>,
1594 },
1595
1596 WindowsJobObject,
1598}
1599
1600impl fmt::Display for AbortDescription {
1601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1602 match self {
1603 Self::UnixSignal { signal, name } => {
1604 write!(f, "aborted with signal {signal}")?;
1605 if let Some(name) = name {
1606 write!(f, " (SIG{name})")?;
1607 }
1608 Ok(())
1609 }
1610 Self::WindowsNtStatus { code, message } => {
1611 write!(f, "aborted with code {code:#010x}")?;
1612 if let Some(message) = message {
1613 write!(f, ": {message}")?;
1614 }
1615 Ok(())
1616 }
1617 Self::WindowsJobObject => {
1618 write!(f, "terminated via job object")
1619 }
1620 }
1621 }
1622}
1623
1624impl From<AbortStatus> for AbortDescription {
1625 fn from(status: AbortStatus) -> Self {
1626 cfg_if::cfg_if! {
1627 if #[cfg(unix)] {
1628 match status {
1629 AbortStatus::UnixSignal(signal) => Self::UnixSignal {
1630 signal,
1631 name: crate::helpers::signal_str(signal).map(SmolStr::new_static),
1632 },
1633 }
1634 } else if #[cfg(windows)] {
1635 match status {
1636 AbortStatus::WindowsNtStatus(code) => Self::WindowsNtStatus {
1637 code,
1638 message: crate::helpers::windows_nt_status_message(code),
1639 },
1640 AbortStatus::JobObject => Self::WindowsJobObject,
1641 }
1642 } else {
1643 match status {}
1644 }
1645 }
1646 }
1647}
1648
1649#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1653#[serde(tag = "kind", rename_all = "kebab-case")]
1654#[non_exhaustive]
1655pub enum FailureDescription {
1656 ExitCode {
1658 code: i32,
1660 },
1661
1662 Abort {
1669 abort: AbortDescription,
1671 },
1672}
1673
1674impl From<FailureStatus> for FailureDescription {
1675 fn from(status: FailureStatus) -> Self {
1676 match status {
1677 FailureStatus::ExitCode(code) => Self::ExitCode { code },
1678 FailureStatus::Abort(abort) => Self::Abort {
1679 abort: AbortDescription::from(abort),
1680 },
1681 }
1682 }
1683}
1684
1685impl fmt::Display for FailureDescription {
1686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1687 match self {
1688 Self::ExitCode { code } => write!(f, "exited with code {code}"),
1689 Self::Abort { abort } => write!(f, "{abort}"),
1690 }
1691 }
1692}
1693
1694#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1699#[serde(tag = "status", rename_all = "kebab-case")]
1700#[non_exhaustive]
1701pub enum ExecutionResultDescription {
1702 Pass,
1704
1705 Leak {
1707 result: LeakTimeoutResult,
1709 },
1710
1711 Fail {
1713 failure: FailureDescription,
1715
1716 leaked: bool,
1718 },
1719
1720 ExecFail,
1722
1723 Timeout {
1725 result: SlowTimeoutResult,
1727 },
1728}
1729
1730impl ExecutionResultDescription {
1731 pub fn is_success(&self) -> bool {
1733 match self {
1734 Self::Pass
1735 | Self::Timeout {
1736 result: SlowTimeoutResult::Pass,
1737 }
1738 | Self::Leak {
1739 result: LeakTimeoutResult::Pass,
1740 } => true,
1741 Self::Leak {
1742 result: LeakTimeoutResult::Fail,
1743 }
1744 | Self::Fail { .. }
1745 | Self::ExecFail
1746 | Self::Timeout {
1747 result: SlowTimeoutResult::Fail,
1748 } => false,
1749 }
1750 }
1751
1752 pub fn as_static_str(&self) -> &'static str {
1754 match self {
1755 Self::Pass => "pass",
1756 Self::Leak { .. } => "leak",
1757 Self::Fail { .. } => "fail",
1758 Self::ExecFail => "exec-fail",
1759 Self::Timeout { .. } => "timeout",
1760 }
1761 }
1762
1763 pub fn is_termination_failure(&self) -> bool {
1775 matches!(
1776 self,
1777 Self::Fail {
1778 failure: FailureDescription::Abort {
1779 abort: AbortDescription::UnixSignal {
1780 signal: SIGTERM,
1781 ..
1782 },
1783 },
1784 ..
1785 } | Self::Fail {
1786 failure: FailureDescription::Abort {
1787 abort: AbortDescription::WindowsJobObject,
1788 },
1789 ..
1790 }
1791 )
1792 }
1793}
1794
1795impl From<ExecutionResult> for ExecutionResultDescription {
1796 fn from(result: ExecutionResult) -> Self {
1797 match result {
1798 ExecutionResult::Pass => Self::Pass,
1799 ExecutionResult::Leak { result } => Self::Leak { result },
1800 ExecutionResult::Fail {
1801 failure_status,
1802 leaked,
1803 } => Self::Fail {
1804 failure: FailureDescription::from(failure_status),
1805 leaked,
1806 },
1807 ExecutionResult::ExecFail => Self::ExecFail,
1808 ExecutionResult::Timeout { result } => Self::Timeout { result },
1809 }
1810 }
1811}
1812
1813#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
1816#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1817pub enum CancelReason {
1818 SetupScriptFailure,
1820
1821 TestFailure,
1823
1824 ReportError,
1826
1827 GlobalTimeout,
1829
1830 TestFailureImmediate,
1832
1833 Signal,
1835
1836 Interrupt,
1838
1839 SecondSignal,
1841}
1842
1843impl CancelReason {
1844 pub(crate) fn to_static_str(self) -> &'static str {
1845 match self {
1846 CancelReason::SetupScriptFailure => "setup script failure",
1847 CancelReason::TestFailure => "test failure",
1848 CancelReason::ReportError => "reporting error",
1849 CancelReason::GlobalTimeout => "global timeout",
1850 CancelReason::TestFailureImmediate => "test failure",
1851 CancelReason::Signal => "signal",
1852 CancelReason::Interrupt => "interrupt",
1853 CancelReason::SecondSignal => "second signal",
1854 }
1855 }
1856}
1857#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1859pub enum UnitKind {
1860 Test,
1862
1863 Script,
1865}
1866
1867impl UnitKind {
1868 pub(crate) const WAITING_ON_TEST_MESSAGE: &str = "waiting on test process";
1869 pub(crate) const WAITING_ON_SCRIPT_MESSAGE: &str = "waiting on script process";
1870
1871 pub(crate) const EXECUTING_TEST_MESSAGE: &str = "executing test";
1872 pub(crate) const EXECUTING_SCRIPT_MESSAGE: &str = "executing script";
1873
1874 pub(crate) fn waiting_on_message(&self) -> &'static str {
1875 match self {
1876 UnitKind::Test => Self::WAITING_ON_TEST_MESSAGE,
1877 UnitKind::Script => Self::WAITING_ON_SCRIPT_MESSAGE,
1878 }
1879 }
1880
1881 pub(crate) fn executing_message(&self) -> &'static str {
1882 match self {
1883 UnitKind::Test => Self::EXECUTING_TEST_MESSAGE,
1884 UnitKind::Script => Self::EXECUTING_SCRIPT_MESSAGE,
1885 }
1886 }
1887}
1888
1889#[derive(Clone, Debug)]
1891pub enum InfoResponse<'a> {
1892 SetupScript(SetupScriptInfoResponse<'a>),
1894
1895 Test(TestInfoResponse<'a>),
1897}
1898
1899#[derive(Clone, Debug)]
1901pub struct SetupScriptInfoResponse<'a> {
1902 pub stress_index: Option<StressIndex>,
1904
1905 pub script_id: ScriptId,
1907
1908 pub program: String,
1910
1911 pub args: &'a [String],
1913
1914 pub state: UnitState,
1916
1917 pub output: ChildExecutionOutputDescription<ChildSingleOutput>,
1919}
1920
1921#[derive(Clone, Debug)]
1923pub struct TestInfoResponse<'a> {
1924 pub stress_index: Option<StressIndex>,
1926
1927 pub test_instance: TestInstanceId<'a>,
1929
1930 pub retry_data: RetryData,
1932
1933 pub state: UnitState,
1935
1936 pub output: ChildExecutionOutputDescription<ChildSingleOutput>,
1938}
1939
1940#[derive(Clone, Debug)]
1945pub enum UnitState {
1946 Running {
1948 pid: u32,
1950
1951 time_taken: Duration,
1953
1954 slow_after: Option<Duration>,
1957 },
1958
1959 Exiting {
1962 pid: u32,
1964
1965 time_taken: Duration,
1967
1968 slow_after: Option<Duration>,
1971
1972 tentative_result: Option<ExecutionResult>,
1977
1978 waiting_duration: Duration,
1980
1981 remaining: Duration,
1983 },
1984
1985 Terminating(UnitTerminatingState),
1987
1988 Exited {
1990 result: ExecutionResult,
1992
1993 time_taken: Duration,
1995
1996 slow_after: Option<Duration>,
1999 },
2000
2001 DelayBeforeNextAttempt {
2004 previous_result: ExecutionResult,
2006
2007 previous_slow: bool,
2009
2010 waiting_duration: Duration,
2012
2013 remaining: Duration,
2015 },
2016}
2017
2018impl UnitState {
2019 pub fn has_valid_output(&self) -> bool {
2021 match self {
2022 UnitState::Running { .. }
2023 | UnitState::Exiting { .. }
2024 | UnitState::Terminating(_)
2025 | UnitState::Exited { .. } => true,
2026 UnitState::DelayBeforeNextAttempt { .. } => false,
2027 }
2028 }
2029}
2030
2031#[derive(Clone, Debug)]
2035pub struct UnitTerminatingState {
2036 pub pid: u32,
2038
2039 pub time_taken: Duration,
2041
2042 pub reason: UnitTerminateReason,
2044
2045 pub method: UnitTerminateMethod,
2047
2048 pub waiting_duration: Duration,
2050
2051 pub remaining: Duration,
2053}
2054
2055#[derive(Clone, Copy, Debug)]
2059pub enum UnitTerminateReason {
2060 Timeout,
2062
2063 Signal,
2065
2066 Interrupt,
2068}
2069
2070impl fmt::Display for UnitTerminateReason {
2071 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2072 match self {
2073 UnitTerminateReason::Timeout => write!(f, "timeout"),
2074 UnitTerminateReason::Signal => write!(f, "signal"),
2075 UnitTerminateReason::Interrupt => write!(f, "interrupt"),
2076 }
2077 }
2078}
2079
2080#[derive(Clone, Copy, Debug)]
2082pub enum UnitTerminateMethod {
2083 #[cfg(unix)]
2085 Signal(UnitTerminateSignal),
2086
2087 #[cfg(windows)]
2089 JobObject,
2090
2091 #[cfg(windows)]
2099 Wait,
2100
2101 #[cfg(test)]
2103 Fake,
2104}
2105
2106#[cfg(unix)]
2107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2109pub enum UnitTerminateSignal {
2110 Interrupt,
2112
2113 Term,
2115
2116 Hangup,
2118
2119 Quit,
2121
2122 Kill,
2124}
2125
2126#[cfg(unix)]
2127impl fmt::Display for UnitTerminateSignal {
2128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2129 match self {
2130 UnitTerminateSignal::Interrupt => write!(f, "SIGINT"),
2131 UnitTerminateSignal::Term => write!(f, "SIGTERM"),
2132 UnitTerminateSignal::Hangup => write!(f, "SIGHUP"),
2133 UnitTerminateSignal::Quit => write!(f, "SIGQUIT"),
2134 UnitTerminateSignal::Kill => write!(f, "SIGKILL"),
2135 }
2136 }
2137}
2138
2139#[cfg(test)]
2140mod tests {
2141 use super::*;
2142
2143 #[test]
2144 fn test_is_success() {
2145 assert_eq!(
2146 RunStats::default().summarize_final(),
2147 FinalRunStats::NoTestsRun,
2148 "empty run => no tests run"
2149 );
2150 assert_eq!(
2151 RunStats {
2152 initial_run_count: 42,
2153 finished_count: 42,
2154 ..RunStats::default()
2155 }
2156 .summarize_final(),
2157 FinalRunStats::Success,
2158 "initial run count = final run count => success"
2159 );
2160 assert_eq!(
2161 RunStats {
2162 initial_run_count: 42,
2163 finished_count: 41,
2164 ..RunStats::default()
2165 }
2166 .summarize_final(),
2167 FinalRunStats::Cancelled {
2168 reason: None,
2169 kind: RunStatsFailureKind::Test {
2170 initial_run_count: 42,
2171 not_run: 1
2172 }
2173 },
2174 "initial run count > final run count => cancelled"
2175 );
2176 assert_eq!(
2177 RunStats {
2178 initial_run_count: 42,
2179 finished_count: 42,
2180 failed: 1,
2181 ..RunStats::default()
2182 }
2183 .summarize_final(),
2184 FinalRunStats::Failed {
2185 kind: RunStatsFailureKind::Test {
2186 initial_run_count: 42,
2187 not_run: 0,
2188 },
2189 },
2190 "failed => failure"
2191 );
2192 assert_eq!(
2193 RunStats {
2194 initial_run_count: 42,
2195 finished_count: 42,
2196 exec_failed: 1,
2197 ..RunStats::default()
2198 }
2199 .summarize_final(),
2200 FinalRunStats::Failed {
2201 kind: RunStatsFailureKind::Test {
2202 initial_run_count: 42,
2203 not_run: 0,
2204 },
2205 },
2206 "exec failed => failure"
2207 );
2208 assert_eq!(
2209 RunStats {
2210 initial_run_count: 42,
2211 finished_count: 42,
2212 failed_timed_out: 1,
2213 ..RunStats::default()
2214 }
2215 .summarize_final(),
2216 FinalRunStats::Failed {
2217 kind: RunStatsFailureKind::Test {
2218 initial_run_count: 42,
2219 not_run: 0,
2220 },
2221 },
2222 "timed out => failure {:?} {:?}",
2223 RunStats {
2224 initial_run_count: 42,
2225 finished_count: 42,
2226 failed_timed_out: 1,
2227 ..RunStats::default()
2228 }
2229 .summarize_final(),
2230 FinalRunStats::Failed {
2231 kind: RunStatsFailureKind::Test {
2232 initial_run_count: 42,
2233 not_run: 0,
2234 },
2235 },
2236 );
2237 assert_eq!(
2238 RunStats {
2239 initial_run_count: 42,
2240 finished_count: 42,
2241 skipped: 1,
2242 ..RunStats::default()
2243 }
2244 .summarize_final(),
2245 FinalRunStats::Success,
2246 "skipped => not considered a failure"
2247 );
2248
2249 assert_eq!(
2250 RunStats {
2251 setup_scripts_initial_count: 2,
2252 setup_scripts_finished_count: 1,
2253 ..RunStats::default()
2254 }
2255 .summarize_final(),
2256 FinalRunStats::Cancelled {
2257 reason: None,
2258 kind: RunStatsFailureKind::SetupScript,
2259 },
2260 "setup script failed => failure"
2261 );
2262
2263 assert_eq!(
2264 RunStats {
2265 setup_scripts_initial_count: 2,
2266 setup_scripts_finished_count: 2,
2267 setup_scripts_failed: 1,
2268 ..RunStats::default()
2269 }
2270 .summarize_final(),
2271 FinalRunStats::Failed {
2272 kind: RunStatsFailureKind::SetupScript,
2273 },
2274 "setup script failed => failure"
2275 );
2276 assert_eq!(
2277 RunStats {
2278 setup_scripts_initial_count: 2,
2279 setup_scripts_finished_count: 2,
2280 setup_scripts_exec_failed: 1,
2281 ..RunStats::default()
2282 }
2283 .summarize_final(),
2284 FinalRunStats::Failed {
2285 kind: RunStatsFailureKind::SetupScript,
2286 },
2287 "setup script exec failed => failure"
2288 );
2289 assert_eq!(
2290 RunStats {
2291 setup_scripts_initial_count: 2,
2292 setup_scripts_finished_count: 2,
2293 setup_scripts_timed_out: 1,
2294 ..RunStats::default()
2295 }
2296 .summarize_final(),
2297 FinalRunStats::Failed {
2298 kind: RunStatsFailureKind::SetupScript,
2299 },
2300 "setup script timed out => failure"
2301 );
2302 assert_eq!(
2303 RunStats {
2304 setup_scripts_initial_count: 2,
2305 setup_scripts_finished_count: 2,
2306 setup_scripts_passed: 2,
2307 ..RunStats::default()
2308 }
2309 .summarize_final(),
2310 FinalRunStats::NoTestsRun,
2311 "setup scripts passed => success, but no tests run"
2312 );
2313 }
2314
2315 #[test]
2316 fn abort_description_serialization() {
2317 let unix_with_name = AbortDescription::UnixSignal {
2319 signal: 15,
2320 name: Some("TERM".into()),
2321 };
2322 let json = serde_json::to_string_pretty(&unix_with_name).unwrap();
2323 insta::assert_snapshot!("abort_unix_signal_with_name", json);
2324 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2325 assert_eq!(unix_with_name, roundtrip);
2326
2327 let unix_no_name = AbortDescription::UnixSignal {
2329 signal: 42,
2330 name: None,
2331 };
2332 let json = serde_json::to_string_pretty(&unix_no_name).unwrap();
2333 insta::assert_snapshot!("abort_unix_signal_no_name", json);
2334 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2335 assert_eq!(unix_no_name, roundtrip);
2336
2337 let windows_nt = AbortDescription::WindowsNtStatus {
2339 code: -1073741510_i32,
2340 message: Some("The application terminated as a result of a CTRL+C.".into()),
2341 };
2342 let json = serde_json::to_string_pretty(&windows_nt).unwrap();
2343 insta::assert_snapshot!("abort_windows_nt_status", json);
2344 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2345 assert_eq!(windows_nt, roundtrip);
2346
2347 let windows_nt_no_msg = AbortDescription::WindowsNtStatus {
2349 code: -1073741819_i32,
2350 message: None,
2351 };
2352 let json = serde_json::to_string_pretty(&windows_nt_no_msg).unwrap();
2353 insta::assert_snapshot!("abort_windows_nt_status_no_message", json);
2354 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2355 assert_eq!(windows_nt_no_msg, roundtrip);
2356
2357 let job = AbortDescription::WindowsJobObject;
2359 let json = serde_json::to_string_pretty(&job).unwrap();
2360 insta::assert_snapshot!("abort_windows_job_object", json);
2361 let roundtrip: AbortDescription = serde_json::from_str(&json).unwrap();
2362 assert_eq!(job, roundtrip);
2363 }
2364
2365 #[test]
2366 fn abort_description_cross_platform_deserialization() {
2367 let unix_json = r#"{"kind":"unix-signal","signal":11,"name":"SEGV"}"#;
2370 let unix_desc: AbortDescription = serde_json::from_str(unix_json).unwrap();
2371 assert_eq!(
2372 unix_desc,
2373 AbortDescription::UnixSignal {
2374 signal: 11,
2375 name: Some("SEGV".into()),
2376 }
2377 );
2378
2379 let windows_json = r#"{"kind":"windows-nt-status","code":-1073741510,"message":"CTRL+C"}"#;
2380 let windows_desc: AbortDescription = serde_json::from_str(windows_json).unwrap();
2381 assert_eq!(
2382 windows_desc,
2383 AbortDescription::WindowsNtStatus {
2384 code: -1073741510,
2385 message: Some("CTRL+C".into()),
2386 }
2387 );
2388
2389 let job_json = r#"{"kind":"windows-job-object"}"#;
2390 let job_desc: AbortDescription = serde_json::from_str(job_json).unwrap();
2391 assert_eq!(job_desc, AbortDescription::WindowsJobObject);
2392 }
2393
2394 #[test]
2395 fn abort_description_display() {
2396 let unix = AbortDescription::UnixSignal {
2398 signal: 15,
2399 name: Some("TERM".into()),
2400 };
2401 assert_eq!(unix.to_string(), "aborted with signal 15 (SIGTERM)");
2402
2403 let unix_no_name = AbortDescription::UnixSignal {
2405 signal: 42,
2406 name: None,
2407 };
2408 assert_eq!(unix_no_name.to_string(), "aborted with signal 42");
2409
2410 let windows = AbortDescription::WindowsNtStatus {
2412 code: -1073741510,
2413 message: Some("CTRL+C exit".into()),
2414 };
2415 assert_eq!(
2416 windows.to_string(),
2417 "aborted with code 0xc000013a: CTRL+C exit"
2418 );
2419
2420 let windows_no_msg = AbortDescription::WindowsNtStatus {
2422 code: -1073741510,
2423 message: None,
2424 };
2425 assert_eq!(windows_no_msg.to_string(), "aborted with code 0xc000013a");
2426
2427 let job = AbortDescription::WindowsJobObject;
2429 assert_eq!(job.to_string(), "terminated via job object");
2430 }
2431
2432 #[cfg(unix)]
2433 #[test]
2434 fn abort_description_from_abort_status() {
2435 let status = AbortStatus::UnixSignal(15);
2437 let description = AbortDescription::from(status);
2438
2439 assert_eq!(
2440 description,
2441 AbortDescription::UnixSignal {
2442 signal: 15,
2443 name: Some("TERM".into()),
2444 }
2445 );
2446
2447 let unknown_status = AbortStatus::UnixSignal(42);
2449 let unknown_description = AbortDescription::from(unknown_status);
2450 assert_eq!(
2451 unknown_description,
2452 AbortDescription::UnixSignal {
2453 signal: 42,
2454 name: None,
2455 }
2456 );
2457 }
2458
2459 #[test]
2460 fn execution_result_description_serialization() {
2461 let pass = ExecutionResultDescription::Pass;
2465 let json = serde_json::to_string_pretty(&pass).unwrap();
2466 insta::assert_snapshot!("pass", json);
2467 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2468 assert_eq!(pass, roundtrip);
2469
2470 let leak_pass = ExecutionResultDescription::Leak {
2472 result: LeakTimeoutResult::Pass,
2473 };
2474 let json = serde_json::to_string_pretty(&leak_pass).unwrap();
2475 insta::assert_snapshot!("leak_pass", json);
2476 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2477 assert_eq!(leak_pass, roundtrip);
2478
2479 let leak_fail = ExecutionResultDescription::Leak {
2481 result: LeakTimeoutResult::Fail,
2482 };
2483 let json = serde_json::to_string_pretty(&leak_fail).unwrap();
2484 insta::assert_snapshot!("leak_fail", json);
2485 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2486 assert_eq!(leak_fail, roundtrip);
2487
2488 let fail_exit_code = ExecutionResultDescription::Fail {
2490 failure: FailureDescription::ExitCode { code: 101 },
2491 leaked: false,
2492 };
2493 let json = serde_json::to_string_pretty(&fail_exit_code).unwrap();
2494 insta::assert_snapshot!("fail_exit_code", json);
2495 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2496 assert_eq!(fail_exit_code, roundtrip);
2497
2498 let fail_exit_code_leaked = ExecutionResultDescription::Fail {
2500 failure: FailureDescription::ExitCode { code: 1 },
2501 leaked: true,
2502 };
2503 let json = serde_json::to_string_pretty(&fail_exit_code_leaked).unwrap();
2504 insta::assert_snapshot!("fail_exit_code_leaked", json);
2505 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2506 assert_eq!(fail_exit_code_leaked, roundtrip);
2507
2508 let fail_unix_signal = ExecutionResultDescription::Fail {
2510 failure: FailureDescription::Abort {
2511 abort: AbortDescription::UnixSignal {
2512 signal: 11,
2513 name: Some("SEGV".into()),
2514 },
2515 },
2516 leaked: false,
2517 };
2518 let json = serde_json::to_string_pretty(&fail_unix_signal).unwrap();
2519 insta::assert_snapshot!("fail_unix_signal", json);
2520 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2521 assert_eq!(fail_unix_signal, roundtrip);
2522
2523 let fail_unix_signal_unknown = ExecutionResultDescription::Fail {
2525 failure: FailureDescription::Abort {
2526 abort: AbortDescription::UnixSignal {
2527 signal: 42,
2528 name: None,
2529 },
2530 },
2531 leaked: true,
2532 };
2533 let json = serde_json::to_string_pretty(&fail_unix_signal_unknown).unwrap();
2534 insta::assert_snapshot!("fail_unix_signal_unknown_leaked", json);
2535 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2536 assert_eq!(fail_unix_signal_unknown, roundtrip);
2537
2538 let fail_windows_nt = ExecutionResultDescription::Fail {
2540 failure: FailureDescription::Abort {
2541 abort: AbortDescription::WindowsNtStatus {
2542 code: -1073741510,
2543 message: Some("The application terminated as a result of a CTRL+C.".into()),
2544 },
2545 },
2546 leaked: false,
2547 };
2548 let json = serde_json::to_string_pretty(&fail_windows_nt).unwrap();
2549 insta::assert_snapshot!("fail_windows_nt_status", json);
2550 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2551 assert_eq!(fail_windows_nt, roundtrip);
2552
2553 let fail_windows_nt_no_msg = ExecutionResultDescription::Fail {
2555 failure: FailureDescription::Abort {
2556 abort: AbortDescription::WindowsNtStatus {
2557 code: -1073741819,
2558 message: None,
2559 },
2560 },
2561 leaked: false,
2562 };
2563 let json = serde_json::to_string_pretty(&fail_windows_nt_no_msg).unwrap();
2564 insta::assert_snapshot!("fail_windows_nt_status_no_message", json);
2565 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2566 assert_eq!(fail_windows_nt_no_msg, roundtrip);
2567
2568 let fail_job_object = ExecutionResultDescription::Fail {
2570 failure: FailureDescription::Abort {
2571 abort: AbortDescription::WindowsJobObject,
2572 },
2573 leaked: false,
2574 };
2575 let json = serde_json::to_string_pretty(&fail_job_object).unwrap();
2576 insta::assert_snapshot!("fail_windows_job_object", json);
2577 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2578 assert_eq!(fail_job_object, roundtrip);
2579
2580 let exec_fail = ExecutionResultDescription::ExecFail;
2582 let json = serde_json::to_string_pretty(&exec_fail).unwrap();
2583 insta::assert_snapshot!("exec_fail", json);
2584 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2585 assert_eq!(exec_fail, roundtrip);
2586
2587 let timeout_pass = ExecutionResultDescription::Timeout {
2589 result: SlowTimeoutResult::Pass,
2590 };
2591 let json = serde_json::to_string_pretty(&timeout_pass).unwrap();
2592 insta::assert_snapshot!("timeout_pass", json);
2593 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2594 assert_eq!(timeout_pass, roundtrip);
2595
2596 let timeout_fail = ExecutionResultDescription::Timeout {
2598 result: SlowTimeoutResult::Fail,
2599 };
2600 let json = serde_json::to_string_pretty(&timeout_fail).unwrap();
2601 insta::assert_snapshot!("timeout_fail", json);
2602 let roundtrip: ExecutionResultDescription = serde_json::from_str(&json).unwrap();
2603 assert_eq!(timeout_fail, roundtrip);
2604 }
2605}