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