Create simple test framework for Act implementors

This commit is contained in:
NGnius (Graham) 2022-09-28 21:15:59 -04:00
parent 83135b3045
commit a52309484e
4 changed files with 230 additions and 0 deletions

View file

@ -155,3 +155,151 @@ impl<'a, X: SeqAct<'a, BuildParam=()>> Act<'a> for SeqActor<'a, X> {
self.seq_act.run(self.param) self.seq_act.run(self.param)
} }
} }
#[cfg(test)]
pub struct SeqActTestHarness<'a, ParamT, ConfigT, ActorT, FnT>
where
ConfigT: 'a,
ActorT: SeqAct<'a, BuildParam=ParamT, Config=ConfigT>,
FnT: Fn(&'a ConfigT, ParamT) -> Result<ActorT, ActError>,
{
actor_factory: FnT,
inputs: Vec<(&'a ConfigT, ParamT, Primitive)>,
outputs: std::collections::VecDeque<Expected>,
}
pub enum Expected {
Output(Primitive),
BuildErr(ActError),
OutputIdc,
}
#[cfg(test)]
impl<'a, ParamT, ConfigT, ActorT, FnT> SeqActTestHarness<'a, ParamT, ConfigT, ActorT, FnT>
where
ConfigT: 'a,
ActorT: SeqAct<'a, BuildParam=ParamT, Config=ConfigT>,
FnT: Fn(&'a ConfigT, ParamT) -> Result<ActorT, ActError>,
{
#[allow(dead_code)]
pub fn new(factory: FnT, inputs_vars: Vec<(&'a ConfigT, ParamT, Primitive)>, output_vars: std::collections::VecDeque<Expected>) -> Self {
Self {
actor_factory: factory,
inputs: inputs_vars,
outputs: output_vars,
}
}
pub fn builder(factory: FnT) -> Self {
Self {
actor_factory: factory,
inputs: Vec::new(),
outputs: std::collections::VecDeque::new(),
}
}
#[allow(dead_code)]
pub fn with_input(mut self, conf: &'a ConfigT, build_param: ParamT, run_param: Primitive) -> Self {
self.inputs.push((conf, build_param, run_param));
self
}
#[allow(dead_code)]
pub fn expect(mut self, expected: Expected) -> Self {
self.outputs.push_back(expected);
self
}
pub fn with_io(mut self, input: (&'a ConfigT, ParamT, Primitive), output: Expected) -> Self {
self.inputs.push(input);
self.outputs.push_back(output);
self
}
pub fn run(mut self) {
for input in self.inputs {
let expected = self.outputs.pop_front().expect("Not enough outputs for available inputs");
let actor_result = (self.actor_factory)(input.0, input.1);
match expected {
Expected::Output(primitive) => {
let debug_prim = crate::runtime::primitive_utils::debug(&input.2);
let actual = actor_result.expect("Expected Ok actor").run(input.2);
let debug_actual = crate::runtime::primitive_utils::debug(&actual);
match primitive {
Primitive::Empty => if let Primitive::Empty = actual {
// good!
} else {
panic!("Expected SeqAct.run({}) to output Empty, got `{}`", debug_prim, debug_actual);
},
Primitive::String(s) => if let Primitive::String(actual) = actual {
assert_eq!(actual, s, "Expected SeqAct.run({}) to output Primitive::String(`{}`)", debug_prim, s);
} else {
panic!("Expected SeqAct.run({}) to output Primitive::String(`{}`), got `{}`", debug_prim, s, debug_actual);
},
Primitive::Json(j) => if let Primitive::Json(actual) = actual {
assert_eq!(actual, j, "Expected SeqAct.run({}) to output Primitive::Json(`{}`)", debug_prim, j);
} else {
panic!("Expected SeqAct.run({}) to output Primitive::Json(`{}`), got `{}`", debug_prim, j, debug_actual);
},
_ => todo!("NGNIUS!!! Complete your damn test harness!!"),
}
},
Expected::BuildErr(err) => {
if let Err(actual) = actor_result {
assert_eq!(actual, err, "Expected and actual build error do not match");
} else {
panic!("Expected build error `{}`, but result was ok", err);
}
},
Expected::OutputIdc => {
actor_result.expect("Expected Ok actor").run(input.2);
},
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::config::*;
#[test]
fn full_actor_test() {
let (runtime_io, _result_rx, _js_rx) = crate::runtime::RuntimeIO::mock();
SeqActTestHarness::builder(Actor::build)
.with_io(
(&ElementConfig::Button(ButtonConfig {
title: "Test Button".into(),
on_click: TopLevelActionConfig::Mirror(MirrorAction)
}),
(0, &runtime_io),
Primitive::Empty),
Expected::Output(Primitive::Empty))
.run();
}
#[test]
fn top_level_actor_test() {
let (runtime_io, _result_rx, _js_rx) = crate::runtime::RuntimeIO::mock();
SeqActTestHarness::builder(TopLevelActorType::build)
.with_io(
(&TopLevelActionConfig::Mirror(MirrorAction), &runtime_io, Primitive::Empty), Expected::Output(Primitive::Empty))
.run();
}
#[test]
fn std_actor_test() {
let (runtime_io, _result_rx, _js_rx) = crate::runtime::RuntimeIO::mock();
SeqActTestHarness::builder(ActorType::build)
.with_io(
(&ActionConfig::Transform(TransformAction{
transformer: TransformTypeAction::Log(LogTransformAction{level: LogLevel::DEBUG})}),
&runtime_io,
Primitive::String("Test log output".into())),
Expected::OutputIdc)
.run();
}
}

View file

@ -75,3 +75,67 @@ impl<'a> SeqAct<'a> for JsonActor {
} }
} }
} }
#[cfg(test)]
mod test {
use crate::runtime::actors::*;
use crate::config::*;
use usdpl_back::core::serdes::Primitive;
#[test]
fn json_actor_test() {
//let (runtime_io, _result_rx, _js_rx) = crate::runtime::RuntimeIO::mock();
SeqActTestHarness::builder(JsonActor::build)
// test 1 ---
.with_io(
(&JsonAction {
jmespath: r"locations[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}".into(),
},
(),
Primitive::Json(
r#"{
"locations": [
{"name": "Seattle", "state": "WA"},
{"name": "New York", "state": "NY"},
{"name": "Bellevue", "state": "WA"},
{"name": "Olympia", "state": "WA"}
]
}"#.into()
)),
Expected::Output(Primitive::Json(
r#"{"WashingtonCities":"Bellevue, Olympia, Seattle"}"#.into()
)))
// test 2 ---
.with_io(
(&JsonAction {
jmespath: r"locations[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}".into(),
},
(),
Primitive::Bool(false)
),
Expected::Output(Primitive::Empty))
// test 3 ---
.with_io(
(&JsonAction {
jmespath: "garb@ge".into(),
},
(),
Primitive::Json(
r#"{
"locations": [
{"name": "Seattle", "state": "WA"},
{"name": "New York", "state": "NY"},
{"name": "Bellevue", "state": "WA"},
{"name": "Olympia", "state": "WA"}
]
}"#.into()
)),
Expected::BuildErr("Failed to compile jmespath `garb@ge`: Parse error: Did not parse the complete expression -- found At (line 0, column 4)\ngarb@ge\n ^\n".into()))
.run();
}
}

View file

@ -7,6 +7,8 @@ mod sequential_actor;
mod transform_actor; mod transform_actor;
pub use actor::{Actor, Act, ActError, ActorType, SeqAct, SeqActor, TopLevelActorType}; pub use actor::{Actor, Act, ActError, ActorType, SeqAct, SeqActor, TopLevelActorType};
#[cfg(test)]
pub use actor::{Expected, SeqActTestHarness, /*ActTestHarness*/};
pub use command_actor::CommandActor; pub use command_actor::CommandActor;
pub use javascript_actor::JavascriptActor; pub use javascript_actor::JavascriptActor;
pub use json_actor::JsonActor; pub use json_actor::JsonActor;

View file

@ -12,6 +12,22 @@ pub struct RuntimeIO {
pub javascript: Sender<JavascriptCommand>, pub javascript: Sender<JavascriptCommand>,
} }
#[cfg(test)]
impl RuntimeIO {
pub fn mock() -> (Self, Receiver<RouterCommand>, Receiver<JavascriptCommand>) {
let (s1, r1) = mpsc::channel();
let (s2, r2) = mpsc::channel();
(
Self {
result: s1,
javascript: s2,
},
r1,
r2,
)
}
}
pub struct RuntimeExecutor { pub struct RuntimeExecutor {
config_data: BaseConfig, config_data: BaseConfig,
tasks_receiver: Receiver<QueueItem>, tasks_receiver: Receiver<QueueItem>,