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_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();

View File

@ -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),

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");
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 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();

View File

@ -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),

View File

@ -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();

View File

@ -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),

View File

@ -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()))
}

View File

@ -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")),

View File

@ -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()
}
}

View File

@ -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));
}
}

View File

@ -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"
);
}
}
}
}

View File

@ -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;

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;
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()
}
}

View File

@ -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)
}
}

View File

@ -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")]),
)
}