Skip to main content

linera_execution/test_utils/
mock_application.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Mocking of user applications to help with execution scenario tests.
5
6use std::{
7    collections::VecDeque,
8    fmt::{self, Debug, Display, Formatter},
9    sync::{
10        atomic::{AtomicUsize, Ordering},
11        Arc, Mutex,
12    },
13};
14
15#[cfg(web)]
16use js_sys::wasm_bindgen;
17use linera_base::data_types::StreamUpdate;
18
19use crate::{
20    ContractSyncRuntimeHandle, ExecutionError, ServiceSyncRuntimeHandle, UserContract,
21    UserContractModule, UserService, UserServiceModule,
22};
23
24/// A mocked implementation of a user application.
25///
26/// Should be configured with any expected calls, and can then be used to create a
27/// [`MockApplicationInstance`] that implements [`UserContract`] and [`UserService`].
28#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen)]
29#[derive(Clone, Default)]
30pub struct MockApplication {
31    expected_calls: Arc<Mutex<VecDeque<ExpectedCall>>>,
32    active_instances: Arc<AtomicUsize>,
33}
34
35/// A mocked implementation of a user application instance.
36///
37/// Will expect certain calls previously configured through [`MockApplication`].
38pub struct MockApplicationInstance<Runtime> {
39    expected_calls: Arc<Mutex<VecDeque<ExpectedCall>>>,
40    runtime: Runtime,
41    active_instances: Arc<AtomicUsize>,
42}
43
44impl MockApplication {
45    /// Queues an expected call to the [`MockApplication`].
46    pub fn expect_call(&self, expected_call: ExpectedCall) {
47        self.expected_calls
48            .lock()
49            .expect("Mutex is poisoned")
50            .push_back(expected_call);
51    }
52
53    /// Creates a new [`MockApplicationInstance`], forwarding the configured expected calls.
54    pub fn create_mock_instance<Runtime>(
55        &self,
56        runtime: Runtime,
57    ) -> MockApplicationInstance<Runtime> {
58        self.active_instances.fetch_add(1, Ordering::AcqRel);
59
60        MockApplicationInstance {
61            expected_calls: self.expected_calls.clone(),
62            runtime,
63            active_instances: self.active_instances.clone(),
64        }
65    }
66
67    /// Panics if there are still expected calls left in this [`MockApplication`].
68    pub fn assert_no_more_expected_calls(&self) {
69        assert!(
70            self.expected_calls.lock().unwrap().is_empty(),
71            "Missing call to instantiate a `MockApplicationInstance`"
72        );
73    }
74
75    /// Panics if there are still expected calls in one of the [`MockApplicationInstance`]s created
76    /// from this [`MockApplication`].
77    pub fn assert_no_active_instances(&self) {
78        assert_eq!(
79            self.active_instances.load(Ordering::Acquire),
80            0,
81            "At least one of `MockApplicationInstance` is still waiting for expected calls"
82        );
83    }
84}
85
86impl Debug for MockApplication {
87    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
88        let mut struct_formatter = formatter.debug_struct("MockApplication");
89
90        match self.expected_calls.lock() {
91            Ok(expected_calls) => struct_formatter.field("expected_calls", &*expected_calls),
92            Err(_) => struct_formatter.field("expected_calls", &"[POISONED]"),
93        };
94
95        struct_formatter
96            .field(
97                "active_instances",
98                &self.active_instances.load(Ordering::Acquire),
99            )
100            .finish()
101    }
102}
103
104impl PartialEq for MockApplication {
105    fn eq(&self, other: &Self) -> bool {
106        Arc::ptr_eq(&self.expected_calls, &other.expected_calls)
107            && Arc::ptr_eq(&self.active_instances, &other.active_instances)
108    }
109}
110
111impl Eq for MockApplication {}
112
113impl<Runtime> Drop for MockApplicationInstance<Runtime> {
114    fn drop(&mut self) {
115        self.active_instances.fetch_sub(1, Ordering::AcqRel);
116    }
117}
118
119type InstantiateHandler = Box<
120    dyn FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError> + Send + Sync,
121>;
122type ExecuteOperationHandler = Box<
123    dyn FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
124        + Send
125        + Sync,
126>;
127type ExecuteMessageHandler = Box<
128    dyn FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError> + Send + Sync,
129>;
130type ProcessStreamHandler = Box<
131    dyn FnOnce(&mut ContractSyncRuntimeHandle, Vec<StreamUpdate>) -> Result<(), ExecutionError>
132        + Send
133        + Sync,
134>;
135type SummarizeEventsHandler = Box<
136    dyn FnOnce(&mut ContractSyncRuntimeHandle, Vec<StreamUpdate>) -> Result<(), ExecutionError>
137        + Send
138        + Sync,
139>;
140type FinalizeHandler =
141    Box<dyn FnOnce(&mut ContractSyncRuntimeHandle) -> Result<(), ExecutionError> + Send + Sync>;
142type HandleQueryHandler = Box<
143    dyn FnOnce(&mut ServiceSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
144        + Send
145        + Sync,
146>;
147
148/// An expected call to a [`MockApplicationInstance`].
149#[derive(custom_debug_derive::Debug)]
150pub enum ExpectedCall {
151    /// An expected call to [`UserContract::instantiate`].
152    Instantiate(#[debug(skip)] InstantiateHandler),
153    /// An expected call to [`UserContract::execute_operation`].
154    ExecuteOperation(#[debug(skip)] ExecuteOperationHandler),
155    /// An expected call to [`UserContract::execute_message`].
156    ExecuteMessage(#[debug(skip)] ExecuteMessageHandler),
157    /// An expected call to [`UserContract::process_streams`].
158    ProcessStreams(#[debug(skip)] ProcessStreamHandler),
159    /// An expected call to [`UserContract::summarize_events`].
160    SummarizeEvents(#[debug(skip)] SummarizeEventsHandler),
161    /// An expected call to [`UserContract::finalize`].
162    Finalize(#[debug(skip)] FinalizeHandler),
163    /// An expected call to [`UserService::handle_query`].
164    HandleQuery(#[debug(skip)] HandleQueryHandler),
165}
166
167impl Display for ExpectedCall {
168    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
169        let name = match self {
170            ExpectedCall::Instantiate(_) => "instantiate",
171            ExpectedCall::ExecuteOperation(_) => "execute_operation",
172            ExpectedCall::ExecuteMessage(_) => "execute_message",
173            ExpectedCall::ProcessStreams(_) => "process_streams",
174            ExpectedCall::SummarizeEvents(_) => "summarize_events",
175            ExpectedCall::Finalize(_) => "finalize",
176            ExpectedCall::HandleQuery(_) => "handle_query",
177        };
178
179        write!(formatter, "{name}")
180    }
181}
182
183impl ExpectedCall {
184    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
185    /// [`UserContract::instantiate`] implementation, which is handled by the provided `handler`.
186    pub fn instantiate(
187        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError>
188            + Send
189            + Sync
190            + 'static,
191    ) -> Self {
192        ExpectedCall::Instantiate(Box::new(handler))
193    }
194
195    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
196    /// [`UserContract::execute_operation`] implementation, which is handled by the provided
197    /// `handler`.
198    pub fn execute_operation(
199        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
200            + Send
201            + Sync
202            + 'static,
203    ) -> Self {
204        ExpectedCall::ExecuteOperation(Box::new(handler))
205    }
206
207    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
208    /// [`UserContract::execute_message`] implementation, which is handled by the provided
209    /// `handler`.
210    pub fn execute_message(
211        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError>
212            + Send
213            + Sync
214            + 'static,
215    ) -> Self {
216        ExpectedCall::ExecuteMessage(Box::new(handler))
217    }
218
219    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
220    /// [`UserContract::process_streams`] implementation, which is handled by the provided
221    /// `handler`.
222    pub fn process_streams(
223        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<StreamUpdate>) -> Result<(), ExecutionError>
224            + Send
225            + Sync
226            + 'static,
227    ) -> Self {
228        ExpectedCall::ProcessStreams(Box::new(handler))
229    }
230
231    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
232    /// [`UserContract::summarize_events`] implementation, which is handled by the provided
233    /// `handler`.
234    pub fn summarize_events(
235        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<StreamUpdate>) -> Result<(), ExecutionError>
236            + Send
237            + Sync
238            + 'static,
239    ) -> Self {
240        ExpectedCall::SummarizeEvents(Box::new(handler))
241    }
242
243    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s [`UserContract::finalize`]
244    /// implementation, which is handled by the provided `handler`.
245    pub fn finalize(
246        handler: impl FnOnce(&mut ContractSyncRuntimeHandle) -> Result<(), ExecutionError>
247            + Send
248            + Sync
249            + 'static,
250    ) -> Self {
251        ExpectedCall::Finalize(Box::new(handler))
252    }
253
254    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s [`UserContract::finalize`]
255    /// implementation, which is handled by the default implementation which does nothing.
256    pub fn default_finalize() -> Self {
257        Self::finalize(|_| Ok(()))
258    }
259
260    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
261    /// [`UserService::handle_query`] implementation, which is handled by the provided `handler`.
262    pub fn handle_query(
263        handler: impl FnOnce(&mut ServiceSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
264            + Send
265            + Sync
266            + 'static,
267    ) -> Self {
268        ExpectedCall::HandleQuery(Box::new(handler))
269    }
270}
271
272impl UserContractModule for MockApplication {
273    fn instantiate(
274        &self,
275        runtime: ContractSyncRuntimeHandle,
276    ) -> Result<Box<dyn UserContract + 'static>, ExecutionError> {
277        Ok(Box::new(self.create_mock_instance(runtime)))
278    }
279}
280
281impl UserServiceModule for MockApplication {
282    fn instantiate(
283        &self,
284        runtime: ServiceSyncRuntimeHandle,
285    ) -> Result<Box<dyn UserService + 'static>, ExecutionError> {
286        Ok(Box::new(self.create_mock_instance(runtime)))
287    }
288}
289
290impl<Runtime> MockApplicationInstance<Runtime> {
291    /// Retrieves the next [`ExpectedCall`] in the queue.
292    fn next_expected_call(&self) -> Option<ExpectedCall> {
293        self.expected_calls
294            .lock()
295            .expect("Queue of expected calls was poisoned")
296            .pop_front()
297    }
298}
299
300impl UserContract for MockApplicationInstance<ContractSyncRuntimeHandle> {
301    fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError> {
302        match self.next_expected_call() {
303            Some(ExpectedCall::Instantiate(handler)) => handler(&mut self.runtime, argument),
304            Some(unexpected_call) => panic!(
305                "Expected a call to `instantiate`, got a call to `{unexpected_call}` instead."
306            ),
307            None => panic!("Unexpected call to `instantiate`"),
308        }
309    }
310
311    fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
312        match self.next_expected_call() {
313            Some(ExpectedCall::ExecuteOperation(handler)) => handler(&mut self.runtime, operation),
314            Some(unexpected_call) => panic!(
315                "Expected a call to `execute_operation`, got a call to `{unexpected_call}` instead."
316            ),
317            None => panic!("Unexpected call to `execute_operation`"),
318        }
319    }
320
321    fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError> {
322        match self.next_expected_call() {
323            Some(ExpectedCall::ExecuteMessage(handler)) => handler(&mut self.runtime, message),
324            Some(unexpected_call) => panic!(
325                "Expected a call to `execute_message`, got a call to `{unexpected_call}` instead."
326            ),
327            None => panic!("Unexpected call to `execute_message`"),
328        }
329    }
330
331    fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
332        match self.next_expected_call() {
333            Some(ExpectedCall::ProcessStreams(handler)) => handler(&mut self.runtime, updates),
334            Some(unexpected_call) => panic!(
335                "Expected a call to `process_streams`, got a call to `{unexpected_call}` instead."
336            ),
337            None => panic!("Unexpected call to `process_streams`"),
338        }
339    }
340
341    fn summarize_events(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
342        match self.next_expected_call() {
343            Some(ExpectedCall::SummarizeEvents(handler)) => handler(&mut self.runtime, updates),
344            Some(unexpected_call) => panic!(
345                "Expected a call to `summarize_events`, got a call to `{unexpected_call}` instead."
346            ),
347            None => panic!("Unexpected call to `summarize_events`"),
348        }
349    }
350
351    fn finalize(&mut self) -> Result<(), ExecutionError> {
352        match self.next_expected_call() {
353            Some(ExpectedCall::Finalize(handler)) => handler(&mut self.runtime),
354            Some(unexpected_call) => {
355                panic!("Expected a call to `finalize`, got a call to `{unexpected_call}` instead.")
356            }
357            None => panic!("Unexpected call to `finalize`"),
358        }
359    }
360}
361
362impl UserService for MockApplicationInstance<ServiceSyncRuntimeHandle> {
363    fn handle_query(&mut self, query: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
364        match self.next_expected_call() {
365            Some(ExpectedCall::HandleQuery(handler)) => handler(&mut self.runtime, query),
366            Some(unexpected_call) => panic!(
367                "Expected a call to `handle_query`, got a call to `{unexpected_call}` instead."
368            ),
369            None => panic!("Unexpected call to `handle_query`"),
370        }
371    }
372}