#![cfg(test)]
use std::sync::{Mutex, MutexGuard};
use lazy_static::lazy_static;
use tempfile::{TempDir, tempdir};
use url::Url;
use super::{Ident, KeyValueStore};
macro_rules! testfns {
(
$(
fn $name:ident($harness:ident: impl Harness) $body:block
)*
) => {
mod testfns {
use super::*;
$(
pub fn $name($harness: impl Harness) $body
)*
}
mod memory {
use super::*;
$(
#[test]
fn $name() {
super::testfns::$name(MemoryHarness::new());
}
)*
}
mod disk {
use super::*;
$(
#[test]
fn $name() {
super::testfns::$name(DiskHarness::new());
}
)*
}
}
}
const NAMESPACE: &Ident = Ident::make("namespace");
const SCOPE: &Ident = Ident::make("scope");
const SCOPE_2: &Ident= Ident::make("other_scope");
const KEY: &Ident = Ident::make("key");
const KEY_2: &Ident = Ident::make("other_key");
const CONTENT: u32 = 42;
const CONTENT_2: u32 = 43;
const CONTENT_3: u32 = 44;
const CONTENT_4: u32 = 45;
testfns! {
fn store_get(harness: impl Harness) {
let store = harness.store(NAMESPACE);
store.store(None, KEY, &CONTENT).unwrap();
assert!(store.has(None, KEY).unwrap());
assert_eq!(store.get(None, KEY).unwrap(), Some(CONTENT));
}
fn store_new(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(store.store_new(None, KEY, &CONTENT).is_ok());
assert!(store.store_new(None, KEY, &CONTENT).is_err());
}
fn store_scoped(harness: impl Harness) {
let store = harness.store(NAMESPACE);
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
assert!(store.has(Some(SCOPE), KEY).unwrap());
assert_eq!(store.get(Some(SCOPE), KEY).unwrap(), Some(CONTENT));
assert!(store.has_scope(SCOPE).unwrap());
store.store(None, KEY, &CONTENT_2).unwrap();
assert!(store.has(None, KEY).unwrap());
assert_eq!(store.get(None, KEY).unwrap(), Some(CONTENT_2));
assert!(store.has(Some(SCOPE), KEY).unwrap());
assert_eq!(store.get(Some(SCOPE), KEY).unwrap(), Some(CONTENT));
}
fn get(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(store.get::<String>(None, KEY).unwrap().is_none());
store.store(None, KEY, &CONTENT).unwrap();
assert_eq!(store.get(None, KEY).unwrap(), Some(CONTENT));
}
fn has(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(!store.has(None, KEY).unwrap());
store.store(None, KEY, &CONTENT).unwrap();
assert!(store.has(None, KEY).unwrap());
}
fn drop_global_key(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(store.drop_key(None, KEY).is_err());
store.store(None, KEY, &CONTENT).unwrap();
assert!(store.has(None, KEY).unwrap());
store.drop_key(None, KEY).unwrap();
assert!(!store.has(None, KEY).unwrap());
}
fn drop_scoped_key(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(store.drop_key(Some(SCOPE), KEY).is_err());
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
assert!(store.has(Some(SCOPE), KEY).unwrap());
store.drop_key(Some(SCOPE), KEY).unwrap();
assert!(!store.has(Some(SCOPE), KEY).unwrap());
assert!(!store.has_scope(SCOPE).unwrap());
}
fn drop_scope(harness: impl Harness) {
let store = harness.store(NAMESPACE);
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
store.store(Some(SCOPE_2), KEY_2, &CONTENT_2).unwrap();
assert!(store.has_scope(SCOPE).unwrap());
assert!(store.has_scope(SCOPE_2).unwrap());
assert!(store.has(Some(SCOPE), KEY).unwrap());
assert!(store.has(Some(SCOPE_2), KEY_2).unwrap());
store.drop_scope(SCOPE).unwrap();
assert!(!store.has_scope(SCOPE).unwrap());
assert!(store.has_scope(SCOPE_2).unwrap());
assert!(!store.has(Some(SCOPE), KEY).unwrap());
assert!(store.has(Some(SCOPE_2), KEY_2).unwrap());
}
fn wipe(harness: impl Harness) {
let store = harness.store(NAMESPACE);
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
assert!(store.has_scope(SCOPE).unwrap());
assert!(store.has(Some(SCOPE), KEY).unwrap());
store.wipe().unwrap();
assert!(!store.has_scope(SCOPE).unwrap());
assert!(!store.has(Some(SCOPE), KEY).unwrap());
assert!(store.keys(None, "").unwrap().is_empty());
assert!(store.keys(Some(SCOPE), "").unwrap().is_empty());
}
fn scopes(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(store.scopes().unwrap().is_empty());
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
assert_eq!(store.scopes().unwrap(), [SCOPE.into()]);
store.store(Some(SCOPE_2), KEY_2, &CONTENT_2).unwrap();
let mut scopes = store.scopes().unwrap();
scopes.sort();
let mut expected = vec![SCOPE.into(), SCOPE_2.into()];
expected.sort();
assert_eq!(scopes, expected);
store.drop_scope(SCOPE_2).unwrap();
assert_eq!(store.scopes().unwrap(), [SCOPE.into()]);
store.drop_scope(SCOPE).unwrap();
assert!(store.scopes().unwrap().is_empty());
}
fn has_scope(harness: impl Harness) {
let store = harness.store(NAMESPACE);
assert!(!store.has_scope(SCOPE).unwrap());
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
assert!(store.has_scope(SCOPE).unwrap());
store.drop_key(Some(SCOPE), KEY).unwrap();
assert!(!store.has_scope(SCOPE).unwrap());
}
fn keys(harness: impl Harness) {
let store = harness.store(NAMESPACE);
const KEY_ID: &Ident = Ident::make("command--id");
const KEY_LS: &Ident = Ident::make("command--ls");
const KEY_OTHER: &Ident = Ident::make("other");
store.store(Some(SCOPE), KEY_ID, &CONTENT).unwrap();
store.store(Some(SCOPE), KEY_LS, &CONTENT_2).unwrap();
store.store(None, KEY_OTHER, &CONTENT_3).unwrap();
store.store(None, KEY_ID, &CONTENT_4).unwrap();
let mut keys = store.keys(Some(SCOPE), "command--").unwrap();
keys.sort();
let mut expected = vec![KEY_ID.into(), KEY_LS.into()];
expected.sort();
assert_eq!(keys, expected);
assert_eq!(
store.keys(Some(SCOPE), KEY_LS.as_str()).unwrap(),
[KEY_LS.into()]
);
assert_eq!(store.keys(Some(SCOPE), KEY_OTHER.as_str()).unwrap(), []);
assert_eq!(
store.keys(None, KEY_OTHER.as_str()).unwrap(),
[KEY_OTHER.into()]
);
let mut keys = store.keys(Some(SCOPE), "").unwrap();
keys.sort();
let mut expected = vec![KEY_ID.into(), KEY_LS.into()];
expected.sort();
assert_eq!(keys, expected);
}
fn move_value(harness: impl Harness) {
let store = harness.store(NAMESPACE);
store.store(Some(SCOPE), KEY, &CONTENT).unwrap();
store.store(Some(SCOPE), KEY_2, &CONTENT_2).unwrap();
assert!(store.has_scope(SCOPE).unwrap());
assert!(!store.has_scope(SCOPE_2).unwrap());
assert_eq!(store.get(Some(SCOPE), KEY).unwrap(), Some(CONTENT));
assert!(!store.has(Some(SCOPE_2), KEY).unwrap());
assert_eq!(store.get(Some(SCOPE), KEY_2).unwrap(), Some(CONTENT_2));
assert!(!store.has(Some(SCOPE_2), KEY_2).unwrap());
store.execute(None, |store| {
store.move_value(Some(SCOPE), KEY, Some(SCOPE_2), KEY)
}).unwrap();
assert!(store.has_scope(SCOPE).unwrap());
assert!(store.has_scope(SCOPE_2).unwrap());
assert!(!store.has(Some(SCOPE), KEY).unwrap());
assert_eq!(store.get(Some(SCOPE_2), KEY).unwrap(), Some(CONTENT));
assert_eq!(store.get(Some(SCOPE), KEY_2).unwrap(), Some(CONTENT_2));
assert!(!store.has(Some(SCOPE_2), KEY_2).unwrap());
store.execute(None, |store| {
store.move_value(Some(SCOPE), KEY_2, Some(SCOPE_2), KEY_2)
}).unwrap();
assert!(!store.has_scope(SCOPE).unwrap());
assert!(store.has_scope(SCOPE_2).unwrap());
assert!(!store.has(Some(SCOPE), KEY).unwrap());
assert_eq!(store.get(Some(SCOPE_2), KEY).unwrap(), Some(CONTENT));
assert!(!store.has(Some(SCOPE), KEY_2).unwrap());
assert_eq!(store.get(Some(SCOPE_2), KEY_2).unwrap(), Some(CONTENT_2));
}
}
trait Harness {
#[allow(dead_code)]
fn url(&self) -> Url;
fn store(&self, namespace: &Ident) -> KeyValueStore;
}
struct MemoryHarness<'a> {
_guard: MutexGuard<'a, ()>,
}
lazy_static! {
static ref MEMORY_LOCK: Mutex<()> = Mutex::new(());
}
impl<'a> MemoryHarness<'a> {
fn new() -> Self {
loop {
let _guard = match MEMORY_LOCK.lock() {
Ok(guard) => guard,
Err(_) => {
MEMORY_LOCK.clear_poison();
continue;
}
};
super::backends::memory::Store::wipe_all();
return Self { _guard }
}
}
}
impl<'a> Harness for MemoryHarness<'a> {
fn url(&self) -> Url {
Url::parse("memory:").unwrap()
}
fn store(&self, namespace: &Ident) -> KeyValueStore {
KeyValueStore::create(
&Url::parse("memory:").unwrap(),
namespace,
).unwrap()
}
}
struct DiskHarness {
_dir: TempDir,
url: Url,
}
impl DiskHarness {
fn new() -> Self {
let _dir = tempdir().unwrap();
let url = format!("local://{}", _dir.path().display());
let url = Url::parse(&url).unwrap();
Self { _dir, url }
}
}
impl Harness for DiskHarness {
fn url(&self) -> Url {
self.url.clone()
}
fn store(&self, namespace: &Ident) -> KeyValueStore {
KeyValueStore::create(&self.url, namespace).unwrap()
}
}