#![cfg(not(target_arch = "wasm32"))]
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use telltale_runtime::effects::{
algebra::{InterpreterState, Program},
handlers::in_memory::InMemoryHandler,
interpreter::{interpret, testing::MockHandler},
middleware::Trace,
ChoreoHandler, LabelId, RoleId,
};
use telltale_runtime::RoleName;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum TestRole {
Alice,
Bob,
Charlie,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum TestLabel {
Accept,
Option1,
Ok,
}
impl LabelId for TestLabel {
fn as_str(&self) -> &'static str {
match self {
TestLabel::Accept => "Accept",
TestLabel::Option1 => "Option1",
TestLabel::Ok => "ok",
}
}
fn from_str(label: &str) -> Option<Self> {
match label {
"Accept" => Some(TestLabel::Accept),
"Option1" => Some(TestLabel::Option1),
"ok" => Some(TestLabel::Ok),
_ => None,
}
}
}
impl RoleId for TestRole {
type Label = TestLabel;
fn role_name(&self) -> RoleName {
match self {
TestRole::Alice => RoleName::from_static("Alice"),
TestRole::Bob => RoleName::from_static("Bob"),
TestRole::Charlie => RoleName::from_static("Charlie"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Request {
id: u32,
payload: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Response {
id: u32,
result: i64,
}
#[tokio::test]
async fn test_interpreter_simple_send_program() {
let mut handler = MockHandler::new(TestRole::Alice);
let mut endpoint = ();
let program = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "hello".into(),
},
)
.end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
let ops = handler.operations();
assert_eq!(ops.len(), 1);
}
#[tokio::test]
async fn test_interpreter_send_recv_sequence() {
use telltale_runtime::effects::interpreter::testing::{MockOperation, MockResponse};
let mut handler = MockHandler::new(TestRole::Alice);
let response = Request {
id: 100,
payload: "reply".into(),
};
handler.add_response(MockResponse::Message(
bincode::serialize(&response).unwrap(),
));
let mut endpoint = ();
let program = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "query".into(),
},
)
.recv::<Request>(TestRole::Bob)
.end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
assert_eq!(result.received_values.len(), 1);
let ops = handler.operations();
assert_eq!(ops.len(), 2);
match &ops[0] {
MockOperation::Send { to, .. } => assert_eq!(*to, TestRole::Bob),
_ => panic!("Expected Send operation first"),
}
match &ops[1] {
MockOperation::Recv { from } => assert_eq!(*from, TestRole::Bob),
_ => panic!("Expected Recv operation second"),
}
}
#[tokio::test]
async fn test_interpreter_choose_offer_sequence() {
use telltale_runtime::effects::interpreter::testing::{MockOperation, MockResponse};
let mut handler = MockHandler::new(TestRole::Bob);
handler.add_response(MockResponse::Label(TestLabel::Accept));
let mut endpoint = ();
let program: Program<TestRole, Request> = Program::new().offer(TestRole::Alice).end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
let ops = handler.operations();
assert_eq!(ops.len(), 1);
match &ops[0] {
MockOperation::Offer { from } => assert_eq!(*from, TestRole::Alice),
_ => panic!("Expected Offer operation"),
}
}
#[tokio::test]
async fn test_interpreter_recv_error_becomes_failed_state() {
use telltale_runtime::effects::algebra::InterpretResult;
let mut handler = MockHandler::new(TestRole::Alice);
let mut endpoint = ();
let program: Program<TestRole, Request> = Program::new().recv::<Request>(TestRole::Bob).end();
let result: InterpretResult<Request> = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
match result.final_state {
InterpreterState::Failed(msg) => {
assert!(msg.contains("No scripted response"));
}
_ => panic!("Expected Failed state, got {:?}", result.final_state),
}
}
#[tokio::test]
async fn test_interpreter_offer_error_becomes_failed_state() {
use telltale_runtime::effects::algebra::InterpretResult;
let mut handler = MockHandler::new(TestRole::Bob);
let mut endpoint = ();
let program: Program<TestRole, Request> = Program::new().offer(TestRole::Alice).end();
let result: InterpretResult<Request> = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
match result.final_state {
InterpreterState::Failed(msg) => {
assert!(msg.contains("No scripted label"));
}
_ => panic!("Expected Failed state"),
}
}
#[tokio::test]
async fn test_trace_middleware_wraps_handler() {
let inner = MockHandler::new(TestRole::Alice);
let mut traced = Trace::new(inner);
let mut endpoint = ();
let msg = Request {
id: 1,
payload: "test".into(),
};
traced
.send(&mut endpoint, TestRole::Bob, &msg)
.await
.expect("Send should succeed");
}
#[tokio::test]
async fn test_trace_middleware_with_custom_prefix() {
let inner = MockHandler::new(TestRole::Alice);
let mut traced = Trace::with_prefix(inner, "custom-prefix");
let mut endpoint = ();
traced
.send(
&mut endpoint,
TestRole::Bob,
&Request {
id: 1,
payload: "x".into(),
},
)
.await
.unwrap();
}
#[tokio::test]
async fn test_trace_middleware_choose_offer() {
let inner = MockHandler::new(TestRole::Alice);
let mut traced = Trace::new(inner);
let mut endpoint = ();
traced
.choose(&mut endpoint, TestRole::Bob, TestLabel::Option1)
.await
.expect("Choose should succeed");
}
#[tokio::test]
async fn test_in_memory_handler_bidirectional_protocol() {
let channels = Arc::new(Mutex::new(BTreeMap::new()));
let choice_channels = Arc::new(Mutex::new(BTreeMap::new()));
let mut alice =
InMemoryHandler::with_channels(TestRole::Alice, channels.clone(), choice_channels.clone());
let mut bob =
InMemoryHandler::with_channels(TestRole::Bob, channels.clone(), choice_channels.clone());
let request = Request {
id: 42,
payload: "hello".into(),
};
alice
.send(&mut (), TestRole::Bob, &request)
.await
.expect("Alice send should succeed");
let received_request: Request = bob
.recv(&mut (), TestRole::Alice)
.await
.expect("Bob recv should succeed");
assert_eq!(received_request, request);
let response = Response {
id: 42,
result: 100,
};
bob.send(&mut (), TestRole::Alice, &response)
.await
.expect("Bob send should succeed");
let received_response: Response = alice
.recv(&mut (), TestRole::Bob)
.await
.expect("Alice recv should succeed");
assert_eq!(received_response, response);
}
#[tokio::test]
async fn test_in_memory_handler_choice_protocol() {
let channels = Arc::new(Mutex::new(BTreeMap::new()));
let choice_channels = Arc::new(Mutex::new(BTreeMap::new()));
let mut alice =
InMemoryHandler::with_channels(TestRole::Alice, channels.clone(), choice_channels.clone());
let mut bob =
InMemoryHandler::with_channels(TestRole::Bob, channels.clone(), choice_channels.clone());
alice
.choose(&mut (), TestRole::Bob, TestLabel::Accept)
.await
.expect("Alice choose should succeed");
let label = bob
.offer(&mut (), TestRole::Alice)
.await
.expect("Bob offer should succeed");
assert_eq!(label, TestLabel::Accept);
}
#[tokio::test]
async fn test_in_memory_handler_three_party_protocol() {
let channels = Arc::new(Mutex::new(BTreeMap::new()));
let choice_channels = Arc::new(Mutex::new(BTreeMap::new()));
let mut alice =
InMemoryHandler::with_channels(TestRole::Alice, channels.clone(), choice_channels.clone());
let mut bob =
InMemoryHandler::with_channels(TestRole::Bob, channels.clone(), choice_channels.clone());
let mut charlie = InMemoryHandler::with_channels(
TestRole::Charlie,
channels.clone(),
choice_channels.clone(),
);
alice
.send(
&mut (),
TestRole::Bob,
&Request {
id: 1,
payload: "to bob".into(),
},
)
.await
.unwrap();
alice
.send(
&mut (),
TestRole::Charlie,
&Request {
id: 2,
payload: "to charlie".into(),
},
)
.await
.unwrap();
let bob_msg: Request = bob.recv(&mut (), TestRole::Alice).await.unwrap();
assert_eq!(bob_msg.id, 1);
let charlie_msg: Request = charlie.recv(&mut (), TestRole::Alice).await.unwrap();
assert_eq!(charlie_msg.id, 2);
bob.send(&mut (), TestRole::Charlie, &Response { id: 1, result: 10 })
.await
.unwrap();
let from_bob: Response = charlie.recv(&mut (), TestRole::Bob).await.unwrap();
assert_eq!(from_bob.result, 10);
}
#[tokio::test]
async fn test_traced_in_memory_handler() {
let channels = Arc::new(Mutex::new(BTreeMap::new()));
let choice_channels = Arc::new(Mutex::new(BTreeMap::new()));
let alice_inner =
InMemoryHandler::with_channels(TestRole::Alice, channels.clone(), choice_channels.clone());
let bob_inner =
InMemoryHandler::with_channels(TestRole::Bob, channels.clone(), choice_channels.clone());
let mut alice = Trace::with_prefix(alice_inner, "alice");
let mut bob = Trace::with_prefix(bob_inner, "bob");
alice
.send(
&mut (),
TestRole::Bob,
&Request {
id: 1,
payload: "test".into(),
},
)
.await
.unwrap();
let msg: Request = bob.recv(&mut (), TestRole::Alice).await.unwrap();
assert_eq!(msg.id, 1);
}
#[test]
fn test_program_send_count() {
let program: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "a".into(),
},
)
.send(
TestRole::Charlie,
Request {
id: 2,
payload: "b".into(),
},
)
.end();
assert_eq!(program.send_count(), 2);
}
#[test]
fn test_program_recv_count() {
let program: Program<TestRole, Response> = Program::new()
.recv::<Response>(TestRole::Bob)
.recv::<Response>(TestRole::Charlie)
.recv::<Response>(TestRole::Alice)
.end();
assert_eq!(program.recv_count(), 3);
}
#[test]
fn test_program_roles_involved() {
let program: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "x".into(),
},
)
.recv::<Request>(TestRole::Charlie)
.choose(TestRole::Alice, TestLabel::Ok)
.end();
let roles = program.roles_involved();
assert!(roles.contains(&TestRole::Alice));
assert!(roles.contains(&TestRole::Bob));
assert!(roles.contains(&TestRole::Charlie));
}
#[test]
fn test_program_has_timeouts() {
use std::time::Duration;
let program_no_timeout: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "x".into(),
},
)
.end();
assert!(!program_no_timeout.has_timeouts());
let inner: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "x".into(),
},
)
.end();
let program_with_timeout: Program<TestRole, Request> = Program::new()
.with_timeout(TestRole::Alice, Duration::from_secs(5), inner)
.end();
assert!(program_with_timeout.has_timeouts());
}
#[test]
fn test_program_has_parallel() {
let program_no_parallel: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "x".into(),
},
)
.end();
assert!(!program_no_parallel.has_parallel());
let p1: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "a".into(),
},
)
.end();
let p2: Program<TestRole, Request> = Program::new()
.send(
TestRole::Charlie,
Request {
id: 2,
payload: "b".into(),
},
)
.end();
let program_with_parallel: Program<TestRole, Request> =
Program::new().parallel(vec![p1, p2]).end();
assert!(program_with_parallel.has_parallel());
}
#[tokio::test]
async fn test_interpreter_multi_step_protocol() {
use telltale_runtime::effects::interpreter::testing::MockResponse;
let mut handler = MockHandler::new(TestRole::Alice);
handler.add_response(MockResponse::Message(
bincode::serialize(&Request {
id: 10,
payload: "response1".into(),
})
.unwrap(),
));
handler.add_response(MockResponse::Message(
bincode::serialize(&Request {
id: 20,
payload: "response2".into(),
})
.unwrap(),
));
let mut endpoint = ();
let program: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "first".into(),
},
)
.recv::<Request>(TestRole::Bob)
.send(
TestRole::Bob,
Request {
id: 2,
payload: "second".into(),
},
)
.recv::<Request>(TestRole::Bob)
.end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
assert_eq!(result.received_values.len(), 2);
}
#[tokio::test]
async fn test_interpreter_loop_effect() {
let mut handler = MockHandler::new(TestRole::Alice);
let mut endpoint = ();
let body: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 0,
payload: "loop".into(),
},
)
.end();
let program: Program<TestRole, Request> = Program::new().loop_n(3, body).end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
let ops = handler.operations();
assert_eq!(ops.len(), 3);
}
#[tokio::test]
async fn test_interpreter_parallel_effect() {
let mut handler = MockHandler::new(TestRole::Alice);
let mut endpoint = ();
let p1: Program<TestRole, Request> = Program::new()
.send(
TestRole::Bob,
Request {
id: 1,
payload: "to bob".into(),
},
)
.end();
let p2: Program<TestRole, Request> = Program::new()
.send(
TestRole::Charlie,
Request {
id: 2,
payload: "to charlie".into(),
},
)
.end();
let program = Program::new().parallel(vec![p1, p2]).end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
let ops = handler.operations();
assert_eq!(ops.len(), 2);
}
#[tokio::test]
async fn test_interpreter_empty_program() {
let mut handler = MockHandler::new(TestRole::Alice);
let mut endpoint = ();
let program: Program<TestRole, Request> = Program::new().end();
let result = interpret(&mut handler, &mut endpoint, program)
.await
.unwrap();
assert_eq!(result.final_state, InterpreterState::Completed);
assert_eq!(result.received_values.len(), 0);
assert_eq!(handler.operations().len(), 0);
}