mirror of
https://github.com/fluencelabs/aquavm
synced 2025-06-20 10:16:30 +00:00
Testing framework, chapter I (#293)
Testing framework for AquaVM Its primary features are: 1. It generates services declaratively by annotation in the comments inserted just after calls. 2. Ephemeral network keeps each node's data and incoming data queue. The network can be also generated based on peer IDs featured in the script. 3. One can explicitly add additional peers and services. The example of the script annotations: ``` (seq (call "peer_1" ("service" "func") [] var) ; ok=42 (call "peer_2" ("service" "func") [var]) ; err={"ret_code": 1, "result":"no towel"} ) ``` Passing this script to `air_test_framework::TestExecutor::new(...)` will create a virtual network with peers "peer_1" and "peer_2" (and any peer you provide in the `TestRunParameters`), and the particular calls will return respective values. Please note that autogenerated services use modified service name as a side channel for finding a correct value: "..{N}" is added to each service name (two dots and serial number). Be careful with service names taken from a variable.
This commit is contained in:
186
crates/testing-framework/src/asserts/parser.rs
Normal file
186
crates/testing-framework/src/asserts/parser.rs
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2022 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::{ServiceDefinition, ServiceTagName};
|
||||
use crate::services::JValue;
|
||||
|
||||
use air_test_utils::CallServiceResult;
|
||||
use nom::{error::VerboseError, IResult, InputTakeAtPosition, Parser};
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
type ParseError<'inp> = VerboseError<&'inp str>;
|
||||
|
||||
impl FromStr for ServiceDefinition {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
nom::combinator::all_consuming(parse_kw)(s)
|
||||
.map(|(_, service_definition)| service_definition)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// kw "=" val
|
||||
// example: "id=firstcall"
|
||||
pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::{cut, map_res, rest};
|
||||
use nom::error::context;
|
||||
use nom::sequence::separated_pair;
|
||||
|
||||
let equal = || delim_ws(tag("="));
|
||||
|
||||
delim_ws(map_res(
|
||||
separated_pair(
|
||||
alt((
|
||||
tag(ServiceTagName::Ok.as_ref()),
|
||||
tag(ServiceTagName::Error.as_ref()),
|
||||
tag(ServiceTagName::SeqResult.as_ref()),
|
||||
tag(ServiceTagName::Behaviour.as_ref()),
|
||||
)),
|
||||
equal(),
|
||||
cut(context(
|
||||
"result value is consumed to end and has to be a valid JSON",
|
||||
rest,
|
||||
)),
|
||||
),
|
||||
|(tag, value): (&str, &str)| {
|
||||
let value = value.trim();
|
||||
match ServiceTagName::from_str(tag) {
|
||||
Ok(ServiceTagName::Ok) => {
|
||||
serde_json::from_str::<JValue>(value).map(ServiceDefinition::Ok)
|
||||
}
|
||||
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::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())),
|
||||
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::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty() {
|
||||
let res = ServiceDefinition::from_str("");
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_garbage0() {
|
||||
let res = ServiceDefinition::from_str("garbage");
|
||||
assert!(res.is_err(), "{:?}", res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_service() {
|
||||
use serde_json::json;
|
||||
|
||||
let res = ServiceDefinition::from_str(r#"ok={"this":["is","value"]}"#);
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(ServiceDefinition::Ok(json!({"this": ["is", "value"]}))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_service_malformed() {
|
||||
let res = ServiceDefinition::from_str(r#"ok={"this":["is","value"]"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_result() {
|
||||
use serde_json::json;
|
||||
|
||||
let res = ServiceDefinition::from_str(r#"err={"ret_code": 0, "result": [1, 2, 3]}"#);
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(ServiceDefinition::Error(CallServiceResult::ok(json!([
|
||||
1, 2, 3
|
||||
])))),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_result_malformed() {
|
||||
let res = ServiceDefinition::from_str(r#"err={"retcode": 0, "result": [1, 2, 3]}"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_result_invalid() {
|
||||
let res = ServiceDefinition::from_str(r#"err={"ret_code": 0, "result": 1, 2, 3]}"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result() {
|
||||
use serde_json::json;
|
||||
|
||||
let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": []}"#);
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(ServiceDefinition::SeqResult(maplit::hashmap! {
|
||||
"default".to_owned() => json!(42),
|
||||
"1".to_owned() => json!(true),
|
||||
"3".to_owned() => json!([]),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result_malformed() {
|
||||
let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": ]}"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq_result_invalid() {
|
||||
// TODO perhaps, we should support both arrays and maps
|
||||
let res = ServiceDefinition::from_str(r#"seq_result=[42, 43]"#);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour() {
|
||||
let res = ServiceDefinition::from_str(r#"behaviour=echo"#);
|
||||
assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user