newstore: HashMap-based in-memory ReadStore / WriteStore implementations for testing

Summary:
Introduce `HashMapStore`, a HashMap-based, generic, in-memory `ReadStore` / `WriteStore` implementation for testing purposes (and perhaps other uses in the future).

Introduce a minimal `KeyedValue` trait so that `HashMapStore` can determine the key to be associated with a written value in a generic way.

Reviewed By: kulshrax

Differential Revision: D26859235

fbshipit-source-id: 66032f072148796bde7a3a176fb2bb6707b95287
This commit is contained in:
Meyer Jacobs 2021-03-22 19:35:11 -07:00 committed by Facebook GitHub Bot
parent b51e7a510f
commit 6d20cd3b1e
3 changed files with 148 additions and 2 deletions

View File

@ -30,8 +30,8 @@ use crate::{
indexedlogutil::{Store, StoreOpenOptions},
localstore::{ExtStoredPolicy, LocalStore},
newstore::{
FetchError, FetchStream, KeyStream, ReadStore, WriteError, WriteResults, WriteStore,
WriteStream,
FetchError, FetchStream, KeyStream, KeyedValue, ReadStore, WriteError, WriteResults,
WriteStore, WriteStream,
},
repack::ToKeys,
sliceext::SliceExt,
@ -252,6 +252,14 @@ impl IndexedLogHgIdDataStore {
}
}
impl KeyedValue for Entry {
type Key = Key;
fn key(&self) -> Self::Key {
self.key.clone()
}
}
impl std::convert::From<TreeEntry> for Entry {
fn from(v: TreeEntry) -> Self {
Entry::new(

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use std::{collections::HashMap, fmt, hash::Hash, sync::Arc};
use async_trait::async_trait;
use futures::{lock::Mutex, StreamExt};
use crate::newstore::{
FetchError, FetchStream, KeyStream, ReadStore, WriteResults, WriteStore, WriteStream,
};
pub struct HashMapStore<K, V> {
store: Mutex<HashMap<K, V>>,
}
impl<K, V> HashMapStore<K, V> {
pub fn new() -> Self {
HashMapStore {
store: Mutex::new(HashMap::new()),
}
}
}
/// A value type which can return it's key.
///
/// Currently only used for testing, but we'll likely want to have a `StoreValue` trait
/// for other purposes in the future. We'll also probably want to lift the associated type
/// to a generic to support values which can be keyed with multiple key types.
pub trait KeyedValue {
type Key;
fn key(&self) -> Self::Key;
}
#[async_trait]
impl<K, V> ReadStore<K, V> for HashMapStore<K, V>
where
K: fmt::Display + fmt::Debug + std::cmp::Eq + Hash + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
async fn fetch_stream(self: Arc<Self>, keys: KeyStream<K>) -> FetchStream<K, V> {
Box::pin(keys.then(move |key| {
let self_ = self.clone();
async move {
self_
.store
.lock()
.await
.get(&key)
.cloned()
.ok_or_else(|| FetchError::not_found(key))
}
}))
}
}
#[async_trait]
impl<K, V> WriteStore<K, V> for HashMapStore<K, V>
where
K: Clone + fmt::Display + fmt::Debug + std::cmp::Eq + Hash + Send + Sync + 'static,
V: KeyedValue<Key = K> + Send + Sync + 'static,
{
async fn write_stream(self: Arc<Self>, values: WriteStream<V>) -> WriteResults<K> {
Box::pin(values.then(move |value| {
let self_ = self.clone();
async move {
let key = value.key();
self_.store.lock().await.insert(key.clone(), value);
Ok(key)
}
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::stream;
use minibytes::Bytes;
use async_runtime::{block_on_future as block_on, stream_to_iter as block_on_stream};
use types::testutil::*;
use crate::{
datastore::Metadata,
indexedlogdatastore::Entry,
newstore::{ReadStore, WriteStore},
};
#[test]
fn test_write_read() {
let entry_key = key("a", "1");
let content = Bytes::from(&[1, 2, 3, 4][..]);
let metadata = Metadata::default();
let entry = Entry::new(entry_key.clone(), content, metadata.clone());
let entries = vec![entry];
let teststore = Arc::new(HashMapStore::new());
// Write test data
let written: Vec<_> = block_on_stream(block_on(
teststore
.clone()
.write_stream(Box::pin(stream::iter(entries.clone()))),
))
.collect();
assert_eq!(
written
.into_iter()
.map(|r| r.expect("failed to write to test write store"))
.collect::<Vec<_>>(),
vec![entry_key.clone()]
);
// Read, also using legacy wrapper
let fetched: Vec<_> = block_on_stream(block_on(
teststore.fetch_stream(Box::pin(stream::iter(vec![entry_key]))),
))
.collect();
assert_eq!(
fetched
.into_iter()
.map(|r| r.expect("failed to fetch from test read store"))
.collect::<Vec<_>>(),
entries
);
}
}

View File

@ -18,11 +18,13 @@ use thiserror::Error;
pub use self::{
edenapi::EdenApiAdapter,
fallback::{Fallback, FallbackCache},
inmemory::{HashMapStore, KeyedValue},
legacy::LegacyDatastore,
};
pub mod edenapi;
pub mod fallback;
pub mod inmemory;
pub mod legacy;
/// A pinned, boxed stream of keys to fetch.