mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 04:30:48 +00:00
feat(testing-framework): tetraplet behavior (#396)
`behavior=tetraplet` annotation crates a service that returns call's tetraplets
This commit is contained in:
parent
9fe7afb897
commit
e5837e9171
129
crates/testing-framework/src/asserts/behavior.rs
Normal file
129
crates/testing-framework/src/asserts/behavior.rs
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 air_test_utils::{
|
||||
prelude::{echo_call_service, unit_call_service},
|
||||
CallServiceResult,
|
||||
};
|
||||
use nom::IResult;
|
||||
use serde_json::json;
|
||||
use strum::{AsRefStr, EnumDiscriminants, EnumString};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants)]
|
||||
#[strum_discriminants(strum(serialize_all = "snake_case"))]
|
||||
#[strum_discriminants(derive(AsRefStr, EnumString))]
|
||||
#[strum_discriminants(name(BehaviorTagName))]
|
||||
pub enum Behavior {
|
||||
Echo,
|
||||
Unit,
|
||||
Service,
|
||||
Function,
|
||||
Arg(usize),
|
||||
Tetraplet,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_behaviour(inp: &str) -> IResult<&str, Behavior, super::parser::ParseError> {
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::u32 as u32_parse;
|
||||
use nom::combinator::{map, value};
|
||||
use nom::sequence::{pair, preceded};
|
||||
|
||||
alt((
|
||||
value(Behavior::Echo, tag(BehaviorTagName::Echo.as_ref())),
|
||||
value(Behavior::Unit, tag(BehaviorTagName::Unit.as_ref())),
|
||||
value(Behavior::Function, tag(BehaviorTagName::Function.as_ref())),
|
||||
value(Behavior::Service, tag(BehaviorTagName::Service.as_ref())),
|
||||
map(
|
||||
preceded(
|
||||
pair(tag(BehaviorTagName::Arg.as_ref()), tag(".")),
|
||||
u32_parse,
|
||||
),
|
||||
|n| Behavior::Arg(n as _),
|
||||
),
|
||||
value(
|
||||
Behavior::Tetraplet,
|
||||
tag(BehaviorTagName::Tetraplet.as_ref()),
|
||||
),
|
||||
))(inp)
|
||||
}
|
||||
|
||||
impl Behavior {
|
||||
pub(crate) fn call(&self, params: air_test_utils::CallRequestParams) -> CallServiceResult {
|
||||
use Behavior::*;
|
||||
|
||||
match self {
|
||||
Echo => echo_call_service()(params),
|
||||
Unit => unit_call_service()(params),
|
||||
Function => CallServiceResult::ok(params.function_name.into()),
|
||||
Service => CallServiceResult::ok(params.service_id.into()),
|
||||
Arg(n) => match params.arguments.get(*n) {
|
||||
Some(val) => CallServiceResult::ok(val.clone()),
|
||||
None => CallServiceResult::err(
|
||||
// TODO test-utils uses just json!{ "default" } value.
|
||||
42,
|
||||
json!("not enough arguments"),
|
||||
),
|
||||
},
|
||||
Tetraplet => CallServiceResult::ok(serde_json::to_value(¶ms.tetraplets).unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_echo() {
|
||||
let res = parse_behaviour("echo");
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(("", Behavior::Echo)),
|
||||
"{:?}",
|
||||
BehaviorTagName::Echo.as_ref()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_unit() {
|
||||
let res = parse_behaviour("unit");
|
||||
assert_eq!(res, Ok(("", Behavior::Unit)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_service() {
|
||||
let res = parse_behaviour("service");
|
||||
assert_eq!(res, Ok(("", Behavior::Service)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_function() {
|
||||
let res = parse_behaviour("function");
|
||||
assert_eq!(res, Ok(("", Behavior::Function)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_arg() {
|
||||
let res = parse_behaviour("arg.42");
|
||||
assert_eq!(res, Ok(("", Behavior::Arg(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_behavior_tetraplet() {
|
||||
let res = parse_behaviour("tetraplet");
|
||||
assert_eq!(res, Ok(("", Behavior::Tetraplet)));
|
||||
}
|
||||
}
|
@ -14,20 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod behavior;
|
||||
mod json;
|
||||
pub(crate) mod parser;
|
||||
|
||||
use crate::services::JValue;
|
||||
|
||||
use air_test_utils::{
|
||||
prelude::{echo_call_service, unit_call_service},
|
||||
CallRequestParams, CallServiceResult,
|
||||
};
|
||||
use air_test_utils::{CallRequestParams, CallServiceResult};
|
||||
use serde_json::json;
|
||||
use strum::{AsRefStr, EnumDiscriminants, EnumString};
|
||||
|
||||
use std::{borrow::Cow, cell::Cell, collections::HashMap};
|
||||
|
||||
use self::behavior::Behavior;
|
||||
|
||||
/// Service definition in the testing framework comment DSL.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, EnumDiscriminants)]
|
||||
#[strum_discriminants(derive(AsRefStr, EnumString))]
|
||||
@ -53,7 +53,7 @@ pub enum ServiceDefinition {
|
||||
},
|
||||
/// Some known service by name: "echo", "unit" (more to follow).
|
||||
#[strum_discriminants(strum(serialize = "behaviour"))]
|
||||
Behaviour(String),
|
||||
Behaviour(Behavior),
|
||||
/// Maps first argument to a value
|
||||
#[strum_discriminants(strum(serialize = "map"))]
|
||||
Map(HashMap<String, JValue>),
|
||||
@ -82,8 +82,8 @@ impl ServiceDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn behaviour(name: impl Into<String>) -> Self {
|
||||
Self::Behaviour(name.into())
|
||||
pub fn behaviour(name: Behavior) -> Self {
|
||||
Self::Behaviour(name)
|
||||
}
|
||||
|
||||
pub fn map(map: HashMap<String, JValue>) -> Self {
|
||||
@ -102,7 +102,7 @@ impl ServiceDefinition {
|
||||
ref call_number_seq,
|
||||
call_map,
|
||||
} => call_seq_error(call_number_seq, call_map),
|
||||
ServiceDefinition::Behaviour(name) => call_named_service(name, params),
|
||||
ServiceDefinition::Behaviour(name) => name.call(params),
|
||||
ServiceDefinition::Map(map) => call_map_service(map, params),
|
||||
}
|
||||
}
|
||||
@ -149,14 +149,6 @@ fn call_seq_error(
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn call_named_service(name: &str, params: CallRequestParams) -> CallServiceResult {
|
||||
match name {
|
||||
"echo" => echo_call_service()(params),
|
||||
"unit" => unit_call_service()(params),
|
||||
_ => unreachable!("shoudn't be allowed by a parser"),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_map_service(
|
||||
map: &HashMap<String, serde_json::Value>,
|
||||
args: CallRequestParams,
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
use super::{ServiceDefinition, ServiceTagName};
|
||||
use crate::services::JValue;
|
||||
use crate::transform::parser::delim_ws;
|
||||
|
||||
use air_test_utils::CallServiceResult;
|
||||
@ -23,7 +22,7 @@ use nom::{error::VerboseError, IResult};
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
type ParseError<'inp> = VerboseError<&'inp str>;
|
||||
pub(crate) type ParseError<'inp> = VerboseError<&'inp str>;
|
||||
|
||||
impl FromStr for ServiceDefinition {
|
||||
type Err = String;
|
||||
@ -38,12 +37,12 @@ impl FromStr for ServiceDefinition {
|
||||
// kw "=" val
|
||||
// example: "id=firstcall"
|
||||
pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
use super::behavior::parse_behaviour;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::alphanumeric1;
|
||||
use nom::combinator::{cut, map_res, recognize};
|
||||
use nom::combinator::{cut, map, map_res, recognize};
|
||||
use nom::error::context;
|
||||
use nom::sequence::separated_pair;
|
||||
use nom::sequence::{pair, preceded};
|
||||
|
||||
let equal = || delim_ws(tag("="));
|
||||
let json_value = || {
|
||||
@ -59,45 +58,56 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
))
|
||||
};
|
||||
|
||||
delim_ws(map_res(
|
||||
alt((
|
||||
separated_pair(tag(ServiceTagName::Ok.as_ref()), equal(), json_value()),
|
||||
separated_pair(tag(ServiceTagName::Error.as_ref()), equal(), json_map()),
|
||||
separated_pair(tag(ServiceTagName::SeqOk.as_ref()), equal(), json_map()),
|
||||
separated_pair(tag(ServiceTagName::SeqError.as_ref()), equal(), json_map()),
|
||||
separated_pair(
|
||||
tag(ServiceTagName::Behaviour.as_ref()),
|
||||
equal(),
|
||||
cut(alphanumeric1),
|
||||
delim_ws(alt((
|
||||
map_res(
|
||||
preceded(
|
||||
pair(tag(ServiceTagName::Ok.as_ref()), equal()),
|
||||
json_value(),
|
||||
),
|
||||
separated_pair(tag(ServiceTagName::Map.as_ref()), equal(), json_map()),
|
||||
)),
|
||||
|(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::SeqOk) => {
|
||||
serde_json::from_str(value).map(ServiceDefinition::seq_ok)
|
||||
}
|
||||
Ok(ServiceTagName::SeqError) => {
|
||||
serde_json::from_str::<HashMap<String, CallServiceResult>>(value)
|
||||
.map(ServiceDefinition::seq_error)
|
||||
}
|
||||
Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::behaviour(value)),
|
||||
Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::map),
|
||||
Err(_) => unreachable!("unknown tag {:?}", tag),
|
||||
}
|
||||
},
|
||||
))(inp)
|
||||
|value| serde_json::from_str(value).map(ServiceDefinition::Ok),
|
||||
),
|
||||
map_res(
|
||||
preceded(
|
||||
pair(tag(ServiceTagName::Error.as_ref()), equal()),
|
||||
json_map(),
|
||||
),
|
||||
|value| serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::Error),
|
||||
),
|
||||
map_res(
|
||||
preceded(
|
||||
pair(tag(ServiceTagName::SeqOk.as_ref()), equal()),
|
||||
json_map(),
|
||||
),
|
||||
|value| serde_json::from_str(value).map(ServiceDefinition::seq_ok),
|
||||
),
|
||||
map_res(
|
||||
preceded(
|
||||
pair(tag(ServiceTagName::SeqError.as_ref()), equal()),
|
||||
json_map(),
|
||||
),
|
||||
|value| {
|
||||
serde_json::from_str::<HashMap<String, CallServiceResult>>(value)
|
||||
.map(ServiceDefinition::seq_error)
|
||||
},
|
||||
),
|
||||
map(
|
||||
preceded(
|
||||
pair(tag(ServiceTagName::Behaviour.as_ref()), equal()),
|
||||
cut(parse_behaviour),
|
||||
),
|
||||
ServiceDefinition::Behaviour,
|
||||
),
|
||||
map_res(
|
||||
preceded(pair(tag(ServiceTagName::Map.as_ref()), equal()), json_map()),
|
||||
|value| serde_json::from_str(value).map(ServiceDefinition::Map),
|
||||
),
|
||||
)))(inp)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::asserts::behavior::Behavior;
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@ -217,7 +227,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_behaviour() {
|
||||
let res = ServiceDefinition::from_str(r#"behaviour=echo"#);
|
||||
assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),);
|
||||
assert_eq!(res, Ok(ServiceDefinition::Behaviour(Behavior::Echo)),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -542,10 +542,7 @@ mod tests {
|
||||
impl MarineService for Service {
|
||||
fn call(&self, _params: CallRequestParams) -> crate::services::FunctionOutcome {
|
||||
let mut cell = self.state.borrow_mut();
|
||||
crate::services::FunctionOutcome::ServiceResult(
|
||||
CallServiceResult::ok(cell.next().unwrap()),
|
||||
<_>::default(),
|
||||
)
|
||||
crate::services::FunctionOutcome::from_value(cell.next().unwrap())
|
||||
}
|
||||
}
|
||||
let service = Service {
|
||||
@ -606,4 +603,110 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_service() {
|
||||
let peer = "peer1";
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id(peer),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
r#"(call "peer1" ("service" "func") [1 22] arg) ; behaviour=service"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_init: Vec<_> = exec.execution_iter(peer).unwrap().collect();
|
||||
|
||||
assert_eq!(result_init.len(), 1);
|
||||
let outcome = &result_init[0];
|
||||
assert_eq!(outcome.ret_code, 0);
|
||||
assert_eq!(outcome.error_message, "");
|
||||
|
||||
assert_eq!(
|
||||
trace_from_result(outcome),
|
||||
ExecutionTrace::from(vec![scalar_string("service"),]),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_function() {
|
||||
let peer = "peer1";
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id(peer),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
r#"(call "peer1" ("service" "func") [1 22] arg) ; behaviour=function"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_init: Vec<_> = exec.execution_iter(peer).unwrap().collect();
|
||||
|
||||
assert_eq!(result_init.len(), 1);
|
||||
let outcome = &result_init[0];
|
||||
assert_eq!(outcome.ret_code, 0);
|
||||
assert_eq!(outcome.error_message, "");
|
||||
|
||||
assert_eq!(
|
||||
trace_from_result(outcome),
|
||||
ExecutionTrace::from(vec![scalar_string("func"),]),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_arg() {
|
||||
let peer = "peer1";
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id(peer),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
r#"(call "peer1" ("service" "func") [1 22] arg) ; behaviour=arg.1"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_init: Vec<_> = exec.execution_iter(peer).unwrap().collect();
|
||||
|
||||
assert_eq!(result_init.len(), 1);
|
||||
let outcome = &result_init[0];
|
||||
assert_eq!(outcome.ret_code, 0);
|
||||
assert_eq!(outcome.error_message, "");
|
||||
|
||||
assert_eq!(
|
||||
trace_from_result(outcome),
|
||||
ExecutionTrace::from(vec![scalar_number(22),]),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_tetraplet() {
|
||||
let peer = "peer1";
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id(peer),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
r#"(call "peer1" ("service" "func") [1 22] arg) ; behaviour=tetraplet"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result_init: Vec<_> = exec.execution_iter(peer).unwrap().collect();
|
||||
|
||||
assert_eq!(result_init.len(), 1);
|
||||
let outcome = &result_init[0];
|
||||
assert_eq!(outcome.ret_code, 0);
|
||||
assert_eq!(outcome.error_message, "");
|
||||
|
||||
assert_eq!(
|
||||
trace_from_result(outcome),
|
||||
ExecutionTrace::from(vec![scalar(json!([[{
|
||||
"function_name": "",
|
||||
"json_path": "",
|
||||
"peer_pk": "peer1",
|
||||
"service_id": "",
|
||||
}], [{
|
||||
"function_name": "",
|
||||
"json_path": "",
|
||||
"peer_pk": "peer1",
|
||||
"service_id": "",
|
||||
}]]))]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,16 @@ pub enum FunctionOutcome {
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl FunctionOutcome {
|
||||
pub fn from_service_result(service_result: CallServiceResult) -> Self {
|
||||
FunctionOutcome::ServiceResult(service_result, Duration::ZERO)
|
||||
}
|
||||
|
||||
pub fn from_value(value: JValue) -> Self {
|
||||
Self::from_service_result(CallServiceResult::ok(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// A mocked Marine service.
|
||||
pub trait MarineService {
|
||||
fn call(&self, params: CallRequestParams) -> FunctionOutcome;
|
||||
|
@ -19,7 +19,7 @@ use crate::asserts::ServiceDefinition;
|
||||
|
||||
use air_test_utils::CallRequestParams;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct ResultStore {
|
||||
@ -36,18 +36,23 @@ impl ResultStore {
|
||||
}
|
||||
|
||||
impl MarineService for ResultStore {
|
||||
fn call(&self, params: CallRequestParams) -> FunctionOutcome {
|
||||
fn call(&self, mut params: CallRequestParams) -> FunctionOutcome {
|
||||
let results = self.results.borrow();
|
||||
if let Some((_, suffix)) = params.service_id.split_once("..") {
|
||||
if let Ok(key) = suffix.parse() {
|
||||
let service_desc = results.get(&key).expect("Unknown result id");
|
||||
let service_result = service_desc.call(params);
|
||||
FunctionOutcome::ServiceResult(service_result, Duration::ZERO)
|
||||
} else {
|
||||
// Pass malformed service names further in a chain
|
||||
FunctionOutcome::NotDefined
|
||||
}
|
||||
|
||||
let (real_service_id, suffix) = match params.service_id.rsplit_once("..") {
|
||||
Some(split) => split,
|
||||
None => return FunctionOutcome::NotDefined,
|
||||
};
|
||||
|
||||
if let Ok(result_id) = suffix.parse::<usize>() {
|
||||
let service_desc = results
|
||||
.get(&result_id)
|
||||
.unwrap_or_else(|| panic!("failed to parse service name {:?}", params.service_id));
|
||||
// hide the artificial service_id
|
||||
params.service_id = real_service_id.to_owned();
|
||||
FunctionOutcome::from_service_result(service_desc.call(params))
|
||||
} else {
|
||||
// Pass malformed service names further in a chain
|
||||
FunctionOutcome::NotDefined
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user