use super::generator::TestCase;
use crate::{Error, Machine, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TestResult {
pub test_name: String,
pub success: bool,
pub actual_state: String,
pub expected_state: String,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CoverageReport {
pub visited_states: HashSet<String>,
pub visited_transitions: HashSet<String>,
pub total_states: usize,
pub total_transitions: usize,
}
impl CoverageReport {
pub fn state_coverage(&self) -> f64 {
if self.total_states == 0 {
return 0.0;
}
(self.visited_states.len() as f64 / self.total_states as f64) * 100.0
}
pub fn transition_coverage(&self) -> f64 {
if self.total_transitions == 0 {
return 0.0;
}
(self.visited_transitions.len() as f64 / self.total_transitions as f64) * 100.0
}
}
#[derive(Clone, Debug)]
pub struct TestResults {
pub results: Vec<TestResult>,
pub coverage: CoverageReport,
}
impl TestResults {
pub fn get_coverage(&self) -> &CoverageReport {
&self.coverage
}
pub fn success_count(&self) -> usize {
self.results.iter().filter(|r| r.success).count()
}
pub fn failure_count(&self) -> usize {
self.results.iter().filter(|r| !r.success).count()
}
pub fn total_count(&self) -> usize {
self.results.len()
}
pub fn success_rate(&self) -> f64 {
if self.total_count() == 0 {
return 0.0;
}
(self.success_count() as f64 / self.total_count() as f64) * 100.0
}
}
#[derive(Debug)]
pub struct TestRunner<'a> {
machine: &'a Machine,
visited_states: HashSet<String>,
visited_transitions: HashSet<String>,
}
impl<'a> TestRunner<'a> {
pub fn new(machine: &'a Machine) -> Self {
Self {
machine,
visited_states: HashSet::new(),
visited_transitions: HashSet::new(),
}
}
pub fn run_test(&mut self, test_case: &TestCase) -> TestResult {
let mut machine_clone = self.machine.clone();
if test_case.initial_state != self.machine.initial {
match self.initialize_to_state(&mut machine_clone, &test_case.initial_state) {
Ok(_) => {}
Err(err) => {
return TestResult {
test_name: test_case.name.clone(),
success: false,
actual_state: machine_clone
.current_states
.iter()
.next()
.unwrap_or(&"unknown".to_string())
.clone(),
expected_state: test_case.initial_state.clone(),
error_message: Some(format!("Failed to initialize to state: {}", err)),
};
}
}
}
self.visited_states.insert(
machine_clone
.current_states
.iter()
.next()
.unwrap_or(&"unknown".to_string())
.clone(),
);
for event in &test_case.events {
let current_state = machine_clone
.current_states
.iter()
.next()
.unwrap_or(&"unknown".to_string())
.clone();
match machine_clone.send(event.clone()) {
Ok(_) => {}
Err(err) => {
return TestResult {
test_name: test_case.name.clone(),
success: false,
actual_state: current_state,
expected_state: test_case.expected_state.clone(),
error_message: Some(format!("Error sending event: {}", err)),
};
}
}
let new_state = machine_clone
.current_states
.iter()
.next()
.unwrap_or(&"unknown".to_string())
.clone();
self.visited_states.insert(new_state.clone());
self.visited_transitions.insert(format!(
"{} --{}--> {}",
current_state, event.event_type, new_state
));
}
let final_state = machine_clone
.current_states
.iter()
.next()
.unwrap_or(&"unknown".to_string())
.clone();
let success = final_state == test_case.expected_state;
TestResult {
test_name: test_case.name.clone(),
success,
actual_state: final_state.clone(),
expected_state: test_case.expected_state.clone(),
error_message: if success {
None
} else {
Some(format!(
"Expected state: {}, but got: {}",
test_case.expected_state, final_state
))
},
}
}
pub fn run_tests(&mut self, test_cases: Vec<TestCase>) -> TestResults {
let mut results = Vec::new();
for test_case in test_cases {
let result = self.run_test(&test_case);
results.push(result);
}
let coverage = CoverageReport {
visited_states: self.visited_states.clone(),
visited_transitions: self.visited_transitions.clone(),
total_states: self.machine.states.len(),
total_transitions: self.machine.transitions.len(),
};
TestResults { results, coverage }
}
fn initialize_to_state(&self, machine: &mut Machine, target_state: &str) -> Result<()> {
if machine.is_in(target_state) {
return Ok(());
}
Err(Error::InvalidConfiguration(format!(
"Cannot initialize to state: {}. This is a limitation of the current implementation.",
target_state
)))
}
}