use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum Outcome {
Passed,
Failed {
message: String,
},
Skipped {
reason: Option<String>,
},
Error {
message: String,
},
XFail {
reason: Option<String>,
},
XPass,
}
impl Outcome {
pub fn is_success(&self) -> bool {
matches!(
self,
Outcome::Passed | Outcome::Skipped { .. } | Outcome::XFail { .. }
)
}
pub fn is_failure(&self) -> bool {
matches!(self, Outcome::Failed { .. } | Outcome::XPass)
}
pub fn is_error(&self) -> bool {
matches!(self, Outcome::Error { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum TestEvent {
TestStart {
node_id: String,
},
TestFinish {
node_id: String,
outcome: Outcome,
duration_ms: u64,
stdout: Option<String>,
stderr: Option<String>,
},
CollectionStart,
ItemCollected {
node_id: String,
file_path: String,
line_number: Option<u32>,
markers: Vec<String>,
},
CollectionFinish {
count: usize,
},
SessionStart {
test_count: usize,
},
SessionFinish {
exit_code: i32,
},
Warning {
message: String,
location: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LogEvent {
pub level: LogLevel,
pub message: String,
pub module: Option<String>,
pub timestamp_ms: u64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
Critical,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn outcome_classification() {
assert!(Outcome::Passed.is_success());
assert!(Outcome::Skipped { reason: None }.is_success());
assert!(Outcome::XFail { reason: None }.is_success());
assert!(Outcome::Failed {
message: "".to_string()
}
.is_failure());
assert!(Outcome::XPass.is_failure());
assert!(Outcome::Error {
message: "".to_string()
}
.is_error());
}
#[test]
fn test_event_roundtrip() {
let events = vec![
TestEvent::TestStart {
node_id: "test_foo.py::test_bar".to_string(),
},
TestEvent::TestFinish {
node_id: "test_foo.py::test_bar".to_string(),
outcome: Outcome::Passed,
duration_ms: 42,
stdout: Some("output".to_string()),
stderr: None,
},
TestEvent::CollectionStart,
TestEvent::ItemCollected {
node_id: "test_foo.py::test_bar".to_string(),
file_path: "test_foo.py".to_string(),
line_number: Some(10),
markers: vec!["slow".to_string()],
},
TestEvent::CollectionFinish { count: 100 },
TestEvent::SessionStart { test_count: 50 },
TestEvent::SessionFinish { exit_code: 0 },
TestEvent::Warning {
message: "Deprecated API".to_string(),
location: Some("test_foo.py:20".to_string()),
},
];
for event in events {
let encoded = rmp_serde::to_vec(&event).unwrap();
let decoded: TestEvent = rmp_serde::from_slice(&encoded).unwrap();
assert_eq!(event, decoded);
}
}
}