feat(testing-framework): Testing framework major refactoring (#372)

1. Network can be shared between several execution, being used by an Rc-handle.
2. The neighborhood is just network's all peers with removed/inserted hosts delta with respect to network.
3. An AIR script is transformed into a separate value of type `TransformedAirScript`.  It allows running several
particles on the same parsed AIR script, sharing state.
4. `TestExecutor` was renamed to `AirScriptExecutor`.  It also has a constructor that accepts a `TransformedAirScript`.
This commit is contained in:
Ivan Boldyrev 2022-11-24 19:33:55 +03:00 committed by GitHub
parent 7ac0ab109c
commit 4e86da7eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 857 additions and 505 deletions

View File

@ -15,7 +15,7 @@
*/ */
use air::CatchableError; use air::CatchableError;
use air_test_framework::TestExecutor; use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use std::cell::RefCell; use std::cell::RefCell;
@ -30,7 +30,7 @@ fn length_functor_for_array_scalar() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();
@ -54,7 +54,7 @@ fn length_functor_for_non_array_scalar() {
"#); "#);
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();
@ -76,7 +76,7 @@ fn length_functor_for_stream() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();
@ -99,7 +99,7 @@ fn length_functor_for_empty_stream() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();
@ -124,7 +124,7 @@ fn length_functor_for_canon_stream() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();
@ -156,7 +156,7 @@ fn length_functor_for_empty_canon_stream() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let result = executor.execute_one(init_peer_id).unwrap(); let result = executor.execute_one(init_peer_id).unwrap();

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air_test_framework::TestExecutor; use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -82,7 +82,7 @@ fn merging_fold_iterations_extensively() {
) )
"#; "#;
let engine = TestExecutor::new( let engine = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("client"), TestRunParameters::from_init_peer_id("client"),
vec![], vec![],
vec!["relay", "p1", "p2", "p3"].into_iter().map(Into::into), vec!["relay", "p1", "p2", "p3"].into_iter().map(Into::into),
@ -216,7 +216,7 @@ fn merging_fold_iterations_extensively_2() {
) )
"#; "#;
let engine = TestExecutor::new( let engine = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("client"), TestRunParameters::from_init_peer_id("client"),
vec![], vec![],
vec!["relay", "p1", "p2", "p3"].into_iter().map(Into::into), vec!["relay", "p1", "p2", "p3"].into_iter().map(Into::into),

View File

@ -37,7 +37,7 @@ fn par_ap_behaviour() {
) )
"#); "#);
let engine = air_test_framework::TestExecutor::simple(TestRunParameters::new("client_id", 0, 1), &script) let engine = air_test_framework::AirScriptExecutor::simple(TestRunParameters::new("client_id", 0, 1), &script)
.expect("invalid test executor config"); .expect("invalid test executor config");
let client_result_1 = engine.execute_one(client_id).unwrap(); let client_result_1 = engine.execute_one(client_id).unwrap();

View File

@ -59,7 +59,8 @@ fn issue_211() {
let run_params = TestRunParameters::from_init_peer_id(peer_1_id); let run_params = TestRunParameters::from_init_peer_id(peer_1_id);
let engine = air_test_framework::TestExecutor::simple(run_params, &script).expect("invalid test executor config"); let engine =
air_test_framework::AirScriptExecutor::simple(run_params, &script).expect("invalid test executor config");
let result = engine.execute_one(peer_1_id).unwrap(); let result = engine.execute_one(peer_1_id).unwrap();

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air_test_framework::TestExecutor; use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -66,7 +66,7 @@ fn issue_221() {
) )
"#); "#);
let executor = TestExecutor::new( let executor = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("set_variable_id"), TestRunParameters::from_init_peer_id("set_variable_id"),
vec![], vec![],
vec![peer_1_id, peer_2_id].into_iter().map(Into::into), vec![peer_1_id, peer_2_id].into_iter().map(Into::into),

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air_test_framework::TestExecutor; use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -33,7 +33,7 @@ fn issue_304() {
"#; "#;
let init_peer_id = "init_peer_id"; let init_peer_id = "init_peer_id";
let executor = TestExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script) let executor = AirScriptExecutor::simple(TestRunParameters::from_init_peer_id(init_peer_id), &script)
.expect("invalid test AIR script"); .expect("invalid test AIR script");
let res = executor.execute_one(init_peer_id).unwrap(); let res = executor.execute_one(init_peer_id).unwrap();

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use air_test_framework::TestExecutor; use air_test_framework::AirScriptExecutor;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
#[test] #[test]
@ -48,7 +48,7 @@ fn issue_356() {
) )
"#; "#;
let engine = TestExecutor::new( let engine = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("client"), TestRunParameters::from_init_peer_id("client"),
vec![], vec![],
vec!["p1", "p2", "p3"].into_iter().map(Into::into), vec!["p1", "p2", "p3"].into_iter().map(Into::into),

View File

@ -18,10 +18,14 @@ pub(crate) mod parser;
use crate::services::JValue; use crate::services::JValue;
use air_test_utils::CallServiceResult; use air_test_utils::{
prelude::{echo_call_service, unit_call_service},
CallRequestParams, CallServiceResult,
};
use serde_json::json;
use strum::{AsRefStr, EnumDiscriminants, EnumString}; use strum::{AsRefStr, EnumDiscriminants, EnumString};
use std::collections::HashMap; use std::{borrow::Cow, cell::Cell, collections::HashMap};
/// Service definition in the testing framework comment DSL. /// Service definition in the testing framework comment DSL.
#[derive(Debug, PartialEq, Eq, Clone, EnumDiscriminants)] #[derive(Debug, PartialEq, Eq, Clone, EnumDiscriminants)]
@ -37,9 +41,15 @@ pub enum ServiceDefinition {
/// 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".
#[strum_discriminants(strum(serialize = "seq_ok"))] #[strum_discriminants(strum(serialize = "seq_ok"))]
SeqOk(HashMap<String, JValue>), SeqOk {
call_number_seq: Cell<usize>,
call_map: HashMap<String, JValue>,
},
#[strum_discriminants(strum(serialize = "seq_error"))] #[strum_discriminants(strum(serialize = "seq_error"))]
SeqError(HashMap<String, CallServiceResult>), SeqError {
call_number_seq: Cell<usize>,
call_map: 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),
@ -47,3 +57,120 @@ pub enum ServiceDefinition {
#[strum_discriminants(strum(serialize = "map"))] #[strum_discriminants(strum(serialize = "map"))]
Map(HashMap<String, JValue>), Map(HashMap<String, JValue>),
} }
impl ServiceDefinition {
pub fn ok(value: JValue) -> Self {
Self::Ok(value)
}
pub fn error(value: CallServiceResult) -> Self {
Self::Error(value)
}
pub fn seq_ok(call_map: HashMap<String, JValue>) -> Self {
Self::SeqOk {
call_number_seq: 0.into(),
call_map,
}
}
pub fn seq_error(call_map: HashMap<String, CallServiceResult>) -> Self {
Self::SeqError {
call_number_seq: 0.into(),
call_map,
}
}
pub fn behaviour(name: impl Into<String>) -> Self {
Self::Behaviour(name.into())
}
pub fn map(map: HashMap<String, JValue>) -> Self {
Self::Map(map)
}
pub fn call(&self, params: CallRequestParams) -> CallServiceResult {
match self {
ServiceDefinition::Ok(ok) => CallServiceResult::ok(ok.clone()),
ServiceDefinition::Error(call_result) => call_result.clone(),
ServiceDefinition::SeqOk {
ref call_number_seq,
call_map,
} => call_seq_ok(call_number_seq, call_map),
ServiceDefinition::SeqError {
ref call_number_seq,
call_map,
} => call_seq_error(call_number_seq, call_map),
ServiceDefinition::Behaviour(name) => call_named_service(name, params),
ServiceDefinition::Map(map) => call_map_service(map, params),
}
}
}
fn call_seq_ok(
call_number_seq: &Cell<usize>,
call_map: &HashMap<String, serde_json::Value>,
) -> CallServiceResult {
let call_number = call_number_seq.get();
let call_num_str = call_number.to_string();
call_number_seq.set(call_number + 1);
let value = call_map
.get(&call_num_str)
.or_else(|| call_map.get("default"))
.unwrap_or_else(|| {
panic!(
r#"neither key {:?} nor "default" key not found in the {:?}"#,
call_num_str, call_map
)
})
.clone();
CallServiceResult::ok(value)
}
fn call_seq_error(
call_number_seq: &Cell<usize>,
call_map: &HashMap<String, CallServiceResult>,
) -> CallServiceResult {
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!(
r#"neither key {:?} nor "default" key not found in the {:?}"#,
call_num_str, call_map
)
})
.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,
) -> CallServiceResult {
let key = args
.arguments
.get(0)
.expect("At least one arugment expected");
// Strings are looked up by value, other objects -- by their 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()))
}

