use crate::tester::*;
use crate::{artifact::JsonTrace, TestError};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value as JsonValue;
use std::iter::Iterator;
use std::{any::Any, fmt::Debug, panic::UnwindSafe};
#[allow(unused_variables)]
pub trait StateHandler<State> {
fn init(&mut self, state: State);
fn read(&self) -> State;
}
pub trait ActionHandler<Action> {
type Outcome;
fn init(&mut self) {}
fn handle(&mut self, action: Action) -> Self::Outcome;
}
#[derive(Debug)]
pub enum Event {
Init(Box<dyn Any>),
Action(Box<dyn Any>),
Expect(String),
Check(Box<dyn Any>),
Equal(Box<dyn Any>),
}
#[derive(Debug)]
pub struct EventStream {
events: Vec<Event>,
}
impl Default for EventStream {
fn default() -> Self {
Self::new()
}
}
impl EventStream {
pub const fn new() -> Self {
Self { events: vec![] }
}
pub fn add_init<T>(&mut self, state: T)
where
T: 'static,
{
self.events.push(Event::Init(Box::new(state)));
}
pub fn init<T>(mut self, state: T) -> Self
where
T: 'static,
{
self.add_init(state);
self
}
pub fn add_action<T>(&mut self, action: T)
where
T: 'static,
{
self.events.push(Event::Action(Box::new(action)));
}
pub fn action<T>(mut self, action: T) -> Self
where
T: 'static,
{
self.add_action(action);
self
}
pub fn add_expect<T>(&mut self, outcome: T)
where
T: 'static + Serialize,
{
self.events.push(Event::Expect(
serde_json::to_string_pretty(&outcome).unwrap(),
));
}
pub fn expect<T>(mut self, outcome: T) -> Self
where
T: 'static + Serialize,
{
self.add_expect(outcome);
self
}
pub fn add_check<T>(&mut self, assertion: fn(T))
where
T: 'static,
{
self.events.push(Event::Check(Box::new(assertion)));
}
pub fn check<T>(mut self, assertion: fn(T)) -> Self
where
T: 'static,
{
self.add_check(assertion);
self
}
pub fn add_equal<T>(&mut self, state: T)
where
T: 'static,
{
self.events.push(Event::Equal(Box::new(state)));
}
pub fn equal<T>(mut self, state: T) -> Self
where
T: 'static,
{
self.add_equal(state);
self
}
}
impl IntoIterator for EventStream {
type Item = Event;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.events.into_iter()
}
}
impl From<JsonTrace> for EventStream {
fn from(trace: JsonTrace) -> Self {
let mut events = Self::new();
for (index, value) in trace.into_iter().enumerate() {
if index == 0 {
events.add_init(value);
} else {
if let JsonValue::Object(value) = value.clone() {
if let Some(action) = value.get("action") {
events.add_action(action.clone());
};
if let Some(outcome) = value.get("actionOutcome") {
events.add_expect(outcome.clone());
}
}
events.add_equal(value);
}
}
events
}
}
pub struct EventRunner<System: Debug> {
inits: SystemTester<System>,
actions: SystemTester<System>,
checks: SystemTester<System>,
equals: SystemTester<System>,
outcome: String,
}
impl<System: Debug> Default for EventRunner<System> {
fn default() -> Self {
Self::new()
}
}
impl<System: Debug> EventRunner<System> {
pub fn new() -> Self {
Self {
inits: SystemTester::new(),
actions: SystemTester::new(),
checks: SystemTester::new(),
equals: SystemTester::new(),
outcome: String::new(),
}
}
pub fn with_state<State>(mut self) -> Self
where
State: 'static + DeserializeOwned + UnwindSafe + Clone + Debug + PartialEq,
System: 'static + StateHandler<State>,
{
self.inits.add(StateHandler::<State>::init);
self.checks
.add_fn(|system, assertion: fn(State)| assertion(system.read()));
self.equals
.add(|system, state: State| assert_eq!(system.read(), state));
self
}
pub fn with_action<Action>(mut self) -> Self
where
Action: 'static + DeserializeOwned + UnwindSafe + Clone,
System: 'static + ActionHandler<Action>,
<System as ActionHandler<Action>>::Outcome: 'static + Serialize,
{
self.actions.add(ActionHandler::<Action>::handle);
self
}
pub fn run(
&mut self,
system: &mut System,
stream: &mut dyn Iterator<Item = Event>,
) -> Result<(), TestError> {
for event in stream {
let result = match event {
Event::Init(input) => self.inits.test(system, &input),
Event::Action(input) => self.actions.test(system, &input),
Event::Expect(expected) => {
if self.outcome == expected {
TestResult::Success(self.outcome.clone())
} else {
TestResult::Failure {
message: format!(
"Expected action outcome '{}', got '{}'",
expected, self.outcome
),
location: String::new(),
}
}
}
Event::Check(assertion) => self.checks.test(system, &assertion),
Event::Equal(state) => self.equals.test(system, &state),
};
match result {
TestResult::Success(res) => self.outcome = res,
TestResult::Failure { message, location } => {
return Err(TestError::FailedTest {
message,
location,
test: "".to_string(), system: format!("{:?}", system),
});
}
TestResult::Unhandled => {
return Err(TestError::UnhandledTest {
test: "".to_string(), system: format!("{:?}", system),
});
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::artifact::JsonTrace;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
struct State1 {
state1: String,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
struct State2 {
state2: String,
}
#[derive(Deserialize, Serialize, Clone)]
struct Action1 {
value1: String,
}
#[derive(Serialize)]
enum Outcome {
Success(String),
Failure(String),
}
#[derive(Deserialize, Serialize, Clone)]
struct Action2 {
value2: String,
}
#[derive(Debug, Default)]
struct MySystem {
state1: String,
state2: String,
}
impl StateHandler<State1> for MySystem {
fn init(&mut self, state: State1) {
self.state1 = state.state1;
}
fn read(&self) -> State1 {
State1 {
state1: self.state1.clone(),
}
}
}
impl StateHandler<State2> for MySystem {
fn init(&mut self, state: State2) {
self.state2 = state.state2;
}
fn read(&self) -> State2 {
State2 {
state2: self.state2.clone(),
}
}
}
impl ActionHandler<Action1> for MySystem {
type Outcome = Outcome;
fn handle(&mut self, action: Action1) -> Outcome {
self.state1 = action.value1;
Outcome::Success("OK".to_string())
}
}
impl ActionHandler<Action2> for MySystem {
type Outcome = Outcome;
fn handle(&mut self, action: Action2) -> Outcome {
self.state2 = action.value2;
Outcome::Failure("NOT OK".to_string())
}
}
#[test]
fn test_stream() {
let events = EventStream::new()
.init(State1 {
state1: "init state 1".to_string(),
})
.init(State2 {
state2: "init state 2".to_string(),
})
.action(Action1 {
value1: "action1 state".to_string(),
})
.expect(Outcome::Success("OK".to_string()))
.action(Action2 {
value2: "action2 state".to_string(),
})
.expect(Outcome::Failure("NOT OK".to_string()))
.check(|state: State1| assert!(state.state1 == "action1 state"))
.equal(State2 {
state2: "action2 state".to_string(),
});
let mut runner = EventRunner::new()
.with_state::<State1>()
.with_state::<State2>()
.with_action::<Action1>()
.with_action::<Action2>();
let mut system = MySystem::default();
let result = runner.run(&mut system, &mut events.into_iter());
assert!(result.is_ok());
}
#[test]
fn test_json_trace() {
let mut system = MySystem::default();
let mut runner = EventRunner::new()
.with_state::<State1>()
.with_state::<State2>()
.with_action::<Action1>()
.with_action::<Action2>();
let trace: JsonTrace = vec![
r#"{ "state1": "init state 1", "state2": "init state 2" }"#,
r#"{ "action": { "value3": "action1 state" },
"state1": "action1 state", "state2": "init state 2" }"#,
]
.into_iter()
.map(|x| serde_json::from_str(x).unwrap())
.collect::<Vec<Value>>()
.into();
let events: EventStream = trace.into();
let result = runner.run(&mut system, &mut events.into_iter());
assert!(matches!(result, Err(TestError::UnhandledTest { .. })));
let trace: JsonTrace = vec![
r#"{ "state1": "init state 1", "state2": "init state 2" }"#,
r#"{ "action": { "value1": "action1 state" },
"actionOutcome": { "Success": "OK" },
"state1": "action1 state", "state2": "init state 2" }"#,
r#"{ "action": { "value2": "action2 state" },
"actionOutcome": { "Failure": "NOT OK" },
"state1": "action1 state", "state2": "action2 state" }"#,
]
.into_iter()
.map(|x| serde_json::from_str(x).unwrap())
.collect::<Vec<Value>>()
.into();
let events: EventStream = trace.into();
let result = runner.run(&mut system, &mut events.into_iter());
assert!(result.is_ok());
}
}