use serde::{Deserialize, Serialize};
use crate::convert::ResolvedPoint;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StepOutcome {
Verified,
Executed,
Skipped,
Failed,
Refused,
}
impl StepOutcome {
pub fn name(self) -> &'static str {
match self {
Self::Verified => "verified",
Self::Executed => "executed",
Self::Skipped => "skipped",
Self::Failed => "failed",
Self::Refused => "refused",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StepReport {
pub index: usize,
pub summary: String,
pub outcome: StepOutcome,
pub points: Vec<ResolvedPoint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
pub elapsed_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RunReport {
pub schema: u32,
pub session: String,
pub executed: bool,
pub steps: Vec<StepReport>,
}
impl RunReport {
pub const SCHEMA: u32 = 1;
pub fn exit_code(&self) -> i32 {
if self.steps.iter().any(|s| s.outcome == StepOutcome::Refused) {
return 3;
}
if self.steps.iter().any(|s| s.outcome == StepOutcome::Failed) {
return 1;
}
0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn step(index: usize, outcome: StepOutcome) -> StepReport {
StepReport {
index,
summary: format!("step {index}"),
outcome,
points: Vec::new(),
detail: None,
elapsed_ms: 1,
}
}
fn report(steps: Vec<StepReport>) -> RunReport {
RunReport {
schema: RunReport::SCHEMA,
session: "s".into(),
executed: true,
steps,
}
}
#[test]
fn a_clean_run_exits_zero() {
let run = report(vec![
step(0, StepOutcome::Verified),
step(1, StepOutcome::Executed),
]);
assert_eq!(run.exit_code(), 0);
}
#[test]
fn any_failure_exits_one() {
let run = report(vec![
step(0, StepOutcome::Verified),
step(1, StepOutcome::Failed),
]);
assert_eq!(run.exit_code(), 1);
}
#[test]
fn skipped_steps_do_not_themselves_fail_the_run() {
let run = report(vec![
step(0, StepOutcome::Executed),
step(1, StepOutcome::Skipped),
]);
assert_eq!(run.exit_code(), 0);
}
#[test]
fn executed_and_verified_are_distinct_in_the_wire_format() {
let executed = serde_json::to_string(&StepOutcome::Executed).expect("serialize");
let verified = serde_json::to_string(&StepOutcome::Verified).expect("serialize");
assert_eq!(executed, "\"executed\"");
assert_eq!(verified, "\"verified\"");
}
#[test]
fn a_refusal_exits_three_not_one() {
let run = report(vec![step(0, StepOutcome::Refused)]);
assert_eq!(run.exit_code(), 3);
}
#[test]
fn the_printed_name_is_the_wire_name() {
for outcome in [
StepOutcome::Verified,
StepOutcome::Executed,
StepOutcome::Skipped,
StepOutcome::Failed,
StepOutcome::Refused,
] {
let json = serde_json::to_string(&outcome).expect("serialize");
assert_eq!(json, format!("\"{}\"", outcome.name()));
}
}
#[test]
fn a_report_round_trips() {
let run = report(vec![step(0, StepOutcome::Verified)]);
let text = serde_json::to_string(&run).expect("serialize");
let back: RunReport = serde_json::from_str(&text).expect("deserialize");
assert_eq!(run, back);
}
}