mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 15:20:16 +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",
|
||||
"nom 7.1.1",
|
||||
"nom_locate",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"strum",
|
||||
]
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -27,24 +28,17 @@ fn issue_221() {
|
||||
|
||||
let peer_1_value = "peer_1_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#"
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
;; 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
|
||||
(par
|
||||
(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
|
||||
;; with help of ap instruction
|
||||
(ap value $stream)
|
||||
@ -61,8 +55,8 @@ fn issue_221() {
|
||||
;; appropriate way and state for (1) is returned
|
||||
(par
|
||||
(par
|
||||
(call "{join_1_id}" ("" "") [iterator])
|
||||
(call "{join_2_id}" ("" "") [iterator])
|
||||
(call "{join_1_id}" ("" "") [iterator]) ; behaviour = echo
|
||||
(call "{join_2_id}" ("" "") [iterator]) ; behaviour = echo
|
||||
)
|
||||
(next iterator)
|
||||
)
|
||||
@ -72,18 +66,20 @@ fn issue_221() {
|
||||
)
|
||||
"#);
|
||||
|
||||
let result = checked_call_vm!(set_variable, <_>::default(), &script, "", "");
|
||||
let peer_1_result = checked_call_vm!(peer_1, <_>::default(), &script, "", result.data.clone());
|
||||
let peer_2_result = checked_call_vm!(peer_2, <_>::default(), &script, "", result.data.clone());
|
||||
|
||||
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(),
|
||||
let executor = TestExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("set_variable_id"),
|
||||
vec![],
|
||||
vec![peer_1_id, peer_2_id].into_iter().map(Into::into),
|
||||
&script,
|
||||
join_1_result.data,
|
||||
peer_2_result.data.clone()
|
||||
); // before 0.20.9 it fails here
|
||||
)
|
||||
.expect("Invalid annotated AIR script");
|
||||
|
||||
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 expected_trace = vec![
|
||||
executed_state::scalar(json!([peer_1_id, peer_2_id])),
|
||||
|
@ -24,5 +24,7 @@ serde_json = "1.0.85"
|
||||
|
||||
[dev-dependencies]
|
||||
maplit = "1.0.2"
|
||||
pretty_assertions = "0.6.1"
|
||||
|
||||
# We do not want to depend on wasm binary path
|
||||
air-test-utils = { path = "../air-lib/test-utils", features = ["test_with_native_code"] }
|
||||
|
@ -36,11 +36,14 @@ pub enum ServiceDefinition {
|
||||
Error(CallServiceResult),
|
||||
/// Service that may return a new value on subsequent call. Its keys are either
|
||||
/// call number string starting from "0", or "default".
|
||||
// TODO We need to return error results too, so we need to define a call result
|
||||
// for default and individual errors.
|
||||
#[strum_discriminants(strum(serialize = "seq_result"))]
|
||||
SeqResult(HashMap<String, JValue>),
|
||||
#[strum_discriminants(strum(serialize = "seq_ok"))]
|
||||
SeqOk(HashMap<String, JValue>),
|
||||
#[strum_discriminants(strum(serialize = "seq_error"))]
|
||||
SeqError(HashMap<String, CallServiceResult>),
|
||||
/// Some known service by name: "echo", "unit" (more to follow).
|
||||
#[strum_discriminants(strum(serialize = "behaviour"))]
|
||||
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 crate::services::JValue;
|
||||
use crate::transform::parser::delim_ws;
|
||||
|
||||
use air_test_utils::CallServiceResult;
|
||||
use nom::{error::VerboseError, IResult, InputTakeAtPosition, Parser};
|
||||
use nom::{error::VerboseError, IResult};
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
@ -50,8 +51,10 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
alt((
|
||||
tag(ServiceTagName::Ok.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::Map.as_ref()),
|
||||
)),
|
||||
equal(),
|
||||
cut(context(
|
||||
@ -68,33 +71,26 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
Ok(ServiceTagName::Error) => {
|
||||
serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::Error)
|
||||
}
|
||||
Ok(ServiceTagName::SeqResult) => {
|
||||
serde_json::from_str::<HashMap<String, JValue>>(value)
|
||||
.map(ServiceDefinition::SeqResult)
|
||||
Ok(ServiceTagName::SeqOk) => {
|
||||
serde_json::from_str(value).map(ServiceDefinition::SeqOk)
|
||||
}
|
||||
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::Map) => serde_json::from_str(value).map(ServiceDefinition::Map),
|
||||
Err(_) => unreachable!("unknown tag {:?}", tag),
|
||||
}
|
||||
},
|
||||
))(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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty() {
|
||||
@ -151,13 +147,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result() {
|
||||
fn test_seq_ok() {
|
||||
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!(
|
||||
res,
|
||||
Ok(ServiceDefinition::SeqResult(maplit::hashmap! {
|
||||
Ok(ServiceDefinition::SeqOk(maplit::hashmap! {
|
||||
"default".to_owned() => json!(42),
|
||||
"1".to_owned() => json!(true),
|
||||
"3".to_owned() => json!([]),
|
||||
@ -166,15 +162,45 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result_malformed() {
|
||||
let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": ]}"#);
|
||||
fn test_seq_ok_malformed() {
|
||||
let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": ]}"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result_invalid() {
|
||||
fn test_seq_ok_invalid() {
|
||||
// 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());
|
||||
}
|
||||
|
||||
@ -183,4 +209,16 @@ mod tests {
|
||||
let res = ServiceDefinition::from_str(r#"behaviour=echo"#);
|
||||
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)]
|
||||
mod tests {
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
use air_test_utils::prelude::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
#[test]
|
||||
fn test_execution() {
|
||||
let exec = TestExecutor::new(
|
||||
@ -248,7 +251,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result() {
|
||||
fn test_seq_ok() {
|
||||
let exec = TestExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
@ -260,7 +263,7 @@ mod tests {
|
||||
(ap 1 k)
|
||||
(fold var i
|
||||
(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)))))
|
||||
(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]
|
||||
fn test_echo() {
|
||||
let exec = TestExecutor::new(
|
||||
|
@ -21,8 +21,9 @@ use air_test_utils::{
|
||||
prelude::{echo_call_service, unit_call_service},
|
||||
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 {
|
||||
results: HashMap<u32, CallServiceClosure>,
|
||||
@ -37,8 +38,10 @@ impl TryInto<CallServiceClosure> for ServiceDefinition {
|
||||
Ok(Box::new(move |_| CallServiceResult::ok(jvalue.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::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);
|
||||
|
||||
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 {
|
||||
pub(crate) fn new(results: HashMap<u32, ServiceDefinition>) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod parser;
|
||||
pub(crate) mod parser;
|
||||
pub(crate) mod walker;
|
||||
|
||||
use crate::asserts::ServiceDefinition;
|
||||
|
@ -15,16 +15,16 @@
|
||||
*/
|
||||
|
||||
use super::{Call, Sexp, Triplet};
|
||||
use crate::asserts::{parser::delim_ws, ServiceDefinition};
|
||||
use crate::asserts::ServiceDefinition;
|
||||
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{is_not, tag};
|
||||
use nom::character::complete::{alphanumeric1, multispace0, multispace1, one_of, space1};
|
||||
use nom::combinator::{cut, map, map_res, opt, recognize, value};
|
||||
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::IResult;
|
||||
use nom::{IResult, InputTakeAtPosition};
|
||||
use nom_locate::LocatedSpan;
|
||||
|
||||
use std::str::FromStr;
|
||||
@ -87,11 +87,11 @@ fn parse_sexp_list(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>> {
|
||||
context(
|
||||
"within generic list",
|
||||
preceded(
|
||||
terminated(tag("("), multispace0),
|
||||
terminated(tag("("), sexp_multispace0),
|
||||
cut(terminated(
|
||||
map(separated_list0(multispace1, parse_sexp), Sexp::list),
|
||||
map(separated_list0(sexp_multispace1, parse_sexp), Sexp::list),
|
||||
preceded(
|
||||
multispace0,
|
||||
sexp_multispace0,
|
||||
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
|
||||
pair(
|
||||
terminated(
|
||||
opt(preceded(multispace1, map(parse_sexp_symbol, Box::new))),
|
||||
preceded(multispace0, tag(")")),
|
||||
opt(preceded(sexp_multispace1, map(parse_sexp_symbol, Box::new))),
|
||||
preceded(sexp_multispace0, tag(")")),
|
||||
),
|
||||
alt((
|
||||
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(
|
||||
separated_pair(
|
||||
context("triplet peer_id", parse_sexp),
|
||||
multispace0,
|
||||
sexp_multispace0,
|
||||
delimited(
|
||||
delim_ws(tag("(")),
|
||||
separated_pair(
|
||||
context("triplet service name", parse_sexp_string),
|
||||
multispace0,
|
||||
sexp_multispace0,
|
||||
context("triplet function name", parse_sexp),
|
||||
),
|
||||
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)
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::asserts::ServiceDefinition;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
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]
|
||||
fn test_symbol() {
|
||||
@ -516,4 +627,37 @@ mod tests {
|
||||
let res = Sexp::from_str(sexp_str);
|
||||
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…
Reference in New Issue
Block a user