telltale_runtime/effects/
interpreter_testing.rs1use super::{
2 async_trait, ChoreoHandler, ChoreoResult, ChoreographyError, DeserializeOwned, RoleId,
3 Serialize,
4};
5use crate::effects::contract::{
6 DeliveryModel, DocumentedHandlerContract, ExtensionDispatchContract, ExtensionDispatchMode,
7 HandlerContractProfile, HandlerContractTier, ProtocolSemanticContract, RetryPolicy,
8 TimeoutPolicy, TransportPolicyContract,
9};
10use std::collections::VecDeque;
11
12pub struct MockHandler<R: RoleId> {
14 _role: R,
16 recorded_operations: Vec<MockOperation<R>>,
17 scripted_responses: VecDeque<MockResponse<<R as RoleId>::Label>>,
18}
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum MockOperation<R: RoleId> {
22 Send { to: R, msg_type: String },
23 Recv { from: R },
24 Choose { at: R, label: <R as RoleId>::Label },
25 Offer { from: R },
26}
27
28#[derive(Debug, Clone)]
29pub enum MockResponse<L> {
30 Message(Vec<u8>),
31 Label(L),
32 Error(String),
33}
34
35impl<R: RoleId> MockHandler<R> {
36 pub fn new(role: R) -> Self {
37 Self {
38 _role: role,
39 recorded_operations: Vec::new(),
40 scripted_responses: VecDeque::new(),
41 }
42 }
43
44 pub fn add_response(&mut self, response: MockResponse<<R as RoleId>::Label>) {
45 self.scripted_responses.push_back(response);
46 }
47
48 pub fn operations(&self) -> &[MockOperation<R>] {
49 &self.recorded_operations
50 }
51
52 pub fn clear_operations(&mut self) {
53 self.recorded_operations.clear();
54 }
55}
56
57impl<R: RoleId> DocumentedHandlerContract for MockHandler<R> {
58 fn contract_profile() -> HandlerContractProfile {
59 HandlerContractProfile {
60 handler_name: std::any::type_name::<Self>(),
61 tier: HandlerContractTier::ObservationalHarness,
62 semantics: ProtocolSemanticContract {
63 typed_send_recv_roundtrip: false,
64 exact_choice_label_preservation: true,
65 fail_closed_transport_errors: true,
66 timeouts_scoped_to_enforcing_role: true,
67 deterministic_for_regression: true,
68 can_materialize_values: true,
69 },
70 transport: TransportPolicyContract {
71 delivery_model: DeliveryModel::ScriptedHarness,
72 retry_policy: RetryPolicy::None,
73 timeout_policy: TimeoutPolicy::PassThrough,
74 },
75 extension_dispatch: ExtensionDispatchContract {
76 mode: ExtensionDispatchMode::Unsupported,
77 fail_closed_when_unregistered: false,
78 type_exact_before_side_effects: false,
79 },
80 notes: vec![
81 "values and labels come only from an explicit scripted response queue",
82 "missing scripted responses fail closed instead of synthesizing protocol progress",
83 ],
84 }
85 }
86}
87
88#[async_trait]
89impl<R: RoleId + 'static> ChoreoHandler for MockHandler<R> {
90 type Role = R;
91 type Endpoint = ();
92
93 async fn send<M: Serialize + Send + Sync>(
94 &mut self,
95 _ep: &mut Self::Endpoint,
96 to: Self::Role,
97 _msg: &M,
98 ) -> ChoreoResult<()> {
99 self.recorded_operations.push(MockOperation::Send {
100 to,
101 msg_type: std::any::type_name::<M>().to_string(),
102 });
103 Ok(())
104 }
105
106 async fn recv<M: DeserializeOwned + Send>(
107 &mut self,
108 _ep: &mut Self::Endpoint,
109 from: Self::Role,
110 ) -> ChoreoResult<M> {
111 self.recorded_operations.push(MockOperation::Recv { from });
112
113 if let Some(MockResponse::Message(bytes)) = self.scripted_responses.pop_front() {
114 bincode::deserialize(&bytes)
115 .map_err(|e| ChoreographyError::Serialization(e.to_string()))
116 } else {
117 Err(ChoreographyError::Transport(
118 "No scripted response available".into(),
119 ))
120 }
121 }
122
123 async fn choose(
124 &mut self,
125 _ep: &mut Self::Endpoint,
126 at: Self::Role,
127 label: <Self::Role as RoleId>::Label,
128 ) -> ChoreoResult<()> {
129 self.recorded_operations
130 .push(MockOperation::Choose { at, label });
131 Ok(())
132 }
133
134 async fn offer(
135 &mut self,
136 _ep: &mut Self::Endpoint,
137 from: Self::Role,
138 ) -> ChoreoResult<<Self::Role as RoleId>::Label> {
139 self.recorded_operations.push(MockOperation::Offer { from });
140
141 if let Some(MockResponse::Label(label)) = self.scripted_responses.pop_front() {
142 Ok(label)
143 } else {
144 Err(ChoreographyError::Transport(
145 "No scripted label available".into(),
146 ))
147 }
148 }
149
150 async fn with_timeout<F, T>(
151 &mut self,
152 _ep: &mut Self::Endpoint,
153 _at: Self::Role,
154 _dur: std::time::Duration,
155 body: F,
156 ) -> ChoreoResult<T>
157 where
158 F: std::future::Future<Output = ChoreoResult<T>> + Send,
159 {
160 body.await
161 }
162}