pixelactions_core/
report.rs1use serde::{Deserialize, Serialize};
9
10use crate::convert::ResolvedPoint;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum StepOutcome {
16 Verified,
18 Executed,
21 Skipped,
23 Failed,
25 Refused,
31}
32
33impl StepOutcome {
34 pub fn name(self) -> &'static str {
39 match self {
40 Self::Verified => "verified",
41 Self::Executed => "executed",
42 Self::Skipped => "skipped",
43 Self::Failed => "failed",
44 Self::Refused => "refused",
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct StepReport {
52 pub index: usize,
53 pub summary: String,
54 pub outcome: StepOutcome,
55 pub points: Vec<ResolvedPoint>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub detail: Option<String>,
59 pub elapsed_ms: u64,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct RunReport {
65 pub schema: u32,
66 pub session: String,
67 pub executed: bool,
70 pub steps: Vec<StepReport>,
71}
72
73impl RunReport {
74 pub const SCHEMA: u32 = 1;
75
76 pub fn exit_code(&self) -> i32 {
83 if self.steps.iter().any(|s| s.outcome == StepOutcome::Refused) {
84 return 3;
85 }
86 if self.steps.iter().any(|s| s.outcome == StepOutcome::Failed) {
87 return 1;
88 }
89 0
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 fn step(index: usize, outcome: StepOutcome) -> StepReport {
98 StepReport {
99 index,
100 summary: format!("step {index}"),
101 outcome,
102 points: Vec::new(),
103 detail: None,
104 elapsed_ms: 1,
105 }
106 }
107
108 fn report(steps: Vec<StepReport>) -> RunReport {
109 RunReport {
110 schema: RunReport::SCHEMA,
111 session: "s".into(),
112 executed: true,
113 steps,
114 }
115 }
116
117 #[test]
118 fn a_clean_run_exits_zero() {
119 let run = report(vec![
120 step(0, StepOutcome::Verified),
121 step(1, StepOutcome::Executed),
122 ]);
123 assert_eq!(run.exit_code(), 0);
124 }
125
126 #[test]
127 fn any_failure_exits_one() {
128 let run = report(vec![
129 step(0, StepOutcome::Verified),
130 step(1, StepOutcome::Failed),
131 ]);
132 assert_eq!(run.exit_code(), 1);
133 }
134
135 #[test]
136 fn skipped_steps_do_not_themselves_fail_the_run() {
137 let run = report(vec![
140 step(0, StepOutcome::Executed),
141 step(1, StepOutcome::Skipped),
142 ]);
143 assert_eq!(run.exit_code(), 0);
144 }
145
146 #[test]
147 fn executed_and_verified_are_distinct_in_the_wire_format() {
148 let executed = serde_json::to_string(&StepOutcome::Executed).expect("serialize");
149 let verified = serde_json::to_string(&StepOutcome::Verified).expect("serialize");
150 assert_eq!(executed, "\"executed\"");
151 assert_eq!(verified, "\"verified\"");
152 }
153
154 #[test]
155 fn a_refusal_exits_three_not_one() {
156 let run = report(vec![step(0, StepOutcome::Refused)]);
159 assert_eq!(run.exit_code(), 3);
160 }
161
162 #[test]
163 fn the_printed_name_is_the_wire_name() {
164 for outcome in [
165 StepOutcome::Verified,
166 StepOutcome::Executed,
167 StepOutcome::Skipped,
168 StepOutcome::Failed,
169 StepOutcome::Refused,
170 ] {
171 let json = serde_json::to_string(&outcome).expect("serialize");
172 assert_eq!(json, format!("\"{}\"", outcome.name()));
173 }
174 }
175
176 #[test]
177 fn a_report_round_trips() {
178 let run = report(vec![step(0, StepOutcome::Verified)]);
179 let text = serde_json::to_string(&run).expect("serialize");
180 let back: RunReport = serde_json::from_str(&text).expect("deserialize");
181 assert_eq!(run, back);
182 }
183}