#![cfg(with_tokio_multi_thread)]
use std::{
any::Any,
sync::{Arc, Mutex},
};
use futures::{channel::mpsc, StreamExt};
use linera_base::{crypto::CryptoHash, data_types::BlockHeight, identifiers::ApplicationId};
use linera_views::batch::Batch;
use super::{ApplicationStatus, SyncRuntimeHandle, SyncRuntimeInternal, WithContext};
use crate::{
execution_state_actor::ExecutionRequest,
runtime::{LoadedApplication, ResourceController, SyncRuntime},
test_utils::{create_dummy_user_application_description, dummy_chain_description},
ContractRuntime, UserContractInstance,
};
#[test_log::test(tokio::test)]
async fn test_dropping_sync_runtime_clears_loaded_applications() -> anyhow::Result<()> {
let (runtime, _receiver) = create_runtime();
let handle = SyncRuntimeHandle::from(runtime);
let weak_handle = Arc::downgrade(&handle.0);
let fake_application = create_fake_application_with_runtime(&handle);
handle
.0
.try_lock()
.expect("Failed to lock runtime")
.loaded_applications
.insert(create_dummy_application_id(), fake_application);
let runtime = SyncRuntime(Some(handle));
drop(runtime);
assert!(weak_handle.upgrade().is_none());
Ok(())
}
#[test_log::test(tokio::test)]
async fn test_into_inner_without_clearing_applications() {
let (runtime, _receiver) = create_runtime();
let handle = SyncRuntimeHandle::from(runtime);
let fake_application = create_fake_application_with_runtime(&handle);
handle
.0
.try_lock()
.expect("Failed to lock runtime")
.loaded_applications
.insert(create_dummy_application_id(), fake_application);
assert!(SyncRuntime(Some(handle)).into_inner().is_none());
}
#[test_log::test(tokio::test)]
async fn test_into_inner_after_clearing_applications() {
let (runtime, _receiver) = create_runtime();
let handle = SyncRuntimeHandle::from(runtime);
let weak_handle = Arc::downgrade(&handle.0);
let fake_application = create_fake_application_with_runtime(&handle);
{
let mut runtime = handle.0.try_lock().expect("Failed to lock runtime");
runtime
.loaded_applications
.insert(create_dummy_application_id(), fake_application);
runtime.loaded_applications.clear();
}
assert!(SyncRuntime(Some(handle)).into_inner().is_some());
assert!(weak_handle.upgrade().is_none());
}
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn test_write_batch() {
let (runtime, mut execution_state_receiver) = create_contract_runtime();
let mut runtime = SyncRuntimeHandle::from(runtime);
let mut batch = Batch::new();
let write_key = vec![1, 2, 3, 4, 5];
let write_data = vec![6, 7, 8, 9];
let delete_key = vec![10, 11, 12];
let delete_key_prefix = vec![13, 14, 15, 16, 17, 18];
let expected_bytes_count =
write_key.len() + write_data.len() + delete_key.len() + delete_key_prefix.len();
batch.put_key_value_bytes(write_key, write_data);
batch.delete_key(delete_key);
batch.delete_key_prefix(delete_key_prefix);
let expected_write_count = batch.operations.len();
let expected_application_id = runtime.inner().current_application().id;
let expected_batch = batch.clone();
tokio::spawn(async move {
let request = execution_state_receiver
.next()
.await
.expect("Missing expected request to write a batch");
let ExecutionRequest::WriteBatch {
id,
batch,
callback,
} = request
else {
panic!("Expected a `ExecutionRequest::WriteBatch` but got {request:?} instead");
};
assert_eq!(id, expected_application_id);
assert_eq!(batch, expected_batch);
callback
.send(())
.expect("Failed to notify that writing the batch finished");
});
runtime
.write_batch(batch)
.expect("Failed to write test batch");
assert_eq!(
runtime.inner().resource_controller.tracker.write_operations,
expected_write_count as u32
);
assert_eq!(
runtime.inner().resource_controller.tracker.bytes_written,
expected_bytes_count as u64
);
}
fn create_contract_runtime() -> (
SyncRuntimeInternal<UserContractInstance>,
mpsc::UnboundedReceiver<ExecutionRequest>,
) {
let (mut runtime, execution_state_receiver) = create_runtime();
runtime.push_application(create_dummy_application());
(runtime, execution_state_receiver)
}
fn create_runtime<Application: WithContext>() -> (
SyncRuntimeInternal<Application>,
mpsc::UnboundedReceiver<ExecutionRequest>,
)
where
Application::UserContext: Default,
{
let chain_id = dummy_chain_description(0).id();
let (execution_state_sender, execution_state_receiver) = mpsc::unbounded();
let resource_controller = ResourceController::default();
let runtime = SyncRuntimeInternal::new(
chain_id,
BlockHeight(0),
Some(0),
None,
execution_state_sender,
None,
None,
resource_controller,
Default::default(),
true,
);
(runtime, execution_state_receiver)
}
fn create_dummy_application() -> ApplicationStatus {
let (description, _, _) = create_dummy_user_application_description(0);
let id = From::from(&description);
ApplicationStatus {
caller_id: None,
id,
description,
signer: None,
}
}
fn create_dummy_application_id() -> ApplicationId {
ApplicationId::new(CryptoHash::test_hash("application description"))
}
fn create_fake_application_with_runtime(
runtime: &SyncRuntimeHandle<Arc<dyn Any + Send + Sync>>,
) -> LoadedApplication<Arc<dyn Any + Send + Sync>> {
let fake_instance: Arc<dyn Any + Send + Sync> = runtime.0.clone();
let (description, _, _) = create_dummy_user_application_description(0);
LoadedApplication {
instance: Arc::new(Mutex::new(fake_instance)),
description,
}
}