use crate::{error::TestErrorDetails, runner::test_repo::TestRepo};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, collections::HashSet, time::Duration};
#[derive(Serialize, Deserialize, Debug)]
pub struct ExecutionResult {
pub(crate) status: ExecutionStatus,
#[serde(flatten)]
pub(crate) execution_time: Duration,
#[cfg(feature = "capture_tracing")]
pub(crate) tracing_logs: String,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum ExecutionStatus {
Success,
Error(TestErrorDetails),
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ExecutionRecord {
pub(crate) test_id: u64,
#[serde(flatten)]
pub(crate) result: ExecutionResult,
}
#[derive(Debug, Default)]
pub struct ExecutionResultMap {
records: Vec<ExecutionRecord>,
executed: HashSet<u64>, successes: usize,
errors: usize,
}
impl ExecutionResultMap {
pub fn records(&self) -> &Vec<ExecutionRecord> { &self.records }
pub fn into_records(self) -> Vec<ExecutionRecord> { self.records }
pub fn with_records(records: Vec<ExecutionRecord>) -> Self {
let mut map = ExecutionResultMap::default();
for record in records {
map.record(record.test_id, record.result);
}
map
}
pub fn results_of(&self, test_id: u64) -> Vec<&ExecutionResult> {
self.records
.iter()
.filter(|r| r.test_id == test_id)
.map(|r| &r.result)
.collect()
}
pub fn did_execute(&self, test_id: u64) -> bool { self.executed.contains(&test_id) }
pub fn did_complete(&self, repo: &TestRepo) -> bool {
!repo
.cases()
.iter()
.filter(|(_, case)| !case.info.test_arguments.optional)
.map(|(key, _)| key)
.any(|key| !self.did_execute(*key))
}
pub fn exitcode(&self) -> u8 {
let mut exitcode_prio = None;
for r in &self.records {
if let ExecutionStatus::Error(e) = &r.result.status {
match exitcode_prio {
None => exitcode_prio = Some(e.exitcode_prio),
Some(sp) => {
if (e.exitcode_prio.1 > sp.1) || (e.exitcode_prio.1 == sp.1 && e.exitcode_prio.0 > sp.0) {
exitcode_prio = Some(e.exitcode_prio);
}
},
}
}
}
exitcode_prio.map(|sp| sp.0).unwrap_or(0)
}
pub fn record(&mut self, test_id: u64, result: ExecutionResult) {
match result.status {
ExecutionStatus::Success => self.successes += 1,
ExecutionStatus::Error(_) => self.errors += 1,
}
self.records.push(ExecutionRecord { test_id, result });
self.executed.insert(test_id);
}
pub fn successes(&self) -> usize { self.successes }
pub fn errors(&self) -> usize { self.errors }
}
impl ExecutionResult {
pub fn avg_runtime(results: &[&ExecutionResult]) -> Option<Duration> {
if results.is_empty() {
None
} else {
Some(results.iter().map(|r| r.execution_time).sum::<Duration>() / results.len() as u32)
}
}
}
impl ExecutionStatus {
pub fn successful(statuses: &[&ExecutionStatus]) -> bool {
if statuses.is_empty() {
return false;
}
for status in statuses {
if let ExecutionStatus::Error(e) = &status {
if e.fails {
return false;
}
}
}
true
}
}
impl Ord for ExecutionStatus {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(ExecutionStatus::Success, ExecutionStatus::Success) => Ordering::Equal,
(ExecutionStatus::Error(_), ExecutionStatus::Success) => Ordering::Greater,
(ExecutionStatus::Success, ExecutionStatus::Error(_)) => Ordering::Less,
(ExecutionStatus::Error(_), ExecutionStatus::Error(_)) => Ordering::Equal, }
}
}
impl PartialOrd for ExecutionStatus {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
#[cfg(test)]
mod tests {
use crate::{runner::panic::PanicError, TestError};
use super::*;
#[test]
fn successfuls() {
let detail = TestErrorDetails::new(Box::new(PanicError { details: None }));
let detail1 = TestErrorDetails::new(Box::new(PanicError { details: None }));
let detail2 = TestErrorDetails::new(Box::new(PanicError { details: None }));
assert!(!ExecutionStatus::successful(&[
&ExecutionStatus::Success,
&ExecutionStatus::Error(detail)
]));
assert!(!ExecutionStatus::successful(&[
&ExecutionStatus::Error(detail1),
&ExecutionStatus::Success,
&ExecutionStatus::Error(detail2)
]));
assert!(ExecutionStatus::successful(&[
&ExecutionStatus::Success,
&ExecutionStatus::Success
]));
assert!(ExecutionStatus::successful(&[&ExecutionStatus::Success]));
assert!(!ExecutionStatus::successful(&[]));
}
#[test]
fn sort_test_case_status() {
let detail = TestErrorDetails::new(Box::new(PanicError { details: None }));
let detail1 = TestErrorDetails::new(Box::new(PanicError { details: None }));
let mut statuses = vec![
Some(ExecutionStatus::Success),
None,
Some(ExecutionStatus::Error(detail)),
];
statuses.sort();
assert_eq!(statuses, vec![
None,
Some(ExecutionStatus::Success),
Some(ExecutionStatus::Error(detail1))
]);
}
fn fake_result(status: ExecutionStatus) -> ExecutionResult {
ExecutionResult {
execution_time: Duration::from_secs(1),
tracing_logs: "".to_string(),
status,
}
}
#[test]
fn exitcode_success() {
let mut map = ExecutionResultMap::default();
map.record(0, fake_result(ExecutionStatus::Success));
map.record(1, fake_result(ExecutionStatus::Success));
map.record(2, fake_result(ExecutionStatus::Success));
assert_eq!(map.exitcode(), 0);
}
#[test]
fn exitcode_fail() {
let mut map = ExecutionResultMap::default();
let detail = TestErrorDetails::new(Box::new(PanicError { details: None }));
map.record(0, fake_result(ExecutionStatus::Success));
map.record(1, fake_result(ExecutionStatus::Error(detail)));
map.record(2, fake_result(ExecutionStatus::Success));
assert_eq!(map.exitcode(), 1);
}
#[derive(Debug)]
struct E1 {}
#[derive(Debug)]
struct E2 {}
impl std::fmt::Display for E1 {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
}
impl std::fmt::Display for E2 {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
}
impl std::error::Error for E1 {}
impl TestError for E1 {
fn exitcode(&self) -> (u8, u64) { (3, 100) }
}
impl std::error::Error for E2 {}
impl TestError for E2 {
fn exitcode(&self) -> (u8, u64) { (4, 100) }
}
#[test]
fn exitcode_fail_higher() {
let mut map = ExecutionResultMap::default();
let detail1 = TestErrorDetails::new(Box::new(E1 {}));
let detail2 = TestErrorDetails::new(Box::new(E2 {}));
map.record(0, fake_result(ExecutionStatus::Success));
map.record(1, fake_result(ExecutionStatus::Error(detail1)));
map.record(2, fake_result(ExecutionStatus::Error(detail2)));
map.record(3, fake_result(ExecutionStatus::Success));
assert_eq!(map.exitcode(), 4);
}
}