mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 20:40:50 +00:00
feat(testing) Testing framework chapter 1, asserts and comments (#342)
* seq_result` -> `seq_ok`; add `seq_err` `seq_ok` and `seq_err` are consistent with `ok` and `err`, but produce results sequentially. * Accept `;;` and longer comments in the sexp parser Currently they are just dropped, and resulting AIR has different character positions in the error messages. * Add "map" assertion Lookup result in a map by service's first argument.
This commit is contained in:
parent
17a6409566
commit
076045124c
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -189,6 +189,7 @@ dependencies = [
|
|||||||
"maplit",
|
"maplit",
|
||||||
"nom 7.1.1",
|
"nom 7.1.1",
|
||||||
"nom_locate",
|
"nom_locate",
|
||||||
|
"pretty_assertions",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
]
|
]
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use air_test_framework::TestExecutor;
|
||||||
use air_test_utils::prelude::*;
|
use air_test_utils::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -27,24 +28,17 @@ fn issue_221() {
|
|||||||
|
|
||||||
let peer_1_value = "peer_1_value";
|
let peer_1_value = "peer_1_value";
|
||||||
let peer_2_value = "peer_2_value";
|
let peer_2_value = "peer_2_value";
|
||||||
let mut peer_1 = create_avm(set_variable_call_service(json!(peer_1_value)), peer_1_id);
|
|
||||||
let mut peer_2 = create_avm(set_variable_call_service(json!(peer_2_value)), peer_2_id);
|
|
||||||
let mut join_1 = create_avm(echo_call_service(), join_1_id);
|
|
||||||
let mut set_variable = create_avm(
|
|
||||||
set_variable_call_service(json!([peer_1_id, peer_2_id])),
|
|
||||||
set_variable_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
let script = f!(r#"
|
let script = f!(r#"
|
||||||
(seq
|
(seq
|
||||||
(seq
|
(seq
|
||||||
(seq
|
(seq
|
||||||
;; let's peers be an array of two values [peer_1_id, peer_2_id]
|
;; let's peers be an array of two values [peer_1_id, peer_2_id]
|
||||||
(call "{set_variable_id}" ("" "") [] peers)
|
(call "{set_variable_id}" ("" "") [] peers) ; ok = ["{peer_1_id}", "{peer_2_id}"]
|
||||||
(fold peers peer
|
(fold peers peer
|
||||||
(par
|
(par
|
||||||
(seq
|
(seq
|
||||||
(call peer ("" "") [] value)
|
(call peer ("" "") [peer] value) ; map = {{"{peer_1_id}": "{peer_1_value}", "{peer_2_id}": "{peer_2_value}"}}
|
||||||
;; it's crucial to reproduce this bug to add value to stream
|
;; it's crucial to reproduce this bug to add value to stream
|
||||||
;; with help of ap instruction
|
;; with help of ap instruction
|
||||||
(ap value $stream)
|
(ap value $stream)
|
||||||
@ -61,8 +55,8 @@ fn issue_221() {
|
|||||||
;; appropriate way and state for (1) is returned
|
;; appropriate way and state for (1) is returned
|
||||||
(par
|
(par
|
||||||
(par
|
(par
|
||||||
(call "{join_1_id}" ("" "") [iterator])
|
(call "{join_1_id}" ("" "") [iterator]) ; behaviour = echo
|
||||||
(call "{join_2_id}" ("" "") [iterator])
|
(call "{join_2_id}" ("" "") [iterator]) ; behaviour = echo
|
||||||
)
|
)
|
||||||
(next iterator)
|
(next iterator)
|
||||||
)
|
)
|
||||||
@ -72,18 +66,20 @@ fn issue_221() {
|
|||||||
)
|
)
|
||||||
"#);
|
"#);
|
||||||
|
|
||||||
let result = checked_call_vm!(set_variable, <_>::default(), &script, "", "");
|
let executor = TestExecutor::new(
|
||||||
let peer_1_result = checked_call_vm!(peer_1, <_>::default(), &script, "", result.data.clone());
|
TestRunParameters::from_init_peer_id("set_variable_id"),
|
||||||
let peer_2_result = checked_call_vm!(peer_2, <_>::default(), &script, "", result.data.clone());
|
vec![],
|
||||||
|
vec![peer_1_id, peer_2_id].into_iter().map(Into::into),
|
||||||
let join_1_result = checked_call_vm!(join_1, <_>::default(), &script, "", peer_1_result.data.clone());
|
|
||||||
let join_1_result = checked_call_vm!(
|
|
||||||
join_1,
|
|
||||||
<_>::default(),
|
|
||||||
&script,
|
&script,
|
||||||
join_1_result.data,
|
)
|
||||||
peer_2_result.data.clone()
|
.expect("Invalid annotated AIR script");
|
||||||
); // before 0.20.9 it fails here
|
|
||||||
|
let _result = executor.execute_one(set_variable_id).unwrap();
|
||||||
|
let _peer_1_result = executor.execute_one(peer_1_id).unwrap();
|
||||||
|
let _peer_2_result = executor.execute_one(peer_2_id).unwrap();
|
||||||
|
|
||||||
|
let _join_1_result = executor.execute_one(join_1_id).unwrap();
|
||||||
|
let join_1_result = executor.execute_one(join_1_id).unwrap(); // before 0.20.9 it fails here
|
||||||
let actual_trace = trace_from_result(&join_1_result);
|
let actual_trace = trace_from_result(&join_1_result);
|
||||||
let expected_trace = vec![
|
let expected_trace = vec![
|
||||||
executed_state::scalar(json!([peer_1_id, peer_2_id])),
|
executed_state::scalar(json!([peer_1_id, peer_2_id])),
|
||||||
|
@ -24,5 +24,7 @@ serde_json = "1.0.85"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
|
pretty_assertions = "0.6.1"
|
||||||
|
|
||||||
# We do not want to depend on wasm binary path
|
# We do not want to depend on wasm binary path
|
||||||
air-test-utils = { path = "../air-lib/test-utils", features = ["test_with_native_code"] }
|
air-test-utils = { path = "../air-lib/test-utils", features = ["test_with_native_code"] }
|
||||||
|
@ -36,11 +36,14 @@ pub enum ServiceDefinition {
|
|||||||
Error(CallServiceResult),
|
Error(CallServiceResult),
|
||||||
/// Service that may return a new value on subsequent call. Its keys are either
|
/// Service that may return a new value on subsequent call. Its keys are either
|
||||||
/// call number string starting from "0", or "default".
|
/// call number string starting from "0", or "default".
|
||||||
// TODO We need to return error results too, so we need to define a call result
|
#[strum_discriminants(strum(serialize = "seq_ok"))]
|
||||||
// for default and individual errors.
|
SeqOk(HashMap<String, JValue>),
|
||||||
#[strum_discriminants(strum(serialize = "seq_result"))]
|
#[strum_discriminants(strum(serialize = "seq_error"))]
|
||||||
SeqResult(HashMap<String, JValue>),
|
SeqError(HashMap<String, CallServiceResult>),
|
||||||
/// Some known service by name: "echo", "unit" (more to follow).
|
/// Some known service by name: "echo", "unit" (more to follow).
|
||||||
#[strum_discriminants(strum(serialize = "behaviour"))]
|
#[strum_discriminants(strum(serialize = "behaviour"))]
|
||||||
Behaviour(String),
|
Behaviour(String),
|
||||||
|
/// Maps first argument to a value
|
||||||
|
#[strum_discriminants(strum(serialize = "map"))]
|
||||||
|
Map(HashMap<String, JValue>),
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
use super::{ServiceDefinition, ServiceTagName};
|
use super::{ServiceDefinition, ServiceTagName};
|
||||||
use crate::services::JValue;
|
use crate::services::JValue;
|
||||||
|
use crate::transform::parser::delim_ws;
|
||||||
|
|
||||||
use air_test_utils::CallServiceResult;
|
use air_test_utils::CallServiceResult;
|
||||||
use nom::{error::VerboseError, IResult, InputTakeAtPosition, Parser};
|
use nom::{error::VerboseError, IResult};
|
||||||
|
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
@ -50,8 +51,10 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
|||||||
alt((
|
alt((
|
||||||
tag(ServiceTagName::Ok.as_ref()),
|
tag(ServiceTagName::Ok.as_ref()),
|
||||||
tag(ServiceTagName::Error.as_ref()),
|
tag(ServiceTagName::Error.as_ref()),
|
||||||
tag(ServiceTagName::SeqResult.as_ref()),
|
tag(ServiceTagName::SeqOk.as_ref()),
|
||||||
|
tag(ServiceTagName::SeqError.as_ref()),
|
||||||
tag(ServiceTagName::Behaviour.as_ref()),
|
tag(ServiceTagName::Behaviour.as_ref()),
|
||||||
|
tag(ServiceTagName::Map.as_ref()),
|
||||||
)),
|
)),
|
||||||
equal(),
|
equal(),
|
||||||
cut(context(
|
cut(context(
|
||||||
@ -68,33 +71,26 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
|||||||
Ok(ServiceTagName::Error) => {
|
Ok(ServiceTagName::Error) => {
|
||||||
serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::Error)
|
serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::Error)
|
||||||
}
|
}
|
||||||
Ok(ServiceTagName::SeqResult) => {
|
Ok(ServiceTagName::SeqOk) => {
|
||||||
serde_json::from_str::<HashMap<String, JValue>>(value)
|
serde_json::from_str(value).map(ServiceDefinition::SeqOk)
|
||||||
.map(ServiceDefinition::SeqResult)
|
}
|
||||||
|
Ok(ServiceTagName::SeqError) => {
|
||||||
|
serde_json::from_str::<HashMap<String, CallServiceResult>>(value)
|
||||||
|
.map(ServiceDefinition::SeqError)
|
||||||
}
|
}
|
||||||
Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())),
|
Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())),
|
||||||
|
Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::Map),
|
||||||
Err(_) => unreachable!("unknown tag {:?}", tag),
|
Err(_) => unreachable!("unknown tag {:?}", tag),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
))(inp)
|
))(inp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn delim_ws<I, O, E, F>(f: F) -> impl FnMut(I) -> IResult<I, O, E>
|
|
||||||
where
|
|
||||||
F: Parser<I, O, E>,
|
|
||||||
E: nom::error::ParseError<I>,
|
|
||||||
I: InputTakeAtPosition,
|
|
||||||
<I as InputTakeAtPosition>::Item: nom::AsChar + Clone,
|
|
||||||
{
|
|
||||||
use nom::character::complete::multispace0;
|
|
||||||
use nom::sequence::delimited;
|
|
||||||
|
|
||||||
delimited(multispace0, f, multispace0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_empty() {
|
fn test_parse_empty() {
|
||||||
@ -151,13 +147,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seq_result() {
|
fn test_seq_ok() {
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": []}"#);
|
let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": []}"#);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Ok(ServiceDefinition::SeqResult(maplit::hashmap! {
|
Ok(ServiceDefinition::SeqOk(maplit::hashmap! {
|
||||||
"default".to_owned() => json!(42),
|
"default".to_owned() => json!(42),
|
||||||
"1".to_owned() => json!(true),
|
"1".to_owned() => json!(true),
|
||||||
"3".to_owned() => json!([]),
|
"3".to_owned() => json!([]),
|
||||||
@ -166,15 +162,45 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seq_result_malformed() {
|
fn test_seq_ok_malformed() {
|
||||||
let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": ]}"#);
|
let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": ]}"#);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seq_result_invalid() {
|
fn test_seq_ok_invalid() {
|
||||||
// TODO perhaps, we should support both arrays and maps
|
// TODO perhaps, we should support both arrays and maps
|
||||||
let res = ServiceDefinition::from_str(r#"seq_result=[42, 43]"#);
|
let res = ServiceDefinition::from_str(r#"seq_ok=[42, 43]"#);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_error() {
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
let res = ServiceDefinition::from_str(
|
||||||
|
r#"seq_error={"default": {"ret_code": 0, "result": 42}, "1": {"ret_code": 0, "result": true}, "3": {"ret_code": 1, "result": "error"}}"#,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Ok(ServiceDefinition::SeqError(maplit::hashmap! {
|
||||||
|
"default".to_owned() => CallServiceResult::ok(json!(42)),
|
||||||
|
"1".to_owned() => CallServiceResult::ok(json!(true)),
|
||||||
|
"3".to_owned() => CallServiceResult::err(1, json!("error")),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_error_malformed() {
|
||||||
|
let res = ServiceDefinition::from_str(r#"seq_error={"default": 42, "1": true]}"#);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_error_invalid() {
|
||||||
|
// TODO perhaps, we should support both arrays and maps
|
||||||
|
let res = ServiceDefinition::from_str(r#"seq_error=[42, 43]"#);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,4 +209,16 @@ mod tests {
|
|||||||
let res = ServiceDefinition::from_str(r#"behaviour=echo"#);
|
let res = ServiceDefinition::from_str(r#"behaviour=echo"#);
|
||||||
assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),);
|
assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map() {
|
||||||
|
let res = ServiceDefinition::from_str(r#"map = {"42": [], "a": 2}"#);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Ok(ServiceDefinition::Map(maplit::hashmap! {
|
||||||
|
"42".to_owned() => json!([]),
|
||||||
|
"a".to_owned() => json!(2)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,10 +145,13 @@ fn build_peers(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use air_test_utils::prelude::*;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use air_test_utils::prelude::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_execution() {
|
fn test_execution() {
|
||||||
let exec = TestExecutor::new(
|
let exec = TestExecutor::new(
|
||||||
@ -248,7 +251,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_seq_result() {
|
fn test_seq_ok() {
|
||||||
let exec = TestExecutor::new(
|
let exec = TestExecutor::new(
|
||||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||||
vec![],
|
vec![],
|
||||||
@ -260,7 +263,7 @@ mod tests {
|
|||||||
(ap 1 k)
|
(ap 1 k)
|
||||||
(fold var i
|
(fold var i
|
||||||
(seq
|
(seq
|
||||||
(call i.$.p ("service" "func") [i k] k) ; seq_result = {"0":12,"default":42}
|
(call i.$.p ("service" "func") [i k] k) ; seq_ok = {"0":12,"default":42}
|
||||||
(next i)))))
|
(next i)))))
|
||||||
(call "init_peer_id" ("a" "b") []) ; ok = 0
|
(call "init_peer_id" ("a" "b") []) ; ok = 0
|
||||||
)"#,
|
)"#,
|
||||||
@ -322,6 +325,152 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map() {
|
||||||
|
let exec = TestExecutor::new(
|
||||||
|
TestRunParameters::from_init_peer_id("peer1"),
|
||||||
|
vec![],
|
||||||
|
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||||
|
r#"
|
||||||
|
(seq
|
||||||
|
(call "peer1" ("" "") [] peers) ; ok = ["peer2", "peer3"]
|
||||||
|
(fold peers p
|
||||||
|
(seq
|
||||||
|
(call p ("" "") [p]) ; map = {"peer2": 42, "peer3": 43}
|
||||||
|
(next p)
|
||||||
|
)))
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result_init: Vec<_> = exec.execution_iter("peer1").unwrap().collect();
|
||||||
|
|
||||||
|
assert_eq!(result_init.len(), 1);
|
||||||
|
let outcome1 = &result_init[0];
|
||||||
|
assert_eq!(outcome1.ret_code, 0);
|
||||||
|
assert_eq!(outcome1.error_message, "");
|
||||||
|
assert_next_pks!(&outcome1.next_peer_pks, ["peer2"]);
|
||||||
|
|
||||||
|
{
|
||||||
|
let results2 = exec.execute_all("peer2").unwrap();
|
||||||
|
assert_eq!(results2.len(), 1);
|
||||||
|
let outcome2 = &results2[0];
|
||||||
|
assert_eq!(outcome2.ret_code, 0, "{:?}", outcome2);
|
||||||
|
assert!(exec.execution_iter("peer2").unwrap().next().is_none());
|
||||||
|
assert_next_pks!(&outcome2.next_peer_pks, ["peer3"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let results3 = exec.execute_all("peer3").unwrap();
|
||||||
|
assert_eq!(results3.len(), 1);
|
||||||
|
let outcome3 = &results3[0];
|
||||||
|
assert_eq!(outcome3.ret_code, 0, "{:?}", outcome3);
|
||||||
|
assert_next_pks!(&outcome3.next_peer_pks, []);
|
||||||
|
|
||||||
|
let trace = trace_from_result(outcome3);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
trace.deref(),
|
||||||
|
vec![
|
||||||
|
executed_state::scalar(json!(["peer2", "peer3"])),
|
||||||
|
executed_state::scalar(json!(42)),
|
||||||
|
executed_state::scalar(json!(43)),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_map_no_arg() {
|
||||||
|
let exec = TestExecutor::new(
|
||||||
|
TestRunParameters::from_init_peer_id("peer1"),
|
||||||
|
vec![],
|
||||||
|
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||||
|
r#"
|
||||||
|
(call "peer1" ("" "") [] p) ; map = {"any": "key"}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let _result_init: Vec<_> = exec.execution_iter("peer1").unwrap().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seq_error() {
|
||||||
|
let exec = TestExecutor::new(
|
||||||
|
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||||
|
vec![],
|
||||||
|
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||||
|
r#"(seq
|
||||||
|
(seq
|
||||||
|
(call "peer1" ("service" "func") [] var) ; ok = [{"p":"peer2","v":2},{"p":"peer3","v":3}, {"p":"peer4"}]
|
||||||
|
(seq
|
||||||
|
(ap 1 k)
|
||||||
|
(fold var i
|
||||||
|
(seq
|
||||||
|
(call i.$.p ("service" "func") [i.$.v k] k) ; seq_error = {"0":{"ret_code":0,"result":12},"default":{"ret_code":1,"result":42}}
|
||||||
|
(next i)))))
|
||||||
|
(call "init_peer_id" ("a" "b") []) ; ok = 0
|
||||||
|
)"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result_init: Vec<_> = exec.execution_iter("init_peer_id").unwrap().collect();
|
||||||
|
|
||||||
|
assert_eq!(result_init.len(), 1);
|
||||||
|
let outcome1 = &result_init[0];
|
||||||
|
assert_eq!(outcome1.ret_code, 0);
|
||||||
|
assert_eq!(outcome1.error_message, "");
|
||||||
|
|
||||||
|
assert!(exec.execution_iter("peer2").unwrap().next().is_none());
|
||||||
|
{
|
||||||
|
let results1 = exec.execute_all("peer1").unwrap();
|
||||||
|
assert_eq!(results1.len(), 1);
|
||||||
|
let outcome1 = &results1[0];
|
||||||
|
assert_eq!(outcome1.ret_code, 0, "{:?}", outcome1);
|
||||||
|
assert!(exec.execution_iter("peer1").unwrap().next().is_none());
|
||||||
|
assert_next_pks!(&outcome1.next_peer_pks, ["peer2"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let results2: Vec<_> = exec.execute_all("peer2").unwrap();
|
||||||
|
assert_eq!(results2.len(), 1);
|
||||||
|
let outcome2 = &results2[0];
|
||||||
|
assert_eq!(outcome2.ret_code, 0, "{:?}", outcome2);
|
||||||
|
assert!(exec.execution_iter("peer2").unwrap().next().is_none());
|
||||||
|
assert_next_pks!(&outcome2.next_peer_pks, ["peer3"]);
|
||||||
|
|
||||||
|
let trace = trace_from_result(outcome2);
|
||||||
|
assert_eq!(
|
||||||
|
trace,
|
||||||
|
ExecutionTrace::from(vec![
|
||||||
|
scalar(json!([{"p":"peer2","v":2},{"p":"peer3","v":3},{"p":"peer4"}])),
|
||||||
|
scalar_number(12),
|
||||||
|
request_sent_by("peer2"),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let results3: Vec<_> = exec.execute_all("peer3").unwrap();
|
||||||
|
assert_eq!(results3.len(), 1);
|
||||||
|
// TODO why doesn't it fail?
|
||||||
|
let outcome3 = &results3[0];
|
||||||
|
assert_eq!(outcome3.ret_code, 0, "{:?}", outcome3);
|
||||||
|
assert!(exec.execution_iter("peer3").unwrap().next().is_none());
|
||||||
|
|
||||||
|
let trace = trace_from_result(outcome3);
|
||||||
|
assert_eq!(
|
||||||
|
trace,
|
||||||
|
ExecutionTrace::from(vec![
|
||||||
|
scalar(json!([{"p":"peer2","v":2},{"p":"peer3","v":3},{"p":"peer4"}])),
|
||||||
|
scalar_number(12),
|
||||||
|
request_sent_by("peer2"),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_echo() {
|
fn test_echo() {
|
||||||
let exec = TestExecutor::new(
|
let exec = TestExecutor::new(
|
||||||
|
@ -21,8 +21,9 @@ use air_test_utils::{
|
|||||||
prelude::{echo_call_service, unit_call_service},
|
prelude::{echo_call_service, unit_call_service},
|
||||||
CallRequestParams, CallServiceClosure, CallServiceResult,
|
CallRequestParams, CallServiceClosure, CallServiceResult,
|
||||||
};
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use std::{cell::Cell, collections::HashMap, convert::TryInto, time::Duration};
|
use std::{borrow::Cow, cell::Cell, collections::HashMap, convert::TryInto, time::Duration};
|
||||||
|
|
||||||
pub struct ResultService {
|
pub struct ResultService {
|
||||||
results: HashMap<u32, CallServiceClosure>,
|
results: HashMap<u32, CallServiceClosure>,
|
||||||
@ -37,8 +38,10 @@ impl TryInto<CallServiceClosure> for ServiceDefinition {
|
|||||||
Ok(Box::new(move |_| CallServiceResult::ok(jvalue.clone())))
|
Ok(Box::new(move |_| CallServiceResult::ok(jvalue.clone())))
|
||||||
}
|
}
|
||||||
ServiceDefinition::Error(call_result) => Ok(Box::new(move |_| call_result.clone())),
|
ServiceDefinition::Error(call_result) => Ok(Box::new(move |_| call_result.clone())),
|
||||||
ServiceDefinition::SeqResult(call_map) => Ok(seq_result_closure(call_map)),
|
ServiceDefinition::SeqOk(call_map) => Ok(seq_ok_closure(call_map)),
|
||||||
|
ServiceDefinition::SeqError(call_map) => Ok(seq_error_closure(call_map)),
|
||||||
ServiceDefinition::Behaviour(name) => named_service_closure(name),
|
ServiceDefinition::Behaviour(name) => named_service_closure(name),
|
||||||
|
ServiceDefinition::Map(map) => Ok(map_service_closure(map)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +54,7 @@ fn named_service_closure(name: String) -> Result<CallServiceClosure, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seq_result_closure(call_map: HashMap<String, serde_json::Value>) -> CallServiceClosure {
|
fn seq_ok_closure(call_map: HashMap<String, serde_json::Value>) -> CallServiceClosure {
|
||||||
let call_number_seq = Cell::new(0);
|
let call_number_seq = Cell::new(0);
|
||||||
|
|
||||||
Box::new(move |_| {
|
Box::new(move |_| {
|
||||||
@ -74,6 +77,45 @@ fn seq_result_closure(call_map: HashMap<String, serde_json::Value>) -> CallServi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn seq_error_closure(call_map: HashMap<String, CallServiceResult>) -> CallServiceClosure {
|
||||||
|
let call_number_seq = Cell::new(0);
|
||||||
|
|
||||||
|
Box::new(move |_| {
|
||||||
|
let call_number = call_number_seq.get();
|
||||||
|
let call_num_str = call_number.to_string();
|
||||||
|
call_number_seq.set(call_number + 1);
|
||||||
|
|
||||||
|
call_map
|
||||||
|
.get(&call_num_str)
|
||||||
|
.or_else(|| call_map.get("default"))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"neither value {} nor default value not found in the {:?}",
|
||||||
|
call_num_str, call_map
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_service_closure(map: HashMap<String, serde_json::Value>) -> CallServiceClosure {
|
||||||
|
Box::new(move |args| {
|
||||||
|
let key = args
|
||||||
|
.arguments
|
||||||
|
.get(0)
|
||||||
|
.expect("At least one arugment expected");
|
||||||
|
// Strings are looked up by value, other objects -- by string representation.
|
||||||
|
//
|
||||||
|
// For example, `"key"` is looked up as `"key"`, `5` is looked up as `"5"`, `["test"]` is looked up
|
||||||
|
// as `"[\"test\"]"`.
|
||||||
|
let key_repr = match key {
|
||||||
|
serde_json::Value::String(s) => Cow::Borrowed(s.as_str()),
|
||||||
|
val => Cow::Owned(val.to_string()),
|
||||||
|
};
|
||||||
|
CallServiceResult::ok(json!(map.get(key_repr.as_ref()).cloned()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl ResultService {
|
impl ResultService {
|
||||||
pub(crate) fn new(results: HashMap<u32, ServiceDefinition>) -> Result<Self, String> {
|
pub(crate) fn new(results: HashMap<u32, ServiceDefinition>) -> Result<Self, String> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod parser;
|
pub(crate) mod parser;
|
||||||
pub(crate) mod walker;
|
pub(crate) mod walker;
|
||||||
|
|
||||||
use crate::asserts::ServiceDefinition;
|
use crate::asserts::ServiceDefinition;
|
||||||
|
@ -15,16 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{Call, Sexp, Triplet};
|
use super::{Call, Sexp, Triplet};
|
||||||
use crate::asserts::{parser::delim_ws, ServiceDefinition};
|
use crate::asserts::ServiceDefinition;
|
||||||
|
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{is_not, tag};
|
use nom::bytes::complete::{is_not, tag};
|
||||||
use nom::character::complete::{alphanumeric1, multispace0, multispace1, one_of, space1};
|
use nom::character::complete::{alphanumeric1, multispace0, multispace1, one_of, space1};
|
||||||
use nom::combinator::{cut, map, map_res, opt, recognize, value};
|
use nom::combinator::{cut, map, map_res, opt, recognize, value};
|
||||||
use nom::error::{context, VerboseError, VerboseErrorKind};
|
use nom::error::{context, VerboseError, VerboseErrorKind};
|
||||||
use nom::multi::{many1_count, separated_list0};
|
use nom::multi::{many0, many1, many1_count, separated_list0};
|
||||||
use nom::sequence::{delimited, pair, preceded, separated_pair, terminated};
|
use nom::sequence::{delimited, pair, preceded, separated_pair, terminated};
|
||||||
use nom::IResult;
|
use nom::{IResult, InputTakeAtPosition};
|
||||||
use nom_locate::LocatedSpan;
|
use nom_locate::LocatedSpan;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -87,11 +87,11 @@ fn parse_sexp_list(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>> {
|
|||||||
context(
|
context(
|
||||||
"within generic list",
|
"within generic list",
|
||||||
preceded(
|
preceded(
|
||||||
terminated(tag("("), multispace0),
|
terminated(tag("("), sexp_multispace0),
|
||||||
cut(terminated(
|
cut(terminated(
|
||||||
map(separated_list0(multispace1, parse_sexp), Sexp::list),
|
map(separated_list0(sexp_multispace1, parse_sexp), Sexp::list),
|
||||||
preceded(
|
preceded(
|
||||||
multispace0,
|
sexp_multispace0,
|
||||||
context("closing parentheses not found", tag(")")),
|
context("closing parentheses not found", tag(")")),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
@ -150,12 +150,12 @@ fn parse_sexp_call_content(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseErro
|
|||||||
// possible variable, closing ")", possible annotation
|
// possible variable, closing ")", possible annotation
|
||||||
pair(
|
pair(
|
||||||
terminated(
|
terminated(
|
||||||
opt(preceded(multispace1, map(parse_sexp_symbol, Box::new))),
|
opt(preceded(sexp_multispace1, map(parse_sexp_symbol, Box::new))),
|
||||||
preceded(multispace0, tag(")")),
|
preceded(sexp_multispace0, tag(")")),
|
||||||
),
|
),
|
||||||
alt((
|
alt((
|
||||||
opt(preceded(pair(space1, tag("; ")), parse_annotation)),
|
opt(preceded(pair(space1, tag("; ")), parse_annotation)),
|
||||||
value(None, multispace0),
|
value(None, sexp_multispace0),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -183,12 +183,12 @@ fn parse_sexp_call_triplet(inp: Input<'_>) -> IResult<Input<'_>, Box<Triplet>, P
|
|||||||
map(
|
map(
|
||||||
separated_pair(
|
separated_pair(
|
||||||
context("triplet peer_id", parse_sexp),
|
context("triplet peer_id", parse_sexp),
|
||||||
multispace0,
|
sexp_multispace0,
|
||||||
delimited(
|
delimited(
|
||||||
delim_ws(tag("(")),
|
delim_ws(tag("(")),
|
||||||
separated_pair(
|
separated_pair(
|
||||||
context("triplet service name", parse_sexp_string),
|
context("triplet service name", parse_sexp_string),
|
||||||
multispace0,
|
sexp_multispace0,
|
||||||
context("triplet function name", parse_sexp),
|
context("triplet function name", parse_sexp),
|
||||||
),
|
),
|
||||||
delim_ws(tag(")")),
|
delim_ws(tag(")")),
|
||||||
@ -202,13 +202,124 @@ fn parse_sexp_call_arguments(inp: Input<'_>) -> IResult<Input<'_>, Vec<Sexp>, Pa
|
|||||||
delimited(tag("["), separated_list0(multispace1, parse_sexp), tag("]"))(inp)
|
delimited(tag("["), separated_list0(multispace1, parse_sexp), tag("]"))(inp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delim_ws<I, O, E, F>(f: F) -> impl FnMut(I) -> IResult<I, O, E>
|
||||||
|
where
|
||||||
|
F: nom::Parser<I, O, E>,
|
||||||
|
E: nom::error::ParseError<I>,
|
||||||
|
I: nom::InputTakeAtPosition
|
||||||
|
+ nom::InputLength
|
||||||
|
+ for<'a> nom::Compare<&'a str>
|
||||||
|
+ nom::InputTake
|
||||||
|
+ Clone,
|
||||||
|
<I as InputTakeAtPosition>::Item: nom::AsChar + Clone,
|
||||||
|
for<'a> &'a str: nom::FindToken<<I as InputTakeAtPosition>::Item>,
|
||||||
|
{
|
||||||
|
delimited(sexp_multispace0, f, sexp_multispace0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sexp_multispace0<I, E>(inp: I) -> IResult<I, (), E>
|
||||||
|
where
|
||||||
|
E: nom::error::ParseError<I>,
|
||||||
|
I: InputTakeAtPosition
|
||||||
|
+ nom::InputLength
|
||||||
|
+ for<'a> nom::Compare<&'a str>
|
||||||
|
+ nom::InputTake
|
||||||
|
+ Clone,
|
||||||
|
<I as InputTakeAtPosition>::Item: nom::AsChar + Clone,
|
||||||
|
for<'a> &'a str: nom::FindToken<<I as InputTakeAtPosition>::Item>,
|
||||||
|
{
|
||||||
|
map(
|
||||||
|
opt(many0(pair(
|
||||||
|
// white space
|
||||||
|
multispace1,
|
||||||
|
// possible ;;, ;;; comment
|
||||||
|
opt(pair(tag(";;"), is_not("\r\n"))),
|
||||||
|
))),
|
||||||
|
|_| (),
|
||||||
|
)(inp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sexp_multispace1(inp: Input<'_>) -> IResult<Input<'_>, (), ParseError<'_>> {
|
||||||
|
map(
|
||||||
|
// It's not the fastest implementation, but easiest to write.
|
||||||
|
// It passes initial whitespace two times.
|
||||||
|
many1(alt((
|
||||||
|
map(
|
||||||
|
pair(
|
||||||
|
// white space
|
||||||
|
multispace0,
|
||||||
|
// ;;, ;;;, etc comment
|
||||||
|
pair(tag(";;"), is_not("\r\n")),
|
||||||
|
),
|
||||||
|
|_| (),
|
||||||
|
),
|
||||||
|
map(multispace1, |_| ()),
|
||||||
|
))),
|
||||||
|
|_| (),
|
||||||
|
)(inp)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::asserts::ServiceDefinition;
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
#[test]
|
||||||
|
fn test_multispace0_empty() {
|
||||||
|
let res = sexp_multispace0::<_, ()>("");
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
use crate::asserts::ServiceDefinition;
|
#[test]
|
||||||
|
fn test_multispace0_spaces() {
|
||||||
|
let res = sexp_multispace0::<_, ()>(" ");
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace0_comment() {
|
||||||
|
let res = sexp_multispace0::<_, ()>(";; this is comment");
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace0_comment_with_space() {
|
||||||
|
let res = sexp_multispace0::<_, ()>(" ;; ");
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace0_multiline() {
|
||||||
|
let res = sexp_multispace0::<_, ()>(" ;; \n ;;;; \n ");
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace1_empty() {
|
||||||
|
let res = sexp_multispace1("".into());
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace1_space() {
|
||||||
|
let res = sexp_multispace1(" ".into());
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace1_comment() {
|
||||||
|
let res = sexp_multispace1(" ;; ".into());
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multispace1_multiline() {
|
||||||
|
let res = sexp_multispace1(" ;; \n ;;;; \n ".into());
|
||||||
|
assert!(res.is_ok(), "{}", res.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_symbol() {
|
fn test_symbol() {
|
||||||
@ -516,4 +627,37 @@ mod tests {
|
|||||||
let res = Sexp::from_str(sexp_str);
|
let res = Sexp::from_str(sexp_str);
|
||||||
assert!(res.is_ok(), "{:?}", res);
|
assert!(res.is_ok(), "{:?}", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comments() {
|
||||||
|
let sexp_str = r#" ;; One comment
|
||||||
|
( ;;; Second comment
|
||||||
|
;; The third one
|
||||||
|
(par ;;;; Comment comment comment
|
||||||
|
;;;; Comment comment comment
|
||||||
|
(null) ;;;;; Comment
|
||||||
|
(fail ;; Fails
|
||||||
|
1 ;; Retcode
|
||||||
|
"test" ;; Message
|
||||||
|
;; Nothing more
|
||||||
|
)
|
||||||
|
) ;;;;; Comment
|
||||||
|
;;;;; Comment
|
||||||
|
) ;;;;; Comment
|
||||||
|
;;; Comment
|
||||||
|
"#;
|
||||||
|
let res = Sexp::from_str(sexp_str);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Ok(Sexp::list(vec![Sexp::list(vec![
|
||||||
|
Sexp::symbol("par"),
|
||||||
|
Sexp::list(vec![Sexp::symbol("null"),]),
|
||||||
|
Sexp::list(vec![
|
||||||
|
Sexp::symbol("fail"),
|
||||||
|
Sexp::symbol("1"),
|
||||||
|
Sexp::string("test"),
|
||||||
|
]),
|
||||||
|
])]))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user