View File

@ -66,20 +66,20 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
let value = value.trim(); let value = value.trim();
match ServiceTagName::from_str(tag) { match ServiceTagName::from_str(tag) {
Ok(ServiceTagName::Ok) => { Ok(ServiceTagName::Ok) => {
serde_json::from_str::<JValue>(value).map(ServiceDefinition::Ok) serde_json::from_str::<JValue>(value).map(ServiceDefinition::ok)
} }
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::SeqOk) => { Ok(ServiceTagName::SeqOk) => {
serde_json::from_str(value).map(ServiceDefinition::SeqOk) serde_json::from_str(value).map(ServiceDefinition::seq_ok)
} }
Ok(ServiceTagName::SeqError) => { Ok(ServiceTagName::SeqError) => {
serde_json::from_str::<HashMap<String, CallServiceResult>>(value) serde_json::from_str::<HashMap<String, CallServiceResult>>(value)
.map(ServiceDefinition::SeqError) .map(ServiceDefinition::seq_error)
} }
Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())), Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::behaviour(value)),
Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::Map), Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::map),
Err(_) => unreachable!("unknown tag {:?}", tag), Err(_) => unreachable!("unknown tag {:?}", tag),
} }
}, },
@ -153,7 +153,7 @@ mod tests {
let res = ServiceDefinition::from_str(r#"seq_ok={"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::SeqOk(maplit::hashmap! { Ok(ServiceDefinition::seq_ok(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!([]),
@ -183,7 +183,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
res, res,
Ok(ServiceDefinition::SeqError(maplit::hashmap! { Ok(ServiceDefinition::seq_error(maplit::hashmap! {
"default".to_owned() => CallServiceResult::ok(json!(42)), "default".to_owned() => CallServiceResult::ok(json!(42)),
"1".to_owned() => CallServiceResult::ok(json!(true)), "1".to_owned() => CallServiceResult::ok(json!(true)),
"3".to_owned() => CallServiceResult::err(1, json!("error")), "3".to_owned() => CallServiceResult::err(1, json!("error")),

View File

@ -17,38 +17,35 @@
pub mod neighborhood; pub mod neighborhood;
use self::neighborhood::{PeerEnv, PeerSet}; use self::neighborhood::{PeerEnv, PeerSet};
use crate::services::{services_to_call_service_closure, MarineServiceHandle}; use crate::{
queue::PeerQueueCell,
services::{services_to_call_service_closure, MarineServiceHandle, NetworkServices},
};
use air_test_utils::{ use air_test_utils::{
test_runner::{create_avm, TestRunParameters, TestRunner}, test_runner::{create_avm, TestRunParameters, TestRunner},
RawAVMOutcome, RawAVMOutcome,
}; };
use std::{ use std::{borrow::Borrow, cell::RefCell, collections::HashMap, hash::Hash, rc::Rc};
borrow::Borrow,
cell::RefCell,
collections::{HashMap, HashSet},
hash::Hash,
rc::Rc,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PeerId(String); pub struct PeerId(Rc<str>);
impl PeerId { impl PeerId {
pub fn new(peer_id: impl Into<String>) -> Self { pub fn new<'any>(peer_id: impl Into<&'any str>) -> Self {
Self(peer_id.into()) Self(peer_id.into().into())
} }
} }
impl From<String> for PeerId { impl From<String> for PeerId {
fn from(source: String) -> Self { fn from(source: String) -> Self {
Self(source) Self(source.as_str().into())
} }
} }
impl From<&str> for PeerId { impl From<&str> for PeerId {
fn from(source: &str) -> Self { fn from(source: &str) -> Self {
Self(source.to_owned()) Self(source.into())
} }
} }
@ -61,9 +58,7 @@ impl Borrow<str> for PeerId {
pub type Data = Vec<u8>; pub type Data = Vec<u8>;
pub struct Peer { pub struct Peer {
peer_id: PeerId, pub(crate) peer_id: PeerId,
// We presume that only one particle is run over the network.
prev_data: Data,
runner: TestRunner, runner: TestRunner,
} }
@ -71,26 +66,22 @@ impl Peer {
pub fn new(peer_id: impl Into<PeerId>, services: Rc<[MarineServiceHandle]>) -> Self { pub fn new(peer_id: impl Into<PeerId>, services: Rc<[MarineServiceHandle]>) -> Self {
let peer_id = Into::into(peer_id); let peer_id = Into::into(peer_id);
let call_service = services_to_call_service_closure(services); let call_service = services_to_call_service_closure(services);
let runner = create_avm(call_service, &peer_id.0); let runner = create_avm(call_service, &*peer_id.0);
Self { Self { peer_id, runner }
peer_id,
prev_data: vec![],
runner,
}
} }
pub fn invoke( pub(crate) fn invoke(
&mut self, &mut self,
air: impl Into<String>, air: impl Into<String>,
data: Data, data: Data,
test_run_params: TestRunParameters, test_run_params: TestRunParameters,
queue_cell: &PeerQueueCell,
) -> Result<RawAVMOutcome, String> { ) -> Result<RawAVMOutcome, String> {
let mut prev_data = vec![]; let prev_data = queue_cell.take_prev_data();
std::mem::swap(&mut prev_data, &mut self.prev_data);
let res = self.runner.call(air, prev_data, data, test_run_params); let res = self.runner.call(air, prev_data, data, test_run_params);
if let Ok(outcome) = &res { if let Ok(outcome) = &res {
self.prev_data = outcome.data.clone(); queue_cell.set_prev_data(outcome.data.clone());
} }
res res
} }
@ -100,32 +91,37 @@ impl std::fmt::Debug for Peer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Peer") f.debug_struct("Peer")
.field("peer_id", &self.peer_id) .field("peer_id", &self.peer_id)
.field("prev_data", &self.prev_data)
.field("services", &"...") .field("services", &"...")
.finish() .finish()
} }
} }
#[derive(Debug)]
pub struct Network { pub struct Network {
peers: HashMap<PeerId, Rc<RefCell<PeerEnv>>>, peers: RefCell<HashMap<PeerId, Rc<RefCell<PeerEnv>>>>,
default_neighborhood: HashSet<PeerId>, services: Rc<NetworkServices>,
} }
impl Network { impl Network {
pub fn empty() -> Self { pub fn empty() -> Rc<Self> {
Self::new(std::iter::empty::<&str>()) Self::new(std::iter::empty::<PeerId>(), vec![])
} }
pub fn new(default_neiborhoud: impl Iterator<Item = impl Into<PeerId>>) -> Self { pub fn new(
Self { peers: impl Iterator<Item = impl Into<PeerId>>,
common_services: Vec<MarineServiceHandle>,
) -> Rc<Self> {
let network = Rc::new(Self {
peers: Default::default(), peers: Default::default(),
default_neighborhood: default_neiborhoud.map(Into::into).collect(), services: NetworkServices::new(common_services).into(),
});
for peer_id in peers {
network.ensure_peer(peer_id);
} }
network
} }
pub fn from_peers(nodes: Vec<Peer>) -> Self { pub fn from_peers(nodes: Vec<Peer>) -> Rc<Self> {
let mut network = Self::empty(); let network = Self::empty();
let neighborhood: PeerSet = nodes.iter().map(|peer| peer.peer_id.clone()).collect(); let neighborhood: PeerSet = nodes.iter().map(|peer| peer.peer_id.clone()).collect();
for peer in nodes { for peer in nodes {
network.add_peer_env(peer, neighborhood.iter().cloned()); network.add_peer_env(peer, neighborhood.iter().cloned());
@ -134,29 +130,41 @@ impl Network {
} }
pub fn add_peer_env( pub fn add_peer_env(
&mut self, self: &Rc<Self>,
peer: Peer, peer: Peer,
neighborhood: impl IntoIterator<Item = impl Into<PeerId>>, neighborhood: impl IntoIterator<Item = impl Into<PeerId>>,
) -> &mut PeerEnv { ) {
let peer_id = peer.peer_id.clone(); let peer_id = peer.peer_id.clone();
let mut peer_env = PeerEnv::new(peer); let mut peer_env = PeerEnv::new(peer, self);
peer_env.extend_neighborhood(neighborhood.into_iter()); peer_env.extend_neighborhood(neighborhood.into_iter());
self.insert_peer_env_entry(peer_id, peer_env) self.insert_peer_env_entry(peer_id, peer_env);
}
pub fn ensure_peer(self: &Rc<Self>, peer_id: impl Into<PeerId>) {
let peer_id = peer_id.into();
let exists = {
let peers_ref = self.peers.borrow();
peers_ref.contains_key(&peer_id)
};
if !exists {
let peer = Peer::new(peer_id, self.services.get_services());
self.add_peer(peer);
}
} }
/// Add a peer with default neighborhood. /// Add a peer with default neighborhood.
pub fn add_peer(&mut self, peer: Peer) -> &mut PeerEnv { pub fn add_peer(self: &Rc<Self>, peer: Peer) {
let peer_id = peer.peer_id.clone(); let peer_id = peer.peer_id.clone();
let mut peer_env = PeerEnv::new(peer); let peer_env = PeerEnv::new(peer, self);
peer_env.extend_neighborhood(self.default_neighborhood.iter().cloned()); self.insert_peer_env_entry(peer_id, peer_env);
self.insert_peer_env_entry(peer_id, peer_env)
} }
fn insert_peer_env_entry(&mut self, peer_id: PeerId, peer_env: PeerEnv) -> &mut PeerEnv { fn insert_peer_env_entry(&self, peer_id: PeerId, peer_env: PeerEnv) {
let mut peers_ref = self.peers.borrow_mut();
let peer_env = Rc::new(peer_env.into()); let peer_env = Rc::new(peer_env.into());
// It will be simplified with entry_insert stabilization // It will be simplified with entry_insert stabilization
// https://github.com/rust-lang/rust/issues/65225 // https://github.com/rust-lang/rust/issues/65225
let cell = match self.peers.entry(peer_id) { match peers_ref.entry(peer_id) {
std::collections::hash_map::Entry::Occupied(ent) => { std::collections::hash_map::Entry::Occupied(ent) => {
let cell = ent.into_mut(); let cell = ent.into_mut();
*cell = peer_env; *cell = peer_env;
@ -164,8 +172,6 @@ impl Network {
} }
std::collections::hash_map::Entry::Vacant(ent) => ent.insert(peer_env), std::collections::hash_map::Entry::Vacant(ent) => ent.insert(peer_env),
}; };
// never panics because Rc have been just created and there's just single reference
Rc::get_mut(cell).unwrap().get_mut()
} }
pub fn set_peer_failed<Id>(&mut self, peer_id: &Id, failed: bool) pub fn set_peer_failed<Id>(&mut self, peer_id: &Id, failed: bool)
@ -173,7 +179,8 @@ impl Network {
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
Id: Hash + Eq + ?Sized, Id: Hash + Eq + ?Sized,
{ {
self.peers let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(peer_id) .get_mut(peer_id)
.expect("unknown peer") .expect("unknown peer")
.as_ref() .as_ref()
@ -186,7 +193,8 @@ impl Network {
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
Id: Hash + Eq + ?Sized, Id: Hash + Eq + ?Sized,
{ {
self.peers let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(source_peer_id) .get_mut(source_peer_id)
.expect("unknown peer") .expect("unknown peer")
.as_ref() .as_ref()
@ -202,7 +210,8 @@ impl Network {
PeerId: Borrow<Id2>, PeerId: Borrow<Id2>,
Id2: Hash + Eq + ?Sized, Id2: Hash + Eq + ?Sized,
{ {
self.peers let mut peers_ref = self.peers.borrow_mut();
peers_ref
.get_mut(source_peer_id) .get_mut(source_peer_id)
.expect("unknown peer") .expect("unknown peer")
.as_ref() .as_ref()
@ -218,41 +227,16 @@ impl Network {
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
Id: Hash + Eq + ?Sized, Id: Hash + Eq + ?Sized,
{ {
self.peers.get(peer_id).cloned() let peers_ref = self.peers.borrow();
peers_ref.get(peer_id).cloned()
} }
/// Iterator for handling al the queued data. It borrows peer env's `RefCell` only temporarily. pub(crate) fn get_services(&self) -> Rc<NetworkServices> {
/// Following test-utils' call_vm macro, it panics on failed VM. self.services.clone()
pub fn execution_iter<'s, Id>(
&'s self,
air: &'s str,
test_parameters: &'s TestRunParameters,
peer_id: &Id,
) -> Option<impl Iterator<Item = RawAVMOutcome> + 's>
where
PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized,
{
let peer_env = self.get_peer_env(peer_id);
peer_env.map(|peer_env_cell| {
std::iter::from_fn(move || {
let mut peer_env = peer_env_cell.borrow_mut();
peer_env
.execute_once(air, self, test_parameters)
.map(|r| r.unwrap_or_else(|err| panic!("VM call failed: {}", err)))
})
})
} }
pub fn distribute_to_peers(&self, peers: &[String], data: &Data) { pub fn get_peers(&self) -> impl Iterator<Item = PeerId> {
for peer_id in peers { let peers_ref = self.peers.borrow();
if let Some(peer_env_cell) = self.get_peer_env(peer_id.as_str()) { peers_ref.keys().cloned().collect::<Vec<_>>().into_iter()
peer_env_cell
.borrow_mut()
.data_queue
.push_back(data.clone());
}
}
} }
} }

View File

@ -14,65 +14,75 @@
* limitations under the License. * limitations under the License.
*/ */
use super::{Data, Network, Peer, PeerId}; use super::{Network, Peer, PeerId};
use crate::queue::ExecutionQueue;
use air_test_utils::test_runner::TestRunParameters; use air_test_utils::test_runner::TestRunParameters;
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
collections::{HashMap, HashSet, VecDeque}, collections::{HashMap, HashSet},
hash::Hash, hash::Hash,
ops::Deref, ops::Deref,
rc::{Rc, Weak},
}; };
const EXPECT_VALID_NETWORK: &str = "Using a peer of a destroyed network";
pub(crate) type PeerSet = HashSet<PeerId>; pub(crate) type PeerSet = HashSet<PeerId>;
#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum LinkState { pub enum AlterState {
Reachable, Added,
Unreachable, Removed,
} }
/// Neighbors of particular node, including set of nodes unreachable from this one (but they might be /// Neighbors of particular node, including set of nodes unreachable from this one (but they might be
/// reachable from others). /// reachable from others).
#[derive(Debug, Default)]
pub struct Neighborhood { pub struct Neighborhood {
// the value is true is link from this peer to neighbor is failng // the value is true is link from this peer to neighbor is failng
neighbors: HashMap<PeerId, LinkState>, network: Weak<Network>,
unreachable: HashSet<PeerId>,
altered: HashMap<PeerId, AlterState>,
} }
impl Neighborhood { impl Neighborhood {
pub fn new() -> Self { pub fn new(network: &Rc<Network>) -> Self {
Default::default() Self {
network: Rc::downgrade(network),
unreachable: <_>::default(),
altered: <_>::default(),
}
} }
pub fn set_neighbors(&mut self, neighbors: PeerSet) { pub fn iter(&self) -> impl Iterator<Item = PeerId> {
self.neighbors = neighbors
.into_iter()
.map(|peer_id| (peer_id, LinkState::Reachable))
.collect();
}
pub fn iter(&self) -> impl Iterator<Item = &PeerId> {
self.into_iter() self.into_iter()
} }
pub fn insert(&mut self, other_peer_id: impl Into<PeerId>) { pub fn alter(&mut self, other_peer_id: impl Into<PeerId>, state: AlterState) {
let other_peer_id = other_peer_id.into(); let other_peer_id = other_peer_id.into();
self.neighbors.insert(other_peer_id, LinkState::Reachable);
self.altered.insert(other_peer_id, state);
} }
/// Removes the other_peer_id from neighborhood, also removes unreachable status. pub fn unalter<Id>(&mut self, other_peer_id: &Id)
pub fn remove<Id>(&mut self, other_peer_id: &Id)
where where
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized, Id: Eq + Hash + ?Sized,
{ {
self.neighbors.remove(other_peer_id); self.altered.remove(other_peer_id);
}
pub fn get_alter_state<Id>(&self, other_peer_id: &Id) -> Option<AlterState>
where
PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized,
{
self.altered.get(other_peer_id).copied()
} }
pub fn set_target_unreachable(&mut self, target: impl Into<PeerId>) { pub fn set_target_unreachable(&mut self, target: impl Into<PeerId>) {
*self.neighbors.get_mut(&target.into()).unwrap() = LinkState::Unreachable; self.unreachable.insert(target.into());
} }
pub fn unset_target_unreachable<Id>(&mut self, target: &Id) pub fn unset_target_unreachable<Id>(&mut self, target: &Id)
@ -80,41 +90,58 @@ impl Neighborhood {
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized, Id: Eq + Hash + ?Sized,
{ {
*self.neighbors.get_mut(target).unwrap() = LinkState::Reachable; self.unreachable.remove(target);
} }
pub fn is_reachable(&self, target: impl Deref<Target = PeerId>) -> bool { pub fn is_reachable<Id>(&self, target: &Id) -> bool
let target_peer_id = target.deref(); where
self.neighbors.get(target_peer_id) == Some(&LinkState::Reachable) PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized,
{
let network = self.network.upgrade().expect(EXPECT_VALID_NETWORK);
if network.get_peer_env(target).is_some()
|| self.altered.get(target) == Some(&AlterState::Added)
{
!self.unreachable.contains(target)
} else {
false
}
} }
} }
impl<'a> std::iter::IntoIterator for &'a Neighborhood { impl std::iter::IntoIterator for &Neighborhood {
type Item = &'a PeerId; type Item = PeerId;
type IntoIter = std::collections::hash_map::Keys<'a, PeerId, LinkState>; type IntoIter = std::collections::hash_set::IntoIter<PeerId>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.neighbors.keys() let network = self.network.upgrade().expect(EXPECT_VALID_NETWORK);
let mut peers: HashSet<_> = network
.get_peers()
.filter(|peer| self.altered.get(peer) != Some(&AlterState::Removed))
.collect();
for (peer, &state) in self.altered.iter() {
if state == AlterState::Added {
peers.insert(peer.clone());
}
}
peers.into_iter()
} }
} }
#[derive(Debug)]
pub struct PeerEnv { pub struct PeerEnv {
pub(crate) peer: Peer, pub(crate) peer: Peer,
// failed for everyone // failed for everyone
failed: bool, failed: bool,
neighborhood: Neighborhood, neighborhood: Neighborhood,
pub(crate) data_queue: VecDeque<Data>,
} }
impl PeerEnv { impl PeerEnv {
pub fn new(peer: Peer) -> Self { pub fn new(peer: Peer, network: &Rc<Network>) -> Self {
Self { Self {
peer, peer,
failed: false, failed: false,
neighborhood: Default::default(), neighborhood: Neighborhood::new(network),
data_queue: Default::default(),
} }
} }
@ -136,26 +163,20 @@ impl PeerEnv {
return true; return true;
} }
self.neighborhood.is_reachable(target) self.neighborhood.is_reachable(target_peer_id)
} }
pub fn extend_neighborhood(&mut self, peers: impl Iterator<Item = impl Into<PeerId>>) { pub fn extend_neighborhood(&mut self, peers: impl Iterator<Item = impl Into<PeerId>>) {
let peer_id = self.peer.peer_id.clone(); let peer_id = &self.peer.peer_id;
for other_peer_id in peers for other_peer_id in peers.map(Into::into).filter(|other_id| other_id != peer_id) {
.map(Into::into) self.neighborhood.alter(other_peer_id, AlterState::Added);
.filter(|other_id| other_id != &peer_id)
{
self.neighborhood.insert(other_peer_id);
} }
} }
pub fn remove_from_neighborhood<'a, Id>(&mut self, peers: impl Iterator<Item = &'a Id>) pub fn remove_from_neighborhood(&mut self, peers: impl Iterator<Item = impl Into<PeerId>>) {
where let peer_id = &self.peer.peer_id;
PeerId: std::borrow::Borrow<Id>, for other_peer_id in peers.map(Into::into).filter(|other_id| other_id != peer_id) {
Id: Eq + Hash + ?Sized + 'a, self.neighborhood.alter(other_peer_id, AlterState::Removed);
{
for peer_id in peers {
self.neighborhood.remove(peer_id);
} }
} }
@ -167,27 +188,28 @@ impl PeerEnv {
&mut self.neighborhood &mut self.neighborhood
} }
pub fn iter(&self) -> impl Iterator<Item = &PeerId> { pub fn iter(&self) -> impl Iterator<Item = PeerId> {
self.neighborhood.iter() self.neighborhood.iter()
} }
pub fn send_data(&mut self, data: Data) { pub(crate) fn execute_once(
self.data_queue.push_back(data);
}
pub fn execute_once(
&mut self, &mut self,
air: impl Into<String>, air: impl Into<String>,
network: &Network, network: &Network,
queue: &ExecutionQueue,
test_parameters: &TestRunParameters, test_parameters: &TestRunParameters,
) -> Option<Result<air_test_utils::RawAVMOutcome, String>> { ) -> Option<Result<air_test_utils::RawAVMOutcome, String>> {
let maybe_data = self.data_queue.pop_front(); let queue = queue.clone();
let queue_cell = queue.get_peer_queue_cell(self.peer.peer_id.clone());
let maybe_data = queue_cell.pop_data();
maybe_data.map(|data| { maybe_data.map(|data| {
let res = self.peer.invoke(air, data, test_parameters.clone()); let res = self
.peer
.invoke(air, data, test_parameters.clone(), &queue_cell);
if let Ok(outcome) = &res { if let Ok(outcome) = &res {
network.distribute_to_peers(&outcome.next_peer_pks, &outcome.data) queue.distribute_to_peers(network, &outcome.next_peer_pks, &outcome.data)
} }
res res
@ -212,23 +234,33 @@ mod tests {
#[test] #[test]
fn test_empty_neighborhood() { fn test_empty_neighborhood() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
assert!(pwn.is_reachable(&peer_id)); assert!(penv.is_reachable(&peer_id));
assert!(!pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
} }
#[test] #[test]
fn test_no_self_disconnect() { fn test_no_self_disconnect() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
let nei = pwn.get_neighborhood_mut(); {
nei.insert(peer_id.clone()); let nei = penv.get_neighborhood_mut();
nei.remove(&peer_id);
assert!(pwn.is_reachable(&peer_id)); nei.alter(peer_id.clone(), AlterState::Added);
assert!(!pwn.is_reachable(&other_id)); nei.alter(peer_id.clone(), AlterState::Removed);
}
assert!(penv.is_reachable(&peer_id));
assert!(!penv.is_reachable(&other_id));
let nei = penv.get_neighborhood_mut();
nei.unalter(&peer_id);
assert!(penv.is_reachable(&peer_id));
assert!(!penv.is_reachable(&other_id));
} }
#[test] #[test]
@ -236,160 +268,179 @@ mod tests {
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id1: PeerId = "other1".into(); let other_id1: PeerId = "other1".into();
let other_id2: PeerId = "other2".into(); let other_id2: PeerId = "other2".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let network = Network::empty();
let penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
// iter is empty // iter is empty
assert!(pwn.iter().next().is_none()); assert!(penv.iter().next().is_none());
network.ensure_peer(other_id1.clone());
network.ensure_peer(other_id2.clone());
let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]); let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]);
pwn.get_neighborhood_mut() assert_eq!(penv.iter().collect::<PeerSet>(), expected_neighborhood);
.set_neighbors(expected_neighborhood.clone());
assert_eq!(
pwn.iter().cloned().collect::<PeerSet>(),
expected_neighborhood
);
} }
#[test] #[test]
fn test_insert() { fn test_insert() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id1: PeerId = "other1".into(); let other_id1: PeerId = "other1".into();
let other_id2: PeerId = "other2".into(); let other_id2: PeerId = "other2".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
// iter is empty // iter is empty
assert!(pwn.iter().next().is_none()); assert!(penv.iter().next().is_none());
let nei = pwn.get_neighborhood_mut();
nei.insert(other_id1.clone()); network.ensure_peer(other_id1.clone());
nei.insert(other_id2.clone()); network.ensure_peer(other_id2.clone());
let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]); let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]);
assert_eq!( assert_eq!(PeerSet::from_iter(penv.iter()), expected_neighborhood);
PeerSet::from_iter(pwn.iter().cloned()), }
expected_neighborhood
); #[test]
fn test_ensure() {
let network = Network::empty();
let peer_id: PeerId = "someone".into();
let other_id1: PeerId = "other1".into();
let other_id2: PeerId = "other2".into();
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
// iter is empty
assert!(penv.iter().next().is_none());
let nei = penv.get_neighborhood_mut();
nei.alter(other_id1.clone(), AlterState::Added);
nei.alter(other_id2.clone(), AlterState::Added);
let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]);
assert_eq!(PeerSet::from_iter(penv.iter()), expected_neighborhood);
} }
#[test] #[test]
fn test_insert_insert() { fn test_insert_insert() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id1: PeerId = "other1".into(); let other_id1: PeerId = "other1".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
// iter is empty // iter is empty
assert!(pwn.iter().next().is_none()); assert!(penv.iter().next().is_none());
let nei = penv.get_neighborhood_mut();
nei.alter(other_id1.clone(), AlterState::Added);
nei.alter(other_id1.clone(), AlterState::Added);
let nei = pwn.get_neighborhood_mut();
nei.insert(other_id1.clone());
nei.insert(other_id1.clone());
let expected_neighborhood = vec![other_id1]; let expected_neighborhood = vec![other_id1];
assert_eq!( assert_eq!(penv.iter().collect::<Vec<_>>(), expected_neighborhood);
pwn.iter().cloned().collect::<Vec<_>>(),
expected_neighborhood
);
} }
#[test] #[test]
fn test_extend_neighborhood() { fn test_extend_neighborhood() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
pwn.get_neighborhood_mut().insert("zero"); penv.get_neighborhood_mut()
pwn.extend_neighborhood(IntoIterator::into_iter(["one", "two"])); .alter(PeerId::from("zero"), AlterState::Added);
penv.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
assert_eq!( assert_eq!(
PeerSet::from_iter(pwn.iter().cloned()), PeerSet::from_iter(penv.iter()),
PeerSet::from_iter(IntoIterator::into_iter(["zero", "one", "two"]).map(PeerId::from)), PeerSet::from_iter(IntoIterator::into_iter(["zero", "one", "two"]).map(PeerId::from)),
); );
} }
#[test] #[test]
fn test_remove_from_neiborhood() { fn test_remove_from_neiborhood() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
pwn.get_neighborhood_mut().insert("zero"); penv.get_neighborhood_mut()
pwn.extend_neighborhood(IntoIterator::into_iter(["one", "two"])); .alter(PeerId::from("zero"), AlterState::Added);
pwn.remove_from_neighborhood(IntoIterator::into_iter(["zero", "two"])); penv.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
penv.remove_from_neighborhood(IntoIterator::into_iter(["zero", "two"]));
assert_eq!( assert_eq!(
pwn.iter().cloned().collect::<HashSet<_>>(), penv.iter().collect::<PeerSet>(),
IntoIterator::into_iter(["one"]) maplit::hashset! {
.map(PeerId::from) PeerId::from("one"),
.collect::<HashSet<_>>() },
); );
} }
#[test] #[test]
fn test_fail() { fn test_fail() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
let nei = pwn.get_neighborhood_mut();
nei.insert(other_id.clone()); let nei = penv.get_neighborhood_mut();
nei.alter(other_id.clone(), AlterState::Added);
nei.set_target_unreachable(other_id.clone()); nei.set_target_unreachable(other_id.clone());
let expected_neighborhood = PeerSet::from([other_id.clone()]); let expected_neighborhood = PeerSet::from([other_id.clone()]);
assert_eq!( assert_eq!(PeerSet::from_iter(penv.iter()), expected_neighborhood);
PeerSet::from_iter(pwn.iter().cloned()), assert!(!penv.is_reachable(&other_id));
expected_neighborhood
);
assert!(!pwn.is_reachable(&other_id));
} }
#[test] #[test]
fn test_fail_remove() { fn test_fail_remove() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
let nei = pwn.get_neighborhood_mut(); let nei = penv.get_neighborhood_mut();
nei.insert(other_id.clone()); nei.alter(other_id.clone(), AlterState::Added);
nei.set_target_unreachable(other_id.clone()); nei.set_target_unreachable(other_id.clone());
assert!(!pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
let nei = pwn.get_neighborhood_mut(); let nei = penv.get_neighborhood_mut();
nei.remove(&other_id); nei.unalter(&other_id);
assert!(!pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
let nei = pwn.get_neighborhood_mut(); let nei = penv.get_neighborhood_mut();
nei.insert(other_id.clone()); nei.alter(other_id.clone(), AlterState::Added);
assert!(pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
} }
#[test] #[test]
fn test_fail_unfail() { fn test_fail_unfail() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
let nei = pwn.get_neighborhood_mut(); let nei = penv.get_neighborhood_mut();
nei.insert(other_id.clone()); nei.alter(other_id.clone(), AlterState::Added);
nei.set_target_unreachable(other_id.clone()); nei.set_target_unreachable(other_id.clone());
assert!(!pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
let nei = pwn.get_neighborhood_mut(); let nei = penv.get_neighborhood_mut();
nei.unset_target_unreachable(&other_id); nei.unset_target_unreachable(&other_id);
assert!(pwn.is_reachable(&other_id)); assert!(penv.is_reachable(&other_id));
} }
#[test] #[test]
fn test_failed() { fn test_failed() {
let network = Network::empty();
let peer_id: PeerId = "someone".into(); let peer_id: PeerId = "someone".into();
let other_id: PeerId = "other".into(); let other_id: PeerId = "other".into();
let remote_id: PeerId = "remote".into(); let remote_id: PeerId = "remote".into();
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![]))); let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
pwn.get_neighborhood_mut().insert(other_id.clone()); penv.get_neighborhood_mut()
.alter(other_id.clone(), AlterState::Added);
assert!(pwn.is_reachable(&peer_id)); assert!(penv.is_reachable(&peer_id));
assert!(pwn.is_reachable(&other_id)); assert!(penv.is_reachable(&other_id));
assert!(!pwn.is_reachable(&remote_id)); assert!(!penv.is_reachable(&remote_id));
pwn.set_failed(true); penv.set_failed(true);
assert!(!pwn.is_reachable(&peer_id)); assert!(!penv.is_reachable(&peer_id));
assert!(!pwn.is_reachable(&other_id)); assert!(!penv.is_reachable(&other_id));
assert!(!pwn.is_reachable(&remote_id)); assert!(!penv.is_reachable(&remote_id));
pwn.set_failed(false); penv.set_failed(false);
assert!(pwn.is_reachable(&peer_id)); assert!(penv.is_reachable(&peer_id));
assert!(pwn.is_reachable(&other_id)); assert!(penv.is_reachable(&other_id));
assert!(!pwn.is_reachable(&remote_id)); assert!(!penv.is_reachable(&remote_id));
} }
} }

