mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 23:20:18 +00:00
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:
parent
7ac0ab109c
commit
4e86da7eda
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
use air::CatchableError;
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
@ -30,7 +30,7 @@ fn length_functor_for_array_scalar() {
|
||||
"#;
|
||||
|
||||
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");
|
||||
|
||||
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 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");
|
||||
|
||||
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 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");
|
||||
|
||||
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 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");
|
||||
|
||||
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 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");
|
||||
|
||||
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 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");
|
||||
|
||||
let result = executor.execute_one(init_peer_id).unwrap();
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -82,7 +82,7 @@ fn merging_fold_iterations_extensively() {
|
||||
)
|
||||
"#;
|
||||
|
||||
let engine = TestExecutor::new(
|
||||
let engine = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("client"),
|
||||
vec![],
|
||||
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"),
|
||||
vec![],
|
||||
vec!["relay", "p1", "p2", "p3"].into_iter().map(Into::into),
|
||||
|
@ -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");
|
||||
|
||||
let client_result_1 = engine.execute_one(client_id).unwrap();
|
||||
|
@ -59,7 +59,8 @@ fn issue_211() {
|
||||
|
||||
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();
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -66,7 +66,7 @@ fn issue_221() {
|
||||
)
|
||||
"#);
|
||||
|
||||
let executor = TestExecutor::new(
|
||||
let executor = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("set_variable_id"),
|
||||
vec![],
|
||||
vec![peer_1_id, peer_2_id].into_iter().map(Into::into),
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -33,7 +33,7 @@ fn issue_304() {
|
||||
"#;
|
||||
|
||||
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");
|
||||
|
||||
let res = executor.execute_one(init_peer_id).unwrap();
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_test_framework::TestExecutor;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
#[test]
|
||||
@ -48,7 +48,7 @@ fn issue_356() {
|
||||
)
|
||||
"#;
|
||||
|
||||
let engine = TestExecutor::new(
|
||||
let engine = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("client"),
|
||||
vec![],
|
||||
vec!["p1", "p2", "p3"].into_iter().map(Into::into),
|
||||
|
@ -18,10 +18,14 @@ pub(crate) mod parser;
|
||||
|
||||
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 std::collections::HashMap;
|
||||
use std::{borrow::Cow, cell::Cell, collections::HashMap};
|
||||
|
||||
/// Service definition in the testing framework comment DSL.
|
||||
#[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
|
||||
/// call number string starting from "0", or "default".
|
||||
#[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"))]
|
||||
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).
|
||||
#[strum_discriminants(strum(serialize = "behaviour"))]
|
||||
Behaviour(String),
|
||||
@ -47,3 +57,120 @@ pub enum ServiceDefinition {
|
||||
#[strum_discriminants(strum(serialize = "map"))]
|
||||
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()))
|
||||
}
|
||||
|
@ -66,20 +66,20 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> {
|
||||
let value = value.trim();
|
||||
match ServiceTagName::from_str(tag) {
|
||||
Ok(ServiceTagName::Ok) => {
|
||||
serde_json::from_str::<JValue>(value).map(ServiceDefinition::Ok)
|
||||
serde_json::from_str::<JValue>(value).map(ServiceDefinition::ok)
|
||||
}
|
||||
Ok(ServiceTagName::Error) => {
|
||||
serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::Error)
|
||||
serde_json::from_str::<CallServiceResult>(value).map(ServiceDefinition::error)
|
||||
}
|
||||
Ok(ServiceTagName::SeqOk) => {
|
||||
serde_json::from_str(value).map(ServiceDefinition::SeqOk)
|
||||
serde_json::from_str(value).map(ServiceDefinition::seq_ok)
|
||||
}
|
||||
Ok(ServiceTagName::SeqError) => {
|
||||
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::Map) => serde_json::from_str(value).map(ServiceDefinition::Map),
|
||||
Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::behaviour(value)),
|
||||
Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::map),
|
||||
Err(_) => unreachable!("unknown tag {:?}", tag),
|
||||
}
|
||||
},
|
||||
@ -153,7 +153,7 @@ mod tests {
|
||||
let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": []}"#);
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(ServiceDefinition::SeqOk(maplit::hashmap! {
|
||||
Ok(ServiceDefinition::seq_ok(maplit::hashmap! {
|
||||
"default".to_owned() => json!(42),
|
||||
"1".to_owned() => json!(true),
|
||||
"3".to_owned() => json!([]),
|
||||
@ -183,7 +183,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
Ok(ServiceDefinition::SeqError(maplit::hashmap! {
|
||||
Ok(ServiceDefinition::seq_error(maplit::hashmap! {
|
||||
"default".to_owned() => CallServiceResult::ok(json!(42)),
|
||||
"1".to_owned() => CallServiceResult::ok(json!(true)),
|
||||
"3".to_owned() => CallServiceResult::err(1, json!("error")),
|
||||
|
@ -17,38 +17,35 @@
|
||||
pub mod neighborhood;
|
||||
|
||||
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::{
|
||||
test_runner::{create_avm, TestRunParameters, TestRunner},
|
||||
RawAVMOutcome,
|
||||
};
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
hash::Hash,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{borrow::Borrow, cell::RefCell, collections::HashMap, hash::Hash, rc::Rc};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PeerId(String);
|
||||
pub struct PeerId(Rc<str>);
|
||||
|
||||
impl PeerId {
|
||||
pub fn new(peer_id: impl Into<String>) -> Self {
|
||||
Self(peer_id.into())
|
||||
pub fn new<'any>(peer_id: impl Into<&'any str>) -> Self {
|
||||
Self(peer_id.into().into())
|
||||
}
|
||||
}
|
||||
impl From<String> for PeerId {
|
||||
fn from(source: String) -> Self {
|
||||
Self(source)
|
||||
Self(source.as_str().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PeerId {
|
||||
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 struct Peer {
|
||||
peer_id: PeerId,
|
||||
// We presume that only one particle is run over the network.
|
||||
prev_data: Data,
|
||||
pub(crate) peer_id: PeerId,
|
||||
runner: TestRunner,
|
||||
}
|
||||
|
||||
@ -71,26 +66,22 @@ impl Peer {
|
||||
pub fn new(peer_id: impl Into<PeerId>, services: Rc<[MarineServiceHandle]>) -> Self {
|
||||
let peer_id = Into::into(peer_id);
|
||||
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 {
|
||||
peer_id,
|
||||
prev_data: vec![],
|
||||
runner,
|
||||
}
|
||||
Self { peer_id, runner }
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
pub(crate) fn invoke(
|
||||
&mut self,
|
||||
air: impl Into<String>,
|
||||
data: Data,
|
||||
test_run_params: TestRunParameters,
|
||||
queue_cell: &PeerQueueCell,
|
||||
) -> Result<RawAVMOutcome, String> {
|
||||
let mut prev_data = vec![];
|
||||
std::mem::swap(&mut prev_data, &mut self.prev_data);
|
||||
let prev_data = queue_cell.take_prev_data();
|
||||
let res = self.runner.call(air, prev_data, data, test_run_params);
|
||||
if let Ok(outcome) = &res {
|
||||
self.prev_data = outcome.data.clone();
|
||||
queue_cell.set_prev_data(outcome.data.clone());
|
||||
}
|
||||
res
|
||||
}
|
||||
@ -100,32 +91,37 @@ impl std::fmt::Debug for Peer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Peer")
|
||||
.field("peer_id", &self.peer_id)
|
||||
.field("prev_data", &self.prev_data)
|
||||
.field("services", &"...")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Network {
|
||||
peers: HashMap<PeerId, Rc<RefCell<PeerEnv>>>,
|
||||
default_neighborhood: HashSet<PeerId>,
|
||||
peers: RefCell<HashMap<PeerId, Rc<RefCell<PeerEnv>>>>,
|
||||
services: Rc<NetworkServices>,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn empty() -> Self {
|
||||
Self::new(std::iter::empty::<&str>())
|
||||
pub fn empty() -> Rc<Self> {
|
||||
Self::new(std::iter::empty::<PeerId>(), vec![])
|
||||
}
|
||||
|
||||
pub fn new(default_neiborhoud: impl Iterator<Item = impl Into<PeerId>>) -> Self {
|
||||
Self {
|
||||
pub fn new(
|
||||
peers: impl Iterator<Item = impl Into<PeerId>>,
|
||||
common_services: Vec<MarineServiceHandle>,
|
||||
) -> Rc<Self> {
|
||||
let network = Rc::new(Self {
|
||||
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 {
|
||||
let mut network = Self::empty();
|
||||
pub fn from_peers(nodes: Vec<Peer>) -> Rc<Self> {
|
||||
let network = Self::empty();
|
||||
let neighborhood: PeerSet = nodes.iter().map(|peer| peer.peer_id.clone()).collect();
|
||||
for peer in nodes {
|
||||
network.add_peer_env(peer, neighborhood.iter().cloned());
|
||||
@ -134,29 +130,41 @@ impl Network {
|
||||
}
|
||||
|
||||
pub fn add_peer_env(
|
||||
&mut self,
|
||||
self: &Rc<Self>,
|
||||
peer: Peer,
|
||||
neighborhood: impl IntoIterator<Item = impl Into<PeerId>>,
|
||||
) -> &mut PeerEnv {
|
||||
) {
|
||||
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());
|
||||
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.
|
||||
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 mut peer_env = PeerEnv::new(peer);
|
||||
peer_env.extend_neighborhood(self.default_neighborhood.iter().cloned());
|
||||
self.insert_peer_env_entry(peer_id, peer_env)
|
||||
let peer_env = PeerEnv::new(peer, self);
|
||||
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());
|
||||
// It will be simplified with entry_insert stabilization
|
||||
// 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) => {
|
||||
let cell = ent.into_mut();
|
||||
*cell = peer_env;
|
||||
@ -164,8 +172,6 @@ impl Network {
|
||||
}
|
||||
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)
|
||||
@ -173,7 +179,8 @@ impl Network {
|
||||
PeerId: Borrow<Id>,
|
||||
Id: Hash + Eq + ?Sized,
|
||||
{
|
||||
self.peers
|
||||
let mut peers_ref = self.peers.borrow_mut();
|
||||
peers_ref
|
||||
.get_mut(peer_id)
|
||||
.expect("unknown peer")
|
||||
.as_ref()
|
||||
@ -186,7 +193,8 @@ impl Network {
|
||||
PeerId: Borrow<Id>,
|
||||
Id: Hash + Eq + ?Sized,
|
||||
{
|
||||
self.peers
|
||||
let mut peers_ref = self.peers.borrow_mut();
|
||||
peers_ref
|
||||
.get_mut(source_peer_id)
|
||||
.expect("unknown peer")
|
||||
.as_ref()
|
||||
@ -202,7 +210,8 @@ impl Network {
|
||||
PeerId: Borrow<Id2>,
|
||||
Id2: Hash + Eq + ?Sized,
|
||||
{
|
||||
self.peers
|
||||
let mut peers_ref = self.peers.borrow_mut();
|
||||
peers_ref
|
||||
.get_mut(source_peer_id)
|
||||
.expect("unknown peer")
|
||||
.as_ref()
|
||||
@ -218,41 +227,16 @@ impl Network {
|
||||
PeerId: Borrow<Id>,
|
||||
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.
|
||||
/// Following test-utils' call_vm macro, it panics on failed VM.
|
||||
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(crate) fn get_services(&self) -> Rc<NetworkServices> {
|
||||
self.services.clone()
|
||||
}
|
||||
|
||||
pub fn distribute_to_peers(&self, peers: &[String], data: &Data) {
|
||||
for peer_id in peers {
|
||||
if let Some(peer_env_cell) = self.get_peer_env(peer_id.as_str()) {
|
||||
peer_env_cell
|
||||
.borrow_mut()
|
||||
.data_queue
|
||||
.push_back(data.clone());
|
||||
}
|
||||
}
|
||||
pub fn get_peers(&self) -> impl Iterator<Item = PeerId> {
|
||||
let peers_ref = self.peers.borrow();
|
||||
peers_ref.keys().cloned().collect::<Vec<_>>().into_iter()
|
||||
}
|
||||
}
|
||||
|
@ -14,65 +14,75 @@
|
||||
* 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 std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
collections::{HashMap, HashSet},
|
||||
hash::Hash,
|
||||
ops::Deref,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
const EXPECT_VALID_NETWORK: &str = "Using a peer of a destroyed network";
|
||||
|
||||
pub(crate) type PeerSet = HashSet<PeerId>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum LinkState {
|
||||
Reachable,
|
||||
Unreachable,
|
||||
pub enum AlterState {
|
||||
Added,
|
||||
Removed,
|
||||
}
|
||||
|
||||
/// Neighbors of particular node, including set of nodes unreachable from this one (but they might be
|
||||
/// reachable from others).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Neighborhood {
|
||||
// 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 {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
pub fn new(network: &Rc<Network>) -> Self {
|
||||
Self {
|
||||
network: Rc::downgrade(network),
|
||||
unreachable: <_>::default(),
|
||||
altered: <_>::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_neighbors(&mut self, neighbors: PeerSet) {
|
||||
self.neighbors = neighbors
|
||||
.into_iter()
|
||||
.map(|peer_id| (peer_id, LinkState::Reachable))
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &PeerId> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = PeerId> {
|
||||
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();
|
||||
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 remove<Id>(&mut self, other_peer_id: &Id)
|
||||
pub fn unalter<Id>(&mut self, other_peer_id: &Id)
|
||||
where
|
||||
PeerId: Borrow<Id>,
|
||||
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>) {
|
||||
*self.neighbors.get_mut(&target.into()).unwrap() = LinkState::Unreachable;
|
||||
self.unreachable.insert(target.into());
|
||||
}
|
||||
|
||||
pub fn unset_target_unreachable<Id>(&mut self, target: &Id)
|
||||
@ -80,41 +90,58 @@ impl Neighborhood {
|
||||
PeerId: Borrow<Id>,
|
||||
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 {
|
||||
let target_peer_id = target.deref();
|
||||
self.neighbors.get(target_peer_id) == Some(&LinkState::Reachable)
|
||||
pub fn is_reachable<Id>(&self, target: &Id) -> bool
|
||||
where
|
||||
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 {
|
||||
type Item = &'a PeerId;
|
||||
impl std::iter::IntoIterator for &Neighborhood {
|
||||
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 {
|
||||
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(crate) peer: Peer,
|
||||
// failed for everyone
|
||||
failed: bool,
|
||||
neighborhood: Neighborhood,
|
||||
pub(crate) data_queue: VecDeque<Data>,
|
||||
}
|
||||
|
||||
impl PeerEnv {
|
||||
pub fn new(peer: Peer) -> Self {
|
||||
pub fn new(peer: Peer, network: &Rc<Network>) -> Self {
|
||||
Self {
|
||||
peer,
|
||||
failed: false,
|
||||
neighborhood: Default::default(),
|
||||
data_queue: Default::default(),
|
||||
neighborhood: Neighborhood::new(network),
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,26 +163,20 @@ impl PeerEnv {
|
||||
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>>) {
|
||||
let peer_id = self.peer.peer_id.clone();
|
||||
for other_peer_id in peers
|
||||
.map(Into::into)
|
||||
.filter(|other_id| other_id != &peer_id)
|
||||
{
|
||||
self.neighborhood.insert(other_peer_id);
|
||||
let peer_id = &self.peer.peer_id;
|
||||
for other_peer_id in peers.map(Into::into).filter(|other_id| other_id != peer_id) {
|
||||
self.neighborhood.alter(other_peer_id, AlterState::Added);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_from_neighborhood<'a, Id>(&mut self, peers: impl Iterator<Item = &'a Id>)
|
||||
where
|
||||
PeerId: std::borrow::Borrow<Id>,
|
||||
Id: Eq + Hash + ?Sized + 'a,
|
||||
{
|
||||
for peer_id in peers {
|
||||
self.neighborhood.remove(peer_id);
|
||||
pub fn remove_from_neighborhood(&mut self, peers: impl Iterator<Item = impl Into<PeerId>>) {
|
||||
let peer_id = &self.peer.peer_id;
|
||||
for other_peer_id in peers.map(Into::into).filter(|other_id| other_id != peer_id) {
|
||||
self.neighborhood.alter(other_peer_id, AlterState::Removed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,27 +188,28 @@ impl PeerEnv {
|
||||
&mut self.neighborhood
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &PeerId> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = PeerId> {
|
||||
self.neighborhood.iter()
|
||||
}
|
||||
|
||||
pub fn send_data(&mut self, data: Data) {
|
||||
self.data_queue.push_back(data);
|
||||
}
|
||||
|
||||
pub fn execute_once(
|
||||
pub(crate) fn execute_once(
|
||||
&mut self,
|
||||
air: impl Into<String>,
|
||||
network: &Network,
|
||||
queue: &ExecutionQueue,
|
||||
test_parameters: &TestRunParameters,
|
||||
) -> 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| {
|
||||
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 {
|
||||
network.distribute_to_peers(&outcome.next_peer_pks, &outcome.data)
|
||||
queue.distribute_to_peers(network, &outcome.next_peer_pks, &outcome.data)
|
||||
}
|
||||
|
||||
res
|
||||
@ -212,23 +234,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_empty_neighborhood() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id: PeerId = "other".into();
|
||||
let pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
assert!(pwn.is_reachable(&peer_id));
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
let penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
assert!(penv.is_reachable(&peer_id));
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_self_disconnect() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id: PeerId = "other".into();
|
||||
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
let nei = pwn.get_neighborhood_mut();
|
||||
nei.insert(peer_id.clone());
|
||||
nei.remove(&peer_id);
|
||||
assert!(pwn.is_reachable(&peer_id));
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
{
|
||||
let nei = penv.get_neighborhood_mut();
|
||||
|
||||
nei.alter(peer_id.clone(), AlterState::Added);
|
||||
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]
|
||||
@ -236,160 +268,179 @@ mod tests {
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id1: PeerId = "other1".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
|
||||
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()]);
|
||||
pwn.get_neighborhood_mut()
|
||||
.set_neighbors(expected_neighborhood.clone());
|
||||
assert_eq!(
|
||||
pwn.iter().cloned().collect::<PeerSet>(),
|
||||
expected_neighborhood
|
||||
);
|
||||
assert_eq!(penv.iter().collect::<PeerSet>(), expected_neighborhood);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id1: PeerId = "other1".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
|
||||
assert!(pwn.iter().next().is_none());
|
||||
let nei = pwn.get_neighborhood_mut();
|
||||
assert!(penv.iter().next().is_none());
|
||||
|
||||
nei.insert(other_id1.clone());
|
||||
nei.insert(other_id2.clone());
|
||||
network.ensure_peer(other_id1.clone());
|
||||
network.ensure_peer(other_id2.clone());
|
||||
let expected_neighborhood = PeerSet::from([other_id1.clone(), other_id2.clone()]);
|
||||
assert_eq!(
|
||||
PeerSet::from_iter(pwn.iter().cloned()),
|
||||
expected_neighborhood
|
||||
);
|
||||
assert_eq!(PeerSet::from_iter(penv.iter()), 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]
|
||||
fn test_insert_insert() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".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
|
||||
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];
|
||||
assert_eq!(
|
||||
pwn.iter().cloned().collect::<Vec<_>>(),
|
||||
expected_neighborhood
|
||||
);
|
||||
assert_eq!(penv.iter().collect::<Vec<_>>(), expected_neighborhood);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_neighborhood() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
pwn.get_neighborhood_mut().insert("zero");
|
||||
pwn.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
|
||||
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
penv.get_neighborhood_mut()
|
||||
.alter(PeerId::from("zero"), AlterState::Added);
|
||||
penv.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
|
||||
|
||||
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)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_from_neiborhood() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
pwn.get_neighborhood_mut().insert("zero");
|
||||
pwn.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
|
||||
pwn.remove_from_neighborhood(IntoIterator::into_iter(["zero", "two"]));
|
||||
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
penv.get_neighborhood_mut()
|
||||
.alter(PeerId::from("zero"), AlterState::Added);
|
||||
penv.extend_neighborhood(IntoIterator::into_iter(["one", "two"]));
|
||||
penv.remove_from_neighborhood(IntoIterator::into_iter(["zero", "two"]));
|
||||
|
||||
assert_eq!(
|
||||
pwn.iter().cloned().collect::<HashSet<_>>(),
|
||||
IntoIterator::into_iter(["one"])
|
||||
.map(PeerId::from)
|
||||
.collect::<HashSet<_>>()
|
||||
penv.iter().collect::<PeerSet>(),
|
||||
maplit::hashset! {
|
||||
PeerId::from("one"),
|
||||
},
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_fail() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id: PeerId = "other".into();
|
||||
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
let nei = pwn.get_neighborhood_mut();
|
||||
nei.insert(other_id.clone());
|
||||
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
|
||||
let nei = penv.get_neighborhood_mut();
|
||||
nei.alter(other_id.clone(), AlterState::Added);
|
||||
nei.set_target_unreachable(other_id.clone());
|
||||
|
||||
let expected_neighborhood = PeerSet::from([other_id.clone()]);
|
||||
assert_eq!(
|
||||
PeerSet::from_iter(pwn.iter().cloned()),
|
||||
expected_neighborhood
|
||||
);
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
assert_eq!(PeerSet::from_iter(penv.iter()), expected_neighborhood);
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_remove() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".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());
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
|
||||
let nei = pwn.get_neighborhood_mut();
|
||||
nei.remove(&other_id);
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
let nei = penv.get_neighborhood_mut();
|
||||
nei.unalter(&other_id);
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
|
||||
let nei = pwn.get_neighborhood_mut();
|
||||
nei.insert(other_id.clone());
|
||||
assert!(pwn.is_reachable(&other_id));
|
||||
let nei = penv.get_neighborhood_mut();
|
||||
nei.alter(other_id.clone(), AlterState::Added);
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_unfail() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".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());
|
||||
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);
|
||||
assert!(pwn.is_reachable(&other_id));
|
||||
assert!(penv.is_reachable(&other_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failed() {
|
||||
let network = Network::empty();
|
||||
let peer_id: PeerId = "someone".into();
|
||||
let other_id: PeerId = "other".into();
|
||||
let remote_id: PeerId = "remote".into();
|
||||
let mut pwn = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])));
|
||||
pwn.get_neighborhood_mut().insert(other_id.clone());
|
||||
let mut penv = PeerEnv::new(Peer::new(peer_id.clone(), Rc::from(vec![])), &network);
|
||||
penv.get_neighborhood_mut()
|
||||
.alter(other_id.clone(), AlterState::Added);
|
||||
|
||||
assert!(pwn.is_reachable(&peer_id));
|
||||
assert!(pwn.is_reachable(&other_id));
|
||||
assert!(!pwn.is_reachable(&remote_id));
|
||||
assert!(penv.is_reachable(&peer_id));
|
||||
assert!(penv.is_reachable(&other_id));
|
||||
assert!(!penv.is_reachable(&remote_id));
|
||||
|
||||
pwn.set_failed(true);
|
||||
assert!(!pwn.is_reachable(&peer_id));
|
||||
assert!(!pwn.is_reachable(&other_id));
|
||||
assert!(!pwn.is_reachable(&remote_id));
|
||||
penv.set_failed(true);
|
||||
assert!(!penv.is_reachable(&peer_id));
|
||||
assert!(!penv.is_reachable(&other_id));
|
||||
assert!(!penv.is_reachable(&remote_id));
|
||||
|
||||
pwn.set_failed(false);
|
||||
assert!(pwn.is_reachable(&peer_id));
|
||||
assert!(pwn.is_reachable(&other_id));
|
||||
assert!(!pwn.is_reachable(&remote_id));
|
||||
penv.set_failed(false);
|
||||
assert!(penv.is_reachable(&peer_id));
|
||||
assert!(penv.is_reachable(&other_id));
|
||||
assert!(!penv.is_reachable(&remote_id));
|
||||
}
|
||||
}
|
||||
|
@ -15,23 +15,44 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
asserts::ServiceDefinition,
|
||||
ephemeral::{Network, Peer, PeerId},
|
||||
services::{results::ResultService, MarineService, MarineServiceHandle},
|
||||
transform::{walker::Transformer, Sexp},
|
||||
ephemeral::{Network, PeerId},
|
||||
queue::ExecutionQueue,
|
||||
services::MarineServiceHandle,
|
||||
transform::walker::TransformedAirScript,
|
||||
};
|
||||
|
||||
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 {
|
||||
pub air_script: String,
|
||||
pub network: Network,
|
||||
pub test_parameters: TestRunParameters,
|
||||
/// A executor for an AIR script. Several executors may share same TransformedAirScript
|
||||
/// and its state.
|
||||
pub struct AirScriptExecutor {
|
||||
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.
|
||||
///
|
||||
/// `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>,
|
||||
annotated_air_script: &str,
|
||||
) -> Result<Self, String> {
|
||||
// validate the AIR script with the standard parser first
|
||||
air_parser::parse(annotated_air_script)?;
|
||||
let network = Network::new(extra_peers.into_iter(), common_services);
|
||||
let transformed = TransformedAirScript::new(annotated_air_script, network)?;
|
||||
|
||||
let mut sexp = Sexp::from_str(annotated_air_script)?;
|
||||
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,
|
||||
})
|
||||
Self::from_transformed_air_script(test_parameters, transformed)
|
||||
}
|
||||
|
||||
/// Simple constructor where everything is generated from the annotated_air_script.
|
||||
@ -78,12 +76,22 @@ impl TestExecutor {
|
||||
) -> Result<Self, String> {
|
||||
Self::new(
|
||||
test_parameters,
|
||||
<_>::default(),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
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
|
||||
/// for particular peer_id.
|
||||
pub fn execution_iter<'s, Id>(
|
||||
@ -92,11 +100,14 @@ impl TestExecutor {
|
||||
) -> Option<impl Iterator<Item = RawAVMOutcome> + 's>
|
||||
where
|
||||
PeerId: Borrow<Id>,
|
||||
// TODO it's not clear why compiler requies + 's here, but not at Network::iter_execution
|
||||
Id: Eq + Hash + ?Sized + 's,
|
||||
Id: Eq + Hash + ?Sized,
|
||||
{
|
||||
self.network
|
||||
.execution_iter(&self.air_script, &self.test_parameters, peer_id)
|
||||
self.queue.execution_iter(
|
||||
&self.transformed_air_script,
|
||||
self.transformed_air_script.get_network(),
|
||||
&self.test_parameters,
|
||||
peer_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Process all queued datas, panicing on error.
|
||||
@ -115,49 +126,23 @@ impl TestExecutor {
|
||||
Id: Eq + Hash + ?Sized,
|
||||
{
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::services::MarineService;
|
||||
|
||||
use air_test_utils::prelude::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[test]
|
||||
fn test_execution() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
@ -188,7 +173,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_call_result_success() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
@ -217,7 +202,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_call_result_error() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
@ -255,7 +240,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_seq_ok() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||
@ -330,7 +315,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("peer1"),
|
||||
vec![],
|
||||
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||
@ -373,7 +358,7 @@ mod tests {
|
||||
let trace = trace_from_result(outcome3);
|
||||
|
||||
assert_eq!(
|
||||
trace.deref(),
|
||||
&*trace,
|
||||
vec![
|
||||
executed_state::scalar(json!(["peer2", "peer3"])),
|
||||
executed_state::scalar(json!(42)),
|
||||
@ -386,7 +371,7 @@ mod tests {
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_map_no_arg() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("peer1"),
|
||||
vec![],
|
||||
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||
@ -400,7 +385,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_seq_error() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into),
|
||||
@ -476,7 +461,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_echo() {
|
||||
let exec = TestExecutor::new(
|
||||
let exec = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
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]
|
||||
fn test_invalid_air() {
|
||||
let res = TestExecutor::new(
|
||||
let res = AirScriptExecutor::new(
|
||||
TestRunParameters::from_init_peer_id("init_peer_id"),
|
||||
vec![],
|
||||
std::iter::empty(),
|
||||
@ -520,13 +594,16 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
|
||||
match &res {
|
||||
Ok(_) => {
|
||||
assert!(res.is_err());
|
||||
// TestExecutor doesn't implement Debug, so we have to unpack the error this way:
|
||||
if let Err(err) = res {
|
||||
}
|
||||
Err(err) => {
|
||||
assert_eq!(
|
||||
err,
|
||||
"error: \n ┌─ script.air:3:1\n │\n3 │ )\n │ ^ expected \"(\"\n\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,8 @@
|
||||
pub mod asserts;
|
||||
pub mod ephemeral;
|
||||
pub mod execution;
|
||||
mod queue;
|
||||
pub mod services;
|
||||
pub mod transform;
|
||||
|
||||
pub use execution::TestExecutor;
|
||||
pub use execution::AirScriptExecutor;
|
||||
|
116
crates/testing-framework/src/queue.rs
Normal file
116
crates/testing-framework/src/queue.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
|
||||
pub(crate) mod results;
|
||||
|
||||
use self::results::{MarineServiceWrapper, ResultStore};
|
||||
|
||||
use air_test_utils::{CallRequestParams, CallServiceClosure, CallServiceResult};
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -17,126 +17,32 @@
|
||||
use super::{FunctionOutcome, MarineService};
|
||||
use crate::asserts::ServiceDefinition;
|
||||
|
||||
use air_test_utils::{
|
||||
prelude::{echo_call_service, unit_call_service},
|
||||
CallRequestParams, CallServiceClosure, CallServiceResult,
|
||||
};
|
||||
use serde_json::json;
|
||||
use air_test_utils::CallRequestParams;
|
||||
|
||||
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 {
|
||||
results: HashMap<u32, CallServiceClosure>,
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct ResultStore {
|
||||
pub(crate) results: RefCell<HashMap<usize, ServiceDefinition>>,
|
||||
}
|
||||
|
||||
impl TryInto<CallServiceClosure> for ServiceDefinition {
|
||||
type Error = String;
|
||||
|
||||
fn try_into(self) -> Result<CallServiceClosure, String> {
|
||||
match self {
|
||||
ServiceDefinition::Ok(jvalue) => {
|
||||
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)),
|
||||
}
|
||||
impl ResultStore {
|
||||
pub(crate) fn insert(&self, service_definition: ServiceDefinition) -> Result<usize, String> {
|
||||
let mut results = self.results.borrow_mut();
|
||||
let id = results.len();
|
||||
results.insert(id, service_definition);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
|
||||
fn named_service_closure(name: String) -> Result<CallServiceClosure, String> {
|
||||
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 {
|
||||
impl MarineService for ResultStore {
|
||||
fn call(&self, 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 = self.results.get(&key).expect("Unknown result id");
|
||||
FunctionOutcome::ServiceResult(service_desc(params), Duration::ZERO)
|
||||
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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -15,24 +15,60 @@
|
||||
*/
|
||||
|
||||
use super::{Call, Sexp};
|
||||
use crate::{asserts::ServiceDefinition, ephemeral::PeerId};
|
||||
use crate::ephemeral::Network;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::{fmt::Write, ops::Deref, rc::Rc, str::FromStr};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Transformer {
|
||||
cnt: u32,
|
||||
pub(crate) results: HashMap<u32, ServiceDefinition>,
|
||||
pub(crate) peers: HashSet<PeerId>,
|
||||
/// Transformed script represents transformed script's services' state within the network.
|
||||
/// Executions that use the same transformed script share same generated services' state.
|
||||
/// This struct is cheap to clone, and cloned copies share same internal state.
|
||||
#[derive(Clone)]
|
||||
pub struct TransformedAirScript {
|
||||
network: Rc<Network>,
|
||||
tranformed: Rc<str>,
|
||||
}
|
||||
|
||||
impl Transformer {
|
||||
pub(crate) fn new() -> Self {
|
||||
Default::default()
|
||||
impl TransformedAirScript {
|
||||
pub fn new(annotated_air_script: &str, network: Rc<Network>) -> Result<Self, String> {
|
||||
// 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 {
|
||||
Sexp::Call(call) => self.handle_call(call),
|
||||
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...
|
||||
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 {
|
||||
// install a value
|
||||
let call_id = self.cnt;
|
||||
self.cnt += 1;
|
||||
|
||||
self.results.insert(call_id, service.clone());
|
||||
let call_id = result_store.insert(service.clone()).unwrap();
|
||||
|
||||
match &mut call.triplet.1 {
|
||||
Sexp::String(ref mut value) => {
|
||||
@ -70,56 +105,64 @@ impl Transformer {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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]
|
||||
fn test_translate_null() {
|
||||
let mut tree = Sexp::from_str("(null)").unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
assert_eq!(tree.to_string(), "(null)");
|
||||
let network = Network::empty();
|
||||
let transformed = TransformedAirScript::new("(null)", network).unwrap();
|
||||
assert_eq!(&*transformed, "(null)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate_call_no_result() {
|
||||
let network = Network::empty();
|
||||
let script = r#"(call peer_id ("service_id" func) [])"#;
|
||||
let mut tree = Sexp::from_str(script).unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
assert_eq!(tree.to_string(), script);
|
||||
let transformed = TransformedAirScript::new_unvalidated(script, network).unwrap();
|
||||
assert_eq!(&*transformed, script);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
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 mut tree = Sexp::from_str(script).unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
assert_eq!(tree.to_string(), script);
|
||||
let transformed = TransformedAirScript::new(script, network);
|
||||
assert_eq!(transformed.as_deref(), Ok(script));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_translate_call_result() {
|
||||
let network = Network::empty();
|
||||
let script = r#"(call "peer_id" ("service_id" func) []) ; ok = 42"#;
|
||||
let mut tree = Sexp::from_str(script).unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
let transformer = TransformedAirScript::new_unvalidated(script, network.clone()).unwrap();
|
||||
assert_eq!(
|
||||
tree.to_string(),
|
||||
&*transformer,
|
||||
r#"(call "peer_id" ("service_id..0" func) [])"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
transformer.results,
|
||||
Rc::deref(&network.get_services().get_result_store())
|
||||
.clone()
|
||||
.into_inner(),
|
||||
maplit::hashmap! {
|
||||
0u32 => ServiceDefinition::Ok(serde_json::json!(42)),
|
||||
0usize => ServiceDefinition::Ok(serde_json::json!(42)),
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
transformer.peers.into_iter().collect::<Vec<_>>(),
|
||||
network.get_peers().collect::<Vec<_>>(),
|
||||
vec![PeerId::new("peer_id")],
|
||||
);
|
||||
}
|
||||
@ -133,11 +176,10 @@ mod tests {
|
||||
(call peer_id ("service_id" func) [1]) ; ok=true
|
||||
))"#;
|
||||
|
||||
let mut tree = Sexp::from_str(script).unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
let network = Network::empty();
|
||||
let transformed = TransformedAirScript::new_unvalidated(script, network.clone()).unwrap();
|
||||
assert_eq!(
|
||||
tree.to_string(),
|
||||
&*transformed,
|
||||
concat!(
|
||||
"(seq ",
|
||||
r#"(call peer_id ("service_id..0" func) [a 11])"#,
|
||||
@ -150,14 +192,16 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
transformer.results,
|
||||
(*network.get_services().get_result_store())
|
||||
.clone()
|
||||
.into_inner(),
|
||||
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)),
|
||||
}
|
||||
);
|
||||
|
||||
assert!(transformer.peers.is_empty());
|
||||
assert!(network.get_peers().collect::<Vec<_>>().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -171,12 +215,11 @@ mod tests {
|
||||
(call peer_id3 ("service_id" func) [b])
|
||||
))"#;
|
||||
|
||||
let mut tree = Sexp::from_str(script).unwrap();
|
||||
let mut transformer = Transformer::new();
|
||||
transformer.transform(&mut tree);
|
||||
let network = Network::empty();
|
||||
let _ = TransformedAirScript::new_unvalidated(script, network.clone());
|
||||
|
||||
assert_eq!(
|
||||
transformer.peers,
|
||||
network.get_peers().collect::<HashSet<_>>(),
|
||||
HashSet::from_iter(vec![PeerId::new("peer_id1"), PeerId::new("peer_id2")]),
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user