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 FinalizeHandler =
136    Box<dyn FnOnce(&mut ContractSyncRuntimeHandle) -> Result<(), ExecutionError> + Send + Sync>;
137type HandleQueryHandler = Box<
138    dyn FnOnce(&mut ServiceSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
139        + Send
140        + Sync,
141>;
142
143/// An expected call to a [`MockApplicationInstance`].
144#[derive(custom_debug_derive::Debug)]
145pub enum ExpectedCall {
146    /// An expected call to [`UserContract::instantiate`].
147    Instantiate(#[debug(skip)] InstantiateHandler),
148    /// An expected call to [`UserContract::execute_operation`].
149    ExecuteOperation(#[debug(skip)] ExecuteOperationHandler),
150    /// An expected call to [`UserContract::execute_message`].
151    ExecuteMessage(#[debug(skip)] ExecuteMessageHandler),
152    /// An expected call to [`UserContract::process_streams`].
153    ProcessStreams(#[debug(skip)] ProcessStreamHandler),
154    /// An expected call to [`UserContract::finalize`].
155    Finalize(#[debug(skip)] FinalizeHandler),
156    /// An expected call to [`UserService::handle_query`].
157    HandleQuery(#[debug(skip)] HandleQueryHandler),
158}
159
160impl Display for ExpectedCall {
161    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
162        let name = match self {
163            ExpectedCall::Instantiate(_) => "instantiate",
164            ExpectedCall::ExecuteOperation(_) => "execute_operation",
165            ExpectedCall::ExecuteMessage(_) => "execute_message",
166            ExpectedCall::ProcessStreams(_) => "process_streams",
167            ExpectedCall::Finalize(_) => "finalize",
168            ExpectedCall::HandleQuery(_) => "handle_query",
169        };
170
171        write!(formatter, "{name}")
172    }
173}
174
175impl ExpectedCall {
176    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
177    /// [`UserContract::instantiate`] implementation, which is handled by the provided `handler`.
178    pub fn instantiate(
179        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError>
180            + Send
181            + Sync
182            + 'static,
183    ) -> Self {
184        ExpectedCall::Instantiate(Box::new(handler))
185    }
186
187    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
188    /// [`UserContract::execute_operation`] implementation, which is handled by the provided
189    /// `handler`.
190    pub fn execute_operation(
191        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
192            + Send
193            + Sync
194            + 'static,
195    ) -> Self {
196        ExpectedCall::ExecuteOperation(Box::new(handler))
197    }
198
199    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
200    /// [`UserContract::execute_message`] implementation, which is handled by the provided
201    /// `handler`.
202    pub fn execute_message(
203        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<u8>) -> Result<(), ExecutionError>
204            + Send
205            + Sync
206            + 'static,
207    ) -> Self {
208        ExpectedCall::ExecuteMessage(Box::new(handler))
209    }
210
211    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
212    /// [`UserContract::process_streams`] implementation, which is handled by the provided
213    /// `handler`.
214    pub fn process_streams(
215        handler: impl FnOnce(&mut ContractSyncRuntimeHandle, Vec<StreamUpdate>) -> Result<(), ExecutionError>
216            + Send
217            + Sync
218            + 'static,
219    ) -> Self {
220        ExpectedCall::ProcessStreams(Box::new(handler))
221    }
222
223    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s [`UserContract::finalize`]
224    /// implementation, which is handled by the provided `handler`.
225    pub fn finalize(
226        handler: impl FnOnce(&mut ContractSyncRuntimeHandle) -> Result<(), ExecutionError>
227            + Send
228            + Sync
229            + 'static,
230    ) -> Self {
231        ExpectedCall::Finalize(Box::new(handler))
232    }
233
234    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s [`UserContract::finalize`]
235    /// implementation, which is handled by the default implementation which does nothing.
236    pub fn default_finalize() -> Self {
237        Self::finalize(|_| Ok(()))
238    }
239
240    /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s
241    /// [`UserService::handle_query`] implementation, which is handled by the provided `handler`.
242    pub fn handle_query(
243        handler: impl FnOnce(&mut ServiceSyncRuntimeHandle, Vec<u8>) -> Result<Vec<u8>, ExecutionError>
244            + Send
245            + Sync
246            + 'static,
247    ) -> Self {
248        ExpectedCall::HandleQuery(Box::new(handler))
249    }
250}
251
252impl UserContractModule for MockApplication {
253    fn instantiate(
254        &self,
255        runtime: ContractSyncRuntimeHandle,
256    ) -> Result<Box<dyn UserContract + 'static>, ExecutionError> {
257        Ok(Box::new(self.create_mock_instance(runtime)))
258    }
259}
260
261impl UserServiceModule for MockApplication {
262    fn instantiate(
263        &self,
264        runtime: ServiceSyncRuntimeHandle,
265    ) -> Result<Box<dyn UserService + 'static>, ExecutionError> {
266        Ok(Box::new(self.create_mock_instance(runtime)))
267    }
268}
269
270impl<Runtime> MockApplicationInstance<Runtime> {
271    /// Retrieves the next [`ExpectedCall`] in the queue.
272    fn next_expected_call(&mut self) -> Option<ExpectedCall> {
273        self.expected_calls
274            .lock()
275            .expect("Queue of expected calls was poisoned")
276            .pop_front()
277    }
278}
279
280impl UserContract for MockApplicationInstance<ContractSyncRuntimeHandle> {
281    fn instantiate(&mut self, argument: Vec<u8>) -> Result<(), ExecutionError> {
282        match self.next_expected_call() {
283            Some(ExpectedCall::Instantiate(handler)) => handler(&mut self.runtime, argument),
284            Some(unexpected_call) => panic!(
285                "Expected a call to `instantiate`, got a call to `{unexpected_call}` instead."
286            ),
287            None => panic!("Unexpected call to `instantiate`"),
288        }
289    }
290
291    fn execute_operation(&mut self, operation: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
292        match self.next_expected_call() {
293            Some(ExpectedCall::ExecuteOperation(handler)) => handler(&mut self.runtime, operation),
294            Some(unexpected_call) => panic!(
295                "Expected a call to `execute_operation`, got a call to `{unexpected_call}` instead."
296            ),
297            None => panic!("Unexpected call to `execute_operation`"),
298        }
299    }
300
301    fn execute_message(&mut self, message: Vec<u8>) -> Result<(), ExecutionError> {
302        match self.next_expected_call() {
303            Some(ExpectedCall::ExecuteMessage(handler)) => handler(&mut self.runtime, message),
304            Some(unexpected_call) => panic!(
305                "Expected a call to `execute_message`, got a call to `{unexpected_call}` instead."
306            ),
307            None => panic!("Unexpected call to `execute_message`"),
308        }
309    }
310
311    fn process_streams(&mut self, updates: Vec<StreamUpdate>) -> Result<(), ExecutionError> {
312        match self.next_expected_call() {
313            Some(ExpectedCall::ProcessStreams(handler)) => handler(&mut self.runtime, updates),
314            Some(unexpected_call) => panic!(
315                "Expected a call to `process_streams`, got a call to `{unexpected_call}` instead."
316            ),
317            None => panic!("Unexpected call to `process_streams`"),
318        }
319    }
320
321    fn finalize(&mut self) -> Result<(), ExecutionError> {
322        match self.next_expected_call() {
323            Some(ExpectedCall::Finalize(handler)) => handler(&mut self.runtime),
324            Some(unexpected_call) => {
325                panic!("Expected a call to `finalize`, got a call to `{unexpected_call}` instead.")
326            }
327            None => panic!("Unexpected call to `finalize`"),
328        }
329    }
330}
331
332impl UserService for MockApplicationInstance<ServiceSyncRuntimeHandle> {
333    fn handle_query(&mut self, query: Vec<u8>) -> Result<Vec<u8>, ExecutionError> {
334        match self.next_expected_call() {
335            Some(ExpectedCall::HandleQuery(handler)) => handler(&mut self.runtime, query),
336            Some(unexpected_call) => panic!(
337                "Expected a call to `handle_query`, got a call to `{unexpected_call}` instead."
338            ),
339            None => panic!("Unexpected call to `handle_query`"),
340        }
341    }
342}