Skip to content

Commit

Permalink
[TestLoop] (1/n) Initial code for TestLoop test framework. (#8481)
Browse files Browse the repository at this point in the history
* [TestLoop] (1/n) Initial code for TestLoop test framework.

* Revise.

* Revise more.
  • Loading branch information
robin-near authored Feb 6, 2023
1 parent 195dd39 commit 83aeb4a
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 0 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"chain/pool",
"chain/rosetta-rpc",
"chain/telemetry",
"core/async",
"core/account-id",
"core/account-id/fuzz",
"core/chain-configs",
Expand Down Expand Up @@ -105,6 +106,7 @@ crossbeam = "0.8"
crossbeam-channel = "0.5"
csv = "1.1.1"
curve25519-dalek = "3"
derive-enum-from-into = "0.1.1"
derive_more = "0.99.9"
dirs = "4"
easy-ext = "0.2"
Expand Down
20 changes: 20 additions & 0 deletions core/async/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "near-async"
version = "0.0.0"
authors.workspace = true
publish = true
rust-version.workspace = true
edition.workspace = true
license = "MIT OR Apache-2.0"
repository = "https://github.com/near/nearcore"
description = "This crate contains the async helpers specific for nearcore"

[dependencies]
actix.workspace = true
derive-enum-from-into.workspace = true
derive_more.workspace = true
once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true

near-o11y = { path = "../o11y" }
1 change: 1 addition & 0 deletions core/async/LICENSE-APACHE
1 change: 1 addition & 0 deletions core/async/LICENSE-MIT
8 changes: 8 additions & 0 deletions core/async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Core Async Helpers

This crate contains helpers related to common asynchronous programming patterns
used in nearcore:

* messaging: common interfaces for sending messages between components.
* test_loop: a event-loop-based test framework that can test multiple components
together in a synchronous way.
2 changes: 2 additions & 0 deletions core/async/src/examples/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod sum_numbers;
mod sum_numbers_test;
36 changes: 36 additions & 0 deletions core/async/src/examples/sum_numbers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::messaging::ArcSender;

#[derive(Debug, PartialEq, Eq)]
pub struct ReportSumMsg(pub i64);

#[derive(Debug)]
pub enum SumRequest {
Number(i64),
GetSum,
}

// Mimics a typical backing component of some actor in nearcore. Handles request
// messages, and sends some other messages to another actor. The other actor is
// abstracted with an ArcSender here. We'll show how to test this in
// sum_numbers_test.rs.
pub struct SumNumbersComponent {
result_sender: ArcSender<ReportSumMsg>,
numbers: Vec<i64>,
}

impl SumNumbersComponent {
pub fn new(result_sender: ArcSender<ReportSumMsg>) -> Self {
Self { result_sender, numbers: vec![] }
}

pub fn handle(&mut self, msg: SumRequest) {
match msg {
SumRequest::Number(n) => self.numbers.push(n),
SumRequest::GetSum => {
let sum = self.numbers.iter().sum();
self.numbers.clear();
self.result_sender.send(ReportSumMsg(sum));
}
}
}
}
64 changes: 64 additions & 0 deletions core/async/src/examples/sum_numbers_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::{sync::Arc, time::Duration};

use derive_enum_from_into::{EnumFrom, EnumTryInto};

use crate::{
messaging::Sender,
test_loop::{CaptureEvents, LoopEventHandler, TestLoopBuilder, TryIntoOrSelf},
};

use super::sum_numbers::{ReportSumMsg, SumNumbersComponent, SumRequest};

#[derive(derive_more::AsMut, derive_more::AsRef)]
struct TestData {
summer: SumNumbersComponent,
sums: Vec<ReportSumMsg>,
}

#[derive(Debug, EnumTryInto, EnumFrom)]
enum TestEvent {
Request(SumRequest),
Sum(ReportSumMsg),
}

// Handler that forwards SumRequest messages to the SumNumberComponent.
// Note that typically we would have a single handler like this, and it can
// be reused for any test that needs to send messages to this component.
pub struct ForwardSumRequest;

impl<Data: AsMut<SumNumbersComponent>, Event: TryIntoOrSelf<SumRequest>>
LoopEventHandler<Data, Event> for ForwardSumRequest
{
fn handle(&mut self, event: Event, data: &mut Data) -> Option<Event> {
match event.try_into_or_self() {
Ok(request) => {
data.as_mut().handle(request);
None
}
Err(event) => Some(event),
}
}
}

#[test]
fn test_simple() {
let builder = TestLoopBuilder::<TestEvent>::new();
// Build the SumNumberComponents so that it sends messages back to the test loop.
let data =
TestData { summer: SumNumbersComponent::new(Arc::new(builder.sender())), sums: vec![] };
let sender = builder.sender();
let mut test = builder.build(data);
test.register_handler(ForwardSumRequest);
test.register_handler(CaptureEvents::<ReportSumMsg>::new());

sender.send(TestEvent::Request(SumRequest::Number(1)));
sender.send(TestEvent::Request(SumRequest::Number(2)));
sender.send(TestEvent::Request(SumRequest::GetSum));
sender.send(TestEvent::Request(SumRequest::Number(3)));
sender.send(TestEvent::Request(SumRequest::Number(4)));
sender.send(TestEvent::Request(SumRequest::Number(5)));
sender.send(TestEvent::Request(SumRequest::GetSum));

test.run(Duration::from_millis(1));
assert_eq!(test.data.sums, vec![ReportSumMsg(3), ReportSumMsg(12)]);
}
5 changes: 5 additions & 0 deletions core/async/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod messaging;
pub mod test_loop;

#[cfg(test)]
mod examples;
10 changes: 10 additions & 0 deletions core/async/src/messaging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::sync::Arc;

/// Anything that allows sending a message of type M.
pub trait Sender<M>: Send + Sync + 'static {
fn send(&self, message: M);
}

pub type ArcSender<M> = Arc<dyn Sender<M>>;

// TODO: Sender implementation for actix::Addr and other utils.
Loading

0 comments on commit 83aeb4a

Please sign in to comment.