View File

@ -15,23 +15,44 @@
*/ */
use crate::{ use crate::{
asserts::ServiceDefinition, ephemeral::{Network, PeerId},
ephemeral::{Network, Peer, PeerId}, queue::ExecutionQueue,
services::{results::ResultService, MarineService, MarineServiceHandle}, services::MarineServiceHandle,
transform::{walker::Transformer, Sexp}, transform::walker::TransformedAirScript,
}; };
use air_test_utils::{test_runner::TestRunParameters, RawAVMOutcome}; use air_test_utils::{test_runner::TestRunParameters, RawAVMOutcome};
use std::{borrow::Borrow, collections::HashMap, hash::Hash, rc::Rc, str::FromStr}; use std::{borrow::Borrow, hash::Hash, rc::Rc};
pub struct TestExecutor { /// A executor for an AIR script. Several executors may share same TransformedAirScript
pub air_script: String, /// and its state.
pub network: Network, pub struct AirScriptExecutor {
pub test_parameters: TestRunParameters, transformed_air_script: TransformedAirScript,
test_parameters: TestRunParameters,
queue: ExecutionQueue,
} }
impl TestExecutor { impl AirScriptExecutor {
pub fn from_transformed_air_script(
test_parameters: TestRunParameters,
transformed_air_script: TransformedAirScript,
) -> Result<Self, String> {
let network = transformed_air_script.get_network();
let init_peer_id = test_parameters.init_peer_id.as_str();
network.ensure_peer(init_peer_id);
let queue = ExecutionQueue::new();
// Seed execution
queue.distribute_to_peers(&network, &[init_peer_id], &<_>::default());
Ok(Self {
transformed_air_script,
test_parameters,
queue,
})
}
/// Create execution from the annotated air script. /// Create execution from the annotated air script.
/// ///
/// `extra_peers` allows you to define peers that are not mentioned in the annotated script /// `extra_peers` allows you to define peers that are not mentioned in the annotated script
@ -42,33 +63,10 @@ impl TestExecutor {
extra_peers: impl IntoIterator<Item = PeerId>, extra_peers: impl IntoIterator<Item = PeerId>,
annotated_air_script: &str, annotated_air_script: &str,
) -> Result<Self, String> { ) -> Result<Self, String> {
// validate the AIR script with the standard parser first let network = Network::new(extra_peers.into_iter(), common_services);
air_parser::parse(annotated_air_script)?; let transformed = TransformedAirScript::new(annotated_air_script, network)?;
let mut sexp = Sexp::from_str(annotated_air_script)?; Self::from_transformed_air_script(test_parameters, transformed)
let mut walker = Transformer::new();
walker.transform(&mut sexp);
let init_peer_id = test_parameters.init_peer_id.clone();
let transformed_air_script = sexp.to_string();
let peers = build_peers(
common_services,
walker.results,
walker.peers,
PeerId::new(init_peer_id.clone()),
extra_peers,
)?;
let network = Network::from_peers(peers);
// Seed execution
network.distribute_to_peers(&[init_peer_id], &vec![]);
Ok(TestExecutor {
air_script: transformed_air_script,
network,
test_parameters,
})
} }
/// Simple constructor where everything is generated from the annotated_air_script. /// Simple constructor where everything is generated from the annotated_air_script.
@ -78,12 +76,22 @@ impl TestExecutor {
) -> Result<Self, String> { ) -> Result<Self, String> {
Self::new( Self::new(
test_parameters, test_parameters,
<_>::default(), vec![],
std::iter::empty(), std::iter::empty(),
annotated_air_script, annotated_air_script,
) )
} }
pub fn from_network(
test_parameters: TestRunParameters,
network: Rc<Network>,
annotated_air_script: &str,
) -> Result<Self, String> {
let transformed = TransformedAirScript::new(annotated_air_script, network)?;
Self::from_transformed_air_script(test_parameters, transformed)
}
/// Return Iterator for handling all the queued datas /// Return Iterator for handling all the queued datas
/// for particular peer_id. /// for particular peer_id.
pub fn execution_iter<'s, Id>( pub fn execution_iter<'s, Id>(
@ -92,11 +100,14 @@ impl TestExecutor {
) -> Option<impl Iterator<Item = RawAVMOutcome> + 's> ) -> Option<impl Iterator<Item = RawAVMOutcome> + 's>
where where
PeerId: Borrow<Id>, PeerId: Borrow<Id>,
// TODO it's not clear why compiler requies + 's here, but not at Network::iter_execution Id: Eq + Hash + ?Sized,
Id: Eq + Hash + ?Sized + 's,
{ {
self.network self.queue.execution_iter(
.execution_iter(&self.air_script, &self.test_parameters, peer_id) &self.transformed_air_script,
self.transformed_air_script.get_network(),
&self.test_parameters,
peer_id,
)
} }
/// Process all queued datas, panicing on error. /// Process all queued datas, panicing on error.
@ -115,49 +126,23 @@ impl TestExecutor {
Id: Eq + Hash + ?Sized, Id: Eq + Hash + ?Sized,
{ {
self.execution_iter(peer_id) self.execution_iter(peer_id)
.map(|mut it| it.next().unwrap()) .map(|mut it| it.next().expect("Nothing to execute"))
} }
} }
fn build_peers(
common_services: Vec<MarineServiceHandle>,
results: HashMap<u32, ServiceDefinition>,
known_peers: std::collections::HashSet<PeerId>,
init_peer_id: PeerId,
extra_peers: impl IntoIterator<Item = PeerId>,
) -> Result<Vec<Peer>, String> {
let mut result_services: Vec<MarineServiceHandle> =
Vec::with_capacity(1 + common_services.len());
result_services.push(ResultService::new(results)?.to_handle());
result_services.extend(common_services);
let result_services = Rc::<[_]>::from(result_services);
let extra_peers_pairs = extra_peers
.into_iter()
.chain(std::iter::once(init_peer_id))
.map(|peer_id| (peer_id.clone(), Peer::new(peer_id, result_services.clone())));
let mut peers = extra_peers_pairs.collect::<HashMap<_, _>>();
let known_peers_pairs = known_peers
.into_iter()
.map(|peer_id| (peer_id.clone(), Peer::new(peer_id, result_services.clone())));
peers.extend(known_peers_pairs);
Ok(peers.into_values().collect())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::services::MarineService;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::ops::Deref; use std::cell::RefCell;
#[test] #[test]
fn test_execution() { fn test_execution() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
std::iter::empty(), std::iter::empty(),
@ -188,7 +173,7 @@ mod tests {
#[test] #[test]
fn test_call_result_success() { fn test_call_result_success() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
std::iter::empty(), std::iter::empty(),
@ -217,7 +202,7 @@ mod tests {
#[test] #[test]
fn test_call_result_error() { fn test_call_result_error() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
std::iter::empty(), std::iter::empty(),
@ -255,7 +240,7 @@ mod tests {
#[test] #[test]
fn test_seq_ok() { fn test_seq_ok() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
@ -330,7 +315,7 @@ mod tests {
#[test] #[test]
fn test_map() { fn test_map() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("peer1"), TestRunParameters::from_init_peer_id("peer1"),
vec![], vec![],
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
@ -373,7 +358,7 @@ mod tests {
let trace = trace_from_result(outcome3); let trace = trace_from_result(outcome3);
assert_eq!( assert_eq!(
trace.deref(), &*trace,
vec![ vec![
executed_state::scalar(json!(["peer2", "peer3"])), executed_state::scalar(json!(["peer2", "peer3"])),
executed_state::scalar(json!(42)), executed_state::scalar(json!(42)),
@ -386,7 +371,7 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn test_map_no_arg() { fn test_map_no_arg() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("peer1"), TestRunParameters::from_init_peer_id("peer1"),
vec![], vec![],
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
@ -400,7 +385,7 @@ mod tests {
#[test] #[test]
fn test_seq_error() { fn test_seq_error() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
@ -476,7 +461,7 @@ mod tests {
#[test] #[test]
fn test_echo() { fn test_echo() {
let exec = TestExecutor::new( let exec = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
std::iter::empty(), std::iter::empty(),
@ -508,9 +493,98 @@ mod tests {
) )
} }
#[test]
fn test_transformed_distinct() {
let peer = "peer1";
let network = Network::empty();
let transformed1 = TransformedAirScript::new(
&f!(r#"(call "{}" ("service" "function") []) ; ok = 42"#, peer),
network.clone(),
)
.unwrap();
let exectution1 = AirScriptExecutor::from_transformed_air_script(
TestRunParameters::from_init_peer_id(peer),
transformed1,
)
.unwrap();
let transformed2 = TransformedAirScript::new(
&f!(r#"(call "{}" ("service" "function") []) ; ok = 24"#, peer),
network.clone(),
)
.unwrap();
let exectution2 = AirScriptExecutor::from_transformed_air_script(
TestRunParameters::from_init_peer_id(peer),
transformed2,
)
.unwrap();
let trace1 = exectution1.execute_one(peer).unwrap();
let trace2 = exectution2.execute_one(peer).unwrap();
assert_eq!(
trace_from_result(&trace1),
ExecutionTrace::from(vec![scalar_number(42)]),
);
assert_eq!(
trace_from_result(&trace2),
ExecutionTrace::from(vec![scalar_number(24)]),
);
}
#[test]
fn test_transformed_shared() {
struct Service {
state: RefCell<std::vec::IntoIter<JValue>>,
}
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(),
)
}
}
let service = Service {
state: vec![json!(42), json!(24)].into_iter().into(),
};
let network = Network::new(std::iter::empty::<PeerId>(), vec![service.to_handle()]);
let peer = "peer1";
let air_script = f!(r#"(call "{}" ("service" "function") [])"#, peer);
let transformed1 = TransformedAirScript::new(&air_script, network.clone()).unwrap();
let exectution1 = AirScriptExecutor::from_transformed_air_script(
TestRunParameters::from_init_peer_id(peer),
transformed1,
)
.unwrap();
let transformed2 = TransformedAirScript::new(&air_script, network.clone()).unwrap();
let exectution2 = AirScriptExecutor::from_transformed_air_script(
TestRunParameters::from_init_peer_id(peer),
transformed2,
)
.unwrap();
let trace1 = exectution1.execute_one(peer).unwrap();
let trace2 = exectution2.execute_one(peer).unwrap();
assert_eq!(
trace_from_result(&trace1),
ExecutionTrace::from(vec![scalar_number(42)]),
);
assert_eq!(
trace_from_result(&trace2),
ExecutionTrace::from(vec![scalar_number(24)]),
);
}
#[test] #[test]
fn test_invalid_air() { fn test_invalid_air() {
let res = TestExecutor::new( let res = AirScriptExecutor::new(
TestRunParameters::from_init_peer_id("init_peer_id"), TestRunParameters::from_init_peer_id("init_peer_id"),
vec![], vec![],
std::iter::empty(), std::iter::empty(),
@ -520,13 +594,16 @@ mod tests {
"#, "#,
); );
assert!(res.is_err()); match &res {
// TestExecutor doesn't implement Debug, so we have to unpack the error this way: Ok(_) => {
if let Err(err) = res { assert!(res.is_err());
assert_eq!( }
err, Err(err) => {
"error: \n ┌─ script.air:3:1\n\n3 │ )\n │ ^ expected \"(\"\n\n" assert_eq!(
); err,
"error: \n ┌─ script.air:3:1\n\n3 │ )\n │ ^ expected \"(\"\n\n"
);
}
} }
} }
} }

View File

@ -17,7 +17,8 @@
pub mod asserts; pub mod asserts;
pub mod ephemeral; pub mod ephemeral;
pub mod execution; pub mod execution;
mod queue;
pub mod services; pub mod services;
pub mod transform; pub mod transform;
pub use execution::TestExecutor; pub use execution::AirScriptExecutor;

View File

@ -0,0 +1,116 @@
/*
* 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 crate::ephemeral::{Data, Network, PeerId};
use air_test_utils::{test_runner::TestRunParameters, RawAVMOutcome};
use std::{
borrow::Borrow,
cell::RefCell,
collections::{HashMap, VecDeque},
hash::Hash,
ops::Deref,
rc::Rc,
};
#[derive(Debug, Default)]
pub(crate) struct PeerQueueCell {
queue: RefCell<VecDeque<Data>>,
data: RefCell<Data>,
}
impl PeerQueueCell {
pub(crate) fn pop_data(&self) -> Option<Data> {
let mut cell_ref = self.queue.borrow_mut();
cell_ref.pop_front()
}
pub(crate) fn push_data(&self, data: Data) {
let mut cell_ref = self.queue.borrow_mut();
cell_ref.push_back(data);
}
pub(crate) fn take_prev_data(&self) -> Data {
let cell_ref = self.data.borrow_mut();
(*cell_ref).clone()
}
pub(crate) fn set_prev_data(&self, data: Data) {
let mut cell_ref = self.data.borrow_mut();
*cell_ref = data;
}
}
/// Per-particle message queue.
#[derive(Debug, Clone, Default)]
// TODO make it pub(crate) and see what is broken
pub(crate) struct ExecutionQueue {
queues: Rc<RefCell<HashMap<PeerId, Rc<PeerQueueCell>>>>,
}
impl ExecutionQueue {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) fn get_peer_queue_cell(&self, peer_id: PeerId) -> Rc<PeerQueueCell> {
let mut queues_ref = RefCell::borrow_mut(&self.queues);
queues_ref.entry(peer_id).or_default().clone()
}
/// Iterator for handling al the queued data. It borrows peer env's `RefCell` only temporarily.
/// Following test-utils' call_vm macro, it panics on failed VM.
pub fn execution_iter<'ctx, Id>(
&'ctx self,
air: &'ctx str,
network: Rc<Network>,
test_parameters: &'ctx TestRunParameters,
peer_id: &Id,
) -> Option<impl Iterator<Item = RawAVMOutcome> + 'ctx>
where
PeerId: Borrow<Id>,
Id: Eq + Hash + ?Sized,
{
let peer_env = network.get_peer_env(peer_id);
peer_env.map(|peer_env_cell| {
std::iter::from_fn(move || {
let mut peer_env = peer_env_cell.borrow_mut();
peer_env
.execute_once(air, &network, self, test_parameters)
.map(|r| r.unwrap_or_else(|err| panic!("VM call failed: {}", err)))
})
})
}
pub fn distribute_to_peers<Id>(&self, network: &Network, peers: &[Id], data: &Data)
where
Id: Deref<Target = str>,
{
for peer_id in peers {
let peer_id: &str = peer_id;
match network.get_peer_env::<str>(peer_id) {
Some(peer_env_cell) => {
let peer_env_ref = RefCell::borrow(&peer_env_cell);
self.get_peer_queue_cell(peer_env_ref.peer.peer_id.clone())
.push_data(data.clone());
}
None => panic!("Unknown peer"),
}
}
}
}

View File

@ -16,6 +16,8 @@
pub(crate) mod results; pub(crate) mod results;
use self::results::{MarineServiceWrapper, ResultStore};
use air_test_utils::{CallRequestParams, CallServiceClosure, CallServiceResult}; use air_test_utils::{CallRequestParams, CallServiceClosure, CallServiceResult};
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
@ -68,3 +70,31 @@ pub(crate) fn services_to_call_service_closure(
panic!("No function found for params {:?}", params) panic!("No function found for params {:?}", params)
}) })
} }
pub(crate) struct NetworkServices {
result_store: Rc<ResultStore>,
common_services: Rc<[MarineServiceHandle]>,
}
impl NetworkServices {
pub(crate) fn new(mut common_services: Vec<MarineServiceHandle>) -> Self {
let result_service = Rc::<ResultStore>::default();
// insert result service into all services:
let wrapper = MarineServiceWrapper::new(result_service.clone()).to_handle();
common_services.insert(0, wrapper);
Self {
result_store: result_service,
common_services: common_services.into(),
}
}
pub(crate) fn get_result_store(&self) -> Rc<ResultStore> {
self.result_store.clone()
}
pub(crate) fn get_services(&self) -> Rc<[MarineServiceHandle]> {
self.common_services.clone()
}
}

View File

@ -17,126 +17,32 @@
use super::{FunctionOutcome, MarineService}; use super::{FunctionOutcome, MarineService};
use crate::asserts::ServiceDefinition; use crate::asserts::ServiceDefinition;
use air_test_utils::{ use air_test_utils::CallRequestParams;
prelude::{echo_call_service, unit_call_service},
CallRequestParams, CallServiceClosure, CallServiceResult,
};
use serde_json::json;
use std::{borrow::Cow, cell::Cell, collections::HashMap, convert::TryInto, time::Duration}; use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration};
pub struct ResultService { #[derive(Clone, Debug, Default, PartialEq, Eq)]
results: HashMap<u32, CallServiceClosure>, pub(crate) struct ResultStore {
pub(crate) results: RefCell<HashMap<usize, ServiceDefinition>>,
} }
impl TryInto<CallServiceClosure> for ServiceDefinition { impl ResultStore {
type Error = String; pub(crate) fn insert(&self, service_definition: ServiceDefinition) -> Result<usize, String> {
let mut results = self.results.borrow_mut();
fn try_into(self) -> Result<CallServiceClosure, String> { let id = results.len();
match self { results.insert(id, service_definition);
ServiceDefinition::Ok(jvalue) => { Ok(id)
Ok(Box::new(move |_| CallServiceResult::ok(jvalue.clone())))
}
ServiceDefinition::Error(call_result) => Ok(Box::new(move |_| call_result.clone())),
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)),
}
} }
} }
fn named_service_closure(name: String) -> Result<CallServiceClosure, String> { impl MarineService for ResultStore {
match name.as_str() {
"echo" => Ok(echo_call_service()),
"unit" => Ok(unit_call_service()),
_ => Err(format!("unknown service name: {:?}", name)),
}
}
fn seq_ok_closure(call_map: HashMap<String, serde_json::Value>) -> 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);
CallServiceResult::ok(
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 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 {
results: results
.into_iter()
.map(|(id, service_def)| {
service_def
.try_into()
.map(move |s: CallServiceClosure| (id, s))
})
.collect::<Result<_, String>>()?,
})
}
}
impl MarineService for ResultService {
fn call(&self, params: CallRequestParams) -> FunctionOutcome { fn call(&self, params: CallRequestParams) -> FunctionOutcome {
let results = self.results.borrow();
if let Some((_, suffix)) = params.service_id.split_once("..") { if let Some((_, suffix)) = params.service_id.split_once("..") {
if let Ok(key) = suffix.parse() { if let Ok(key) = suffix.parse() {
let service_desc = self.results.get(&key).expect("Unknown result id"); let service_desc = results.get(&key).expect("Unknown result id");
FunctionOutcome::ServiceResult(service_desc(params), Duration::ZERO) let service_result = service_desc.call(params);
FunctionOutcome::ServiceResult(service_result, Duration::ZERO)
} else { } else {
// Pass malformed service names further in a chain // Pass malformed service names further in a chain
FunctionOutcome::NotDefined FunctionOutcome::NotDefined
@ -146,3 +52,19 @@ impl MarineService for ResultService {
} }
} }
} }
pub(crate) struct MarineServiceWrapper<T> {
wrapped: Rc<T>,
}
impl<T> MarineServiceWrapper<T> {
pub(crate) fn new(wrapped: Rc<T>) -> Self {
Self { wrapped }
}
}
impl<T: MarineService> MarineService for MarineServiceWrapper<T> {
fn call(&self, params: CallRequestParams) -> FunctionOutcome {
self.wrapped.call(params)
}
}

View File

@ -15,24 +15,60 @@
*/ */
use super::{Call, Sexp}; use super::{Call, Sexp};
use crate::{asserts::ServiceDefinition, ephemeral::PeerId}; use crate::ephemeral::Network;
use std::collections::{HashMap, HashSet}; use std::{fmt::Write, ops::Deref, rc::Rc, str::FromStr};
use std::fmt::Write;
#[derive(Debug, Default)] /// Transformed script represents transformed script's services' state within the network.
pub(crate) struct Transformer { /// Executions that use the same transformed script share same generated services' state.
cnt: u32, /// This struct is cheap to clone, and cloned copies share same internal state.
pub(crate) results: HashMap<u32, ServiceDefinition>, #[derive(Clone)]
pub(crate) peers: HashSet<PeerId>, pub struct TransformedAirScript {
network: Rc<Network>,
tranformed: Rc<str>,
} }
impl Transformer { impl TransformedAirScript {
pub(crate) fn new() -> Self { pub fn new(annotated_air_script: &str, network: Rc<Network>) -> Result<Self, String> {
Default::default() // validate the AIR script with the standard parser first
air_parser::parse(annotated_air_script)?;
Self::new_unvalidated(annotated_air_script, network)
} }
pub(crate) fn transform(&mut self, sexp: &mut Sexp) { pub(crate) fn new_unvalidated(
annotated_air_script: &str,
network: Rc<Network>,
) -> Result<Self, String> {
let transformer = Transformer { network: &network };
let mut sexp = Sexp::from_str(annotated_air_script)?;
transformer.transform(&mut sexp);
Ok(Self {
network,
tranformed: Rc::from(sexp.to_string().as_str()),
})
}
pub(crate) fn get_network(&self) -> Rc<Network> {
self.network.clone()
}
}
impl Deref for TransformedAirScript {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.tranformed
}
}
struct Transformer<'net> {
network: &'net Rc<Network>,
}
impl Transformer<'_> {
pub(crate) fn transform(&self, sexp: &mut Sexp) {
match sexp { match sexp {
Sexp::Call(call) => self.handle_call(call), Sexp::Call(call) => self.handle_call(call),
Sexp::List(children) => { Sexp::List(children) => {
@ -44,18 +80,17 @@ impl Transformer {
} }
} }
fn handle_call(&mut self, call: &mut Call) { fn handle_call(&self, call: &mut Call) {
// collect peers... // collect peers...
if let Sexp::String(peer_id) = &call.triplet.0 { if let Sexp::String(peer_id) = &call.triplet.0 {
self.peers.insert(peer_id.clone().into()); self.network.ensure_peer(peer_id.clone());
} }
let result_store = self.network.get_services().get_result_store();
if let Some(service) = &call.service_desc { if let Some(service) = &call.service_desc {
// install a value // install a value
let call_id = self.cnt; let call_id = result_store.insert(service.clone()).unwrap();
self.cnt += 1;
self.results.insert(call_id, service.clone());
match &mut call.triplet.1 { match &mut call.triplet.1 {
Sexp::String(ref mut value) => { Sexp::String(ref mut value) => {
@ -70,56 +105,64 @@ impl Transformer {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::{iter::FromIterator, str::FromStr}; use crate::{asserts::ServiceDefinition, ephemeral::PeerId, services::results::ResultStore};
use std::{
collections::{HashMap, HashSet},
iter::FromIterator,
};
impl ResultStore {
pub fn into_inner(self) -> HashMap<usize, ServiceDefinition> {
self.results.into_inner()
}
}
#[test] #[test]
fn test_translate_null() { fn test_translate_null() {
let mut tree = Sexp::from_str("(null)").unwrap(); let network = Network::empty();
let mut transformer = Transformer::new(); let transformed = TransformedAirScript::new("(null)", network).unwrap();
transformer.transform(&mut tree); assert_eq!(&*transformed, "(null)");
assert_eq!(tree.to_string(), "(null)");
} }
#[test] #[test]
fn test_translate_call_no_result() { fn test_translate_call_no_result() {
let network = Network::empty();
let script = r#"(call peer_id ("service_id" func) [])"#; let script = r#"(call peer_id ("service_id" func) [])"#;
let mut tree = Sexp::from_str(script).unwrap(); let transformed = TransformedAirScript::new_unvalidated(script, network).unwrap();
let mut transformer = Transformer::new(); assert_eq!(&*transformed, script);
transformer.transform(&mut tree);
assert_eq!(tree.to_string(), script);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_translate_call_no_string() { fn test_translate_call_no_string() {
// TODO rewrite to Result instead of panic? let network = Network::empty();
let script = r#"(call "peer_id" (service_id func) [])"#; let script = r#"(call "peer_id" (service_id func) [])"#;
let mut tree = Sexp::from_str(script).unwrap(); let transformed = TransformedAirScript::new(script, network);
let mut transformer = Transformer::new(); assert_eq!(transformed.as_deref(), Ok(script));
transformer.transform(&mut tree);
assert_eq!(tree.to_string(), script);
} }
#[test] #[test]
fn test_translate_call_result() { fn test_translate_call_result() {
let network = Network::empty();
let script = r#"(call "peer_id" ("service_id" func) []) ; ok = 42"#; let script = r#"(call "peer_id" ("service_id" func) []) ; ok = 42"#;
let mut tree = Sexp::from_str(script).unwrap(); let transformer = TransformedAirScript::new_unvalidated(script, network.clone()).unwrap();
let mut transformer = Transformer::new();
transformer.transform(&mut tree);
assert_eq!( assert_eq!(
tree.to_string(), &*transformer,
r#"(call "peer_id" ("service_id..0" func) [])"# r#"(call "peer_id" ("service_id..0" func) [])"#
); );
assert_eq!( assert_eq!(
transformer.results, Rc::deref(&network.get_services().get_result_store())
.clone()
.into_inner(),
maplit::hashmap! { maplit::hashmap! {
0u32 => ServiceDefinition::Ok(serde_json::json!(42)), 0usize => ServiceDefinition::Ok(serde_json::json!(42)),
} }
); );
assert_eq!( assert_eq!(
transformer.peers.into_iter().collect::<Vec<_>>(), network.get_peers().collect::<Vec<_>>(),
vec![PeerId::new("peer_id")], vec![PeerId::new("peer_id")],
); );
} }
@ -133,11 +176,10 @@ mod tests {
(call peer_id ("service_id" func) [1]) ; ok=true (call peer_id ("service_id" func) [1]) ; ok=true
))"#; ))"#;
let mut tree = Sexp::from_str(script).unwrap(); let network = Network::empty();
let mut transformer = Transformer::new(); let transformed = TransformedAirScript::new_unvalidated(script, network.clone()).unwrap();
transformer.transform(&mut tree);
assert_eq!( assert_eq!(
tree.to_string(), &*transformed,
concat!( concat!(
"(seq ", "(seq ",
r#"(call peer_id ("service_id..0" func) [a 11])"#, r#"(call peer_id ("service_id..0" func) [a 11])"#,
@ -150,14 +192,16 @@ mod tests {
); );
assert_eq!( assert_eq!(
transformer.results, (*network.get_services().get_result_store())
.clone()
.into_inner(),
maplit::hashmap! { maplit::hashmap! {
0u32 => ServiceDefinition::Ok(serde_json::json!({"test":"me"})), 0usize => ServiceDefinition::Ok(serde_json::json!({"test":"me"})),
1 => ServiceDefinition::Ok(serde_json::json!(true)), 1 => ServiceDefinition::Ok(serde_json::json!(true)),
} }
); );
assert!(transformer.peers.is_empty()); assert!(network.get_peers().collect::<Vec<_>>().is_empty());
} }
#[test] #[test]
@ -171,12 +215,11 @@ mod tests {
(call peer_id3 ("service_id" func) [b]) (call peer_id3 ("service_id" func) [b])
))"#; ))"#;
let mut tree = Sexp::from_str(script).unwrap(); let network = Network::empty();
let mut transformer = Transformer::new(); let _ = TransformedAirScript::new_unvalidated(script, network.clone());
transformer.transform(&mut tree);
assert_eq!( assert_eq!(
transformer.peers, network.get_peers().collect::<HashSet<_>>(),
HashSet::from_iter(vec![PeerId::new("peer_id1"), PeerId::new("peer_id2")]), HashSet::from_iter(vec![PeerId::new("peer_id1"), PeerId::new("peer_id2")]),
) )
} }