add tests and mocks

This commit is contained in:
mpostma 2021-03-23 16:19:01 +01:00
parent 3cc3637e2d
commit 46293546f3
No known key found for this signature in database
GPG key ID: CBC8A7C1D7A28C3A
9 changed files with 273 additions and 23 deletions

View file

@ -23,6 +23,9 @@ use actor::IndexActor;
pub use handle_impl::IndexActorHandleImpl;
#[cfg(test)]
use mockall::automock;
pub type Result<T> = std::result::Result<T, IndexError>;
type UpdateResult = std::result::Result<Processed<UpdateMeta, UResult>, Failed<UpdateMeta, String>>;
@ -68,7 +71,8 @@ pub enum IndexError {
#[async_trait::async_trait]
pub trait IndexActorHandle: Sync + Send + Clone {
#[cfg_attr(test, automock)]
pub trait IndexActorHandle {
async fn create_index(&self, uuid: Uuid, primary_key: Option<String>) -> Result<IndexMeta>;
async fn update(
&self,

View file

@ -7,9 +7,9 @@ use tokio::fs;
use tokio::task::spawn_blocking;
use tokio::time::sleep;
use crate::helpers::compression;
use super::update_actor::UpdateActorHandle;
use super::uuid_resolver::UuidResolverHandle;
use crate::helpers::compression;
#[allow(dead_code)]
pub struct SnapshotService<U, R> {
@ -22,7 +22,7 @@ pub struct SnapshotService<U, R> {
impl<U, R> SnapshotService<U, R>
where
U: UpdateActorHandle,
R: UuidResolverHandle
R: UuidResolverHandle,
{
pub fn new(
uuid_resolver_handle: R,
@ -39,7 +39,6 @@ where
}
pub async fn run(self) {
loop {
sleep(self.snapshot_period).await;
if let Err(e) = self.perform_snapshot().await {
@ -49,7 +48,7 @@ where
}
async fn perform_snapshot(&self) -> anyhow::Result<()> {
if self.snapshot_path.file_name().is_none() {
if !self.snapshot_path.is_file() {
bail!("invalid snapshot file path");
}
@ -58,15 +57,21 @@ where
fs::create_dir_all(&temp_snapshot_path).await?;
let uuids = self.uuid_resolver_handle.snapshot(temp_snapshot_path.clone()).await?;
let uuids = self
.uuid_resolver_handle
.snapshot(temp_snapshot_path.clone())
.await?;
if uuids.is_empty() {
return Ok(())
return Ok(());
}
let tasks = uuids
.iter()
.map(|&uuid| self.update_handle.snapshot(uuid, temp_snapshot_path.clone()))
.map(|&uuid| {
self.update_handle
.snapshot(uuid, temp_snapshot_path.clone())
})
.collect::<Vec<_>>();
futures::future::try_join_all(tasks).await?;
@ -75,7 +80,10 @@ where
let temp_snapshot_file_clone = temp_snapshot_file.clone();
let temp_snapshot_path_clone = temp_snapshot_path.clone();
spawn_blocking(move || compression::to_tar_gz(temp_snapshot_path_clone, temp_snapshot_file_clone)).await??;
spawn_blocking(move || {
compression::to_tar_gz(temp_snapshot_path_clone, temp_snapshot_file_clone)
})
.await??;
fs::rename(temp_snapshot_file, &self.snapshot_path).await?;
@ -84,3 +92,137 @@ where
Ok(())
}
}
#[cfg(test)]
mod test {
use futures::future::{ok, err};
use rand::Rng;
use tokio::time::timeout;
use uuid::Uuid;
use super::*;
use crate::index_controller::update_actor::{MockUpdateActorHandle, UpdateError};
use crate::index_controller::uuid_resolver::{MockUuidResolverHandle, UuidError};
#[actix_rt::test]
async fn test_normal() {
let mut rng = rand::thread_rng();
let uuids_num = rng.gen_range(5, 10);
let uuids = (0..uuids_num).map(|_| Uuid::new_v4()).collect::<Vec<_>>();
let mut uuid_resolver = MockUuidResolverHandle::new();
let uuids_clone = uuids.clone();
uuid_resolver
.expect_snapshot()
.times(1)
.returning(move |_| Box::pin(ok(uuids_clone.clone())));
let mut update_handle = MockUpdateActorHandle::new();
let uuids_clone = uuids.clone();
update_handle
.expect_snapshot()
.withf(move |uuid, _path| uuids_clone.contains(uuid))
.times(uuids_num)
.returning(move |_, _| Box::pin(ok(())));
let snapshot_path = tempfile::NamedTempFile::new_in(".").unwrap();
let snapshot_service = SnapshotService::new(
uuid_resolver,
update_handle,
Duration::from_millis(100),
snapshot_path.path().to_owned(),
);
snapshot_service.perform_snapshot().await.unwrap();
}
#[actix_rt::test]
async fn bad_file_name() {
let uuid_resolver = MockUuidResolverHandle::new();
let update_handle = MockUpdateActorHandle::new();
let snapshot_service = SnapshotService::new(
uuid_resolver,
update_handle,
Duration::from_millis(100),
"directory/".into(),
);
assert!(snapshot_service.perform_snapshot().await.is_err());
}
#[actix_rt::test]
async fn error_performing_uuid_snapshot() {
let mut uuid_resolver = MockUuidResolverHandle::new();
uuid_resolver
.expect_snapshot()
.times(1)
// abitrary error
.returning(|_| Box::pin(err(UuidError::NameAlreadyExist)));
let update_handle = MockUpdateActorHandle::new();
let snapshot_path = tempfile::NamedTempFile::new_in(".").unwrap();
let snapshot_service = SnapshotService::new(
uuid_resolver,
update_handle,
Duration::from_millis(100),
snapshot_path.path().to_owned(),
);
assert!(snapshot_service.perform_snapshot().await.is_err());
// Nothing was written to the file
assert_eq!(snapshot_path.as_file().metadata().unwrap().len(), 0);
}
#[actix_rt::test]
async fn error_performing_index_snapshot() {
let uuid = Uuid::new_v4();
let mut uuid_resolver = MockUuidResolverHandle::new();
uuid_resolver
.expect_snapshot()
.times(1)
.returning(move |_| Box::pin(ok(vec![uuid])));
let mut update_handle = MockUpdateActorHandle::new();
update_handle
.expect_snapshot()
// abitrary error
.returning(|_, _| Box::pin(err(UpdateError::UnexistingUpdate(0))));
let snapshot_path = tempfile::NamedTempFile::new_in(".").unwrap();
let snapshot_service = SnapshotService::new(
uuid_resolver,
update_handle,
Duration::from_millis(100),
snapshot_path.path().to_owned(),
);
assert!(snapshot_service.perform_snapshot().await.is_err());
// Nothing was written to the file
assert_eq!(snapshot_path.as_file().metadata().unwrap().len(), 0);
}
#[actix_rt::test]
async fn test_loop() {
let mut uuid_resolver = MockUuidResolverHandle::new();
uuid_resolver
.expect_snapshot()
// we expect the funtion to be called between 2 and 3 time in the given interval.
.times(2..4)
// abitrary error, to short-circuit the function
.returning(move |_| Box::pin(err(UuidError::NameAlreadyExist)));
let update_handle = MockUpdateActorHandle::new();
let snapshot_path = tempfile::NamedTempFile::new_in(".").unwrap();
let snapshot_service = SnapshotService::new(
uuid_resolver,
update_handle,
Duration::from_millis(100),
snapshot_path.path().to_owned(),
);
let _ = timeout(Duration::from_millis(300), snapshot_service.run()).await;
}
}

View file

@ -23,7 +23,7 @@ impl<D, S, I> UpdateActor<D, S, I>
where
D: AsRef<[u8]> + Sized + 'static,
S: UpdateStoreStore,
I: IndexActorHandle + 'static,
I: IndexActorHandle + Clone + Send + Sync + 'static,
{
pub fn new(
store: S,

View file

@ -24,7 +24,7 @@ where
update_store_size: usize,
) -> anyhow::Result<Self>
where
I: IndexActorHandle + 'static,
I: IndexActorHandle + Clone + Send + Sync + 'static,
{
let path = path.as_ref().to_owned().join("updates");
let (sender, receiver) = mpsc::channel(100);

View file

@ -23,6 +23,9 @@ pub type Result<T> = std::result::Result<T, UpdateError>;
type UpdateStore = update_store::UpdateStore<UpdateMeta, UpdateResult, String>;
type PayloadData<D> = std::result::Result<D, Box<dyn std::error::Error + Sync + Send + 'static>>;
#[cfg(test)]
use mockall::automock;
#[derive(Debug, Error)]
pub enum UpdateError {
#[error("error with update: {0}")]
@ -34,6 +37,7 @@ pub enum UpdateError {
}
#[async_trait::async_trait]
#[cfg_attr(test, automock(type Data=Vec<u8>;))]
pub trait UpdateActorHandle {
type Data: AsRef<[u8]> + Sized + 'static + Sync + Send;

View file

@ -1,14 +1,14 @@
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use uuid::Uuid;
use tokio::sync::RwLock;
use tokio::fs;
use tokio::sync::RwLock;
use uuid::Uuid;
use super::{Result, UpdateError, UpdateStore};
use crate::index_controller::IndexActorHandle;
use super::{UpdateStore, UpdateError, Result};
#[async_trait::async_trait]
pub trait UpdateStoreStore {
@ -25,11 +25,7 @@ pub struct MapUpdateStoreStore<I> {
}
impl<I: IndexActorHandle> MapUpdateStoreStore<I> {
pub fn new(
index_handle: I,
path: impl AsRef<Path>,
update_store_size: usize,
) -> Self {
pub fn new(index_handle: I, path: impl AsRef<Path>, update_store_size: usize) -> Self {
let db = Arc::new(RwLock::new(HashMap::new()));
let path = path.as_ref().to_owned();
Self {
@ -42,7 +38,10 @@ impl<I: IndexActorHandle> MapUpdateStoreStore<I> {
}
#[async_trait::async_trait]
impl<I: IndexActorHandle + 'static> UpdateStoreStore for MapUpdateStoreStore<I> {
impl<I> UpdateStoreStore for MapUpdateStoreStore<I>
where
I: IndexActorHandle + Clone + Send + Sync + 'static,
{
async fn get_or_create(&self, uuid: Uuid) -> Result<Arc<UpdateStore>> {
match self.db.write().await.entry(uuid) {
Entry::Vacant(e) => {

View file

@ -12,6 +12,9 @@ use actor::UuidResolverActor;
use message::UuidResolveMsg;
use store::{HeedUuidStore, UuidStore};
#[cfg(test)]
use mockall::automock;
pub use handle_impl::UuidResolverHandleImpl;
const UUID_STORE_SIZE: usize = 1_073_741_824; //1GiB
@ -19,6 +22,7 @@ const UUID_STORE_SIZE: usize = 1_073_741_824; //1GiB
pub type Result<T> = std::result::Result<T, UuidError>;
#[async_trait::async_trait]
#[cfg_attr(test, automock)]
pub trait UuidResolverHandle {
async fn resolve(&self, name: String) -> anyhow::Result<Uuid>;
async fn get_or_create(&self, name: String) -> Result<Uuid>;