use std::collections::HashSet; use kvs::adapters::ram::*; use kvs::{Grasshopper, KVStore, StoreConfig}; const KEY_COLLISION_HASH: u16 = 58263; const KEY_COLLISIONS: [&str; 16] = [ "/bin/charlotte/big/great/year.jpg", "/bin/group/place/public.txt", "/bin/oliver/fact/year/place.txt", "/etc/able/woman/mia.mp4", "/etc/big/week.jpg", "/home/able/year.rar", "/home/early/group.jpg", "/isabella/time.mp4", "/sbin/young/eye/problem.xls", "/thing/charlotte.flv", "/tmp/amelia/emma/good.tar", "/usr/elijah/right.tar", "/usr/emma.rar", "/usr/olivia/different.jpg", "/var/aria/high.zip", "/var/life/time/oliver.tar", ]; #[test] fn test_collisions() { for key in KEY_COLLISIONS.iter() { let hash = Grasshopper::<{ tiny::BUCKETS }>::new(tiny::MAX_HOPS, 0, key.as_bytes()).hash(); assert_eq!(KEY_COLLISION_HASH, hash); } } mod tiny { use crate::*; pub const MAGIC: u32 = 0x796e6974; pub const STORE_SIZE: usize = 1024; pub const BUCKETS: usize = 32; pub const SLOTS: usize = 8; pub const MAX_HOPS: usize = 32; pub type Store = KVStore, BUCKETS, SLOTS>; pub fn create_store() -> Store { Store::open( MemoryAdapter::default(), StoreConfig::new(MAGIC, MAX_HOPS), true, ) .unwrap() } } #[test] fn test_create_store() { tiny::create_store(); } #[test] fn test_create_invalid_store() { type TooSmallStore = KVStore, { tiny::BUCKETS }, { tiny::SLOTS }>; let store = TooSmallStore::open( MemoryAdapter::default(), StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS), true, ); assert!(store.is_err()); if let Err(err) = store { assert_eq!(err, kvs::Error::AdapterError(())); } } #[test] fn test_reopen_store() { let adapter = tiny::create_store().close(); let store = tiny::Store::open( adapter, StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS), false, ); assert!(store.is_ok()); } #[test] fn test_reopen_store_with_invalid_magic() { let adapter = tiny::create_store().close(); let store = tiny::Store::open( adapter, StoreConfig::new(tiny::MAGIC + 1, tiny::MAX_HOPS), false, ); assert!(store.is_err()); if let Err(err) = store { assert_eq!(err, kvs::Error::StoreNotFound); } } #[test] fn test_reopen_store_with_invalid_nonce() { let adapter = tiny::create_store().close(); let store = tiny::Store::open( adapter, StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS).nonce(1), false, ); assert!(store.is_err()); if let Err(err) = store { assert_eq!(err, kvs::Error::InvalidNonce); } } #[test] fn test_reopen_store_with_invalid_buckets() { let adapter = tiny::create_store().close(); type WrongCapacityStore = KVStore, { tiny::BUCKETS * 2 }, { tiny::SLOTS }>; let store = WrongCapacityStore::open( adapter, StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS), false, ); assert!(store.is_err()); if let Err(err) = store { assert_eq!(err, kvs::Error::InvalidCapacity); } } #[test] fn test_insert() { let mut store = tiny::create_store(); let bucket = store.insert(b"foo", b"bar").unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); } #[test] fn test_list_keys() { let mut check = HashSet::new(); check.insert("foo"); check.insert("bar"); check.insert("baz"); assert_eq!(check.len(), 3); let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); store.insert(b"bar", b"barbaz").unwrap(); store.insert(b"baz", b"foobarbaz").unwrap(); for key_ref in store.keys() { let key = core::str::from_utf8(key_ref.key()).unwrap(); check.remove(key); } assert_eq!(check.len(), 0); } #[test] fn test_list_keys_with_prefix() { let mut check = HashSet::new(); check.insert("/root/foo"); check.insert("/root/bar"); check.insert("/root/baz"); check.insert("/etc/foo"); assert_eq!(check.len(), 4); let mut store = tiny::create_store(); store.insert(b"/root/foo", b"bar").unwrap(); store.insert(b"/root/bar", b"barbaz").unwrap(); store.insert(b"/root/baz", b"foobarbaz").unwrap(); store.insert(b"/etc/foo", b"baz").unwrap(); for key_ref in store.keys_with_prefix(b"/root") { let key = core::str::from_utf8(key_ref.key()).unwrap(); check.remove(key); } assert_eq!(check.len(), 1); } #[test] fn test_load() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_exists() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); assert_eq!(store.exists(b"foo").unwrap(), true); assert_eq!(store.exists(b"bar").unwrap(), false); } #[test] fn test_load_slice() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let val = store.load_slice(b"foo", &mut scratch).unwrap(); assert_eq!(val, b"bar"); let mut scratch = [0; 2]; let val = store.load_slice(b"foo", &mut scratch).unwrap(); assert_eq!(val, b"ba"); } #[test] fn test_load_str() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let val = store.load_str(b"foo", &mut scratch).unwrap(); assert_eq!(val, "bar"); let mut scratch = [0; 2]; let val = store.load_str(b"foo", &mut scratch).unwrap(); assert_eq!(val, "ba"); } #[test] fn test_load_rewrited() { let mut store = tiny::create_store(); store.insert(b"foo", b"Ba").unwrap(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_reopen() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let adapter = store.close(); let mut store = tiny::Store::open( adapter, StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS), false, ) .unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_rewrite() { let mut store = tiny::create_store(); store.insert(b"foo", b"Lorem").unwrap(); store.insert(b"foo", b"ipsum").unwrap(); store.insert(b"foo", b"dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua").unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.val_len(), 110); store.insert(b"foo", b"bar").unwrap(); let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_alloc() { let mut store = tiny::create_store(); store.alloc(b"foo", 9, None).unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 9); assert_eq!(bucket.record_len(), 12); assert_eq!(&scratch[..bucket.val_len()], &[0, 0, 0, 0, 0, 0, 0, 0, 0]); } #[test] fn test_alloc_fill() { let mut store = tiny::create_store(); store.alloc(b"foo", 9, Some(b'x')).unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 9); assert_eq!(bucket.record_len(), 12); assert_eq!(&scratch[..bucket.val_len()], b"xxxxxxxxx"); } #[test] fn test_patch() { let mut store = tiny::create_store(); store.insert(b"foo", b"Bar").unwrap(); let bucket = store.patch(b"foo", 0, b"b").unwrap(); assert_eq!(bucket.key_len(), 3); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(bucket.record_len(), 6); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_append() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"bar"); let bucket = store.append(b"foo", b" baz").unwrap(); assert_eq!(bucket.val_len(), 7); let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.val_len(), 7); assert_eq!(&scratch[..bucket.val_len()], b"bar baz"); } #[test] fn test_patch_with_hole() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let err = store.patch(b"foo", 4, b"baz").unwrap_err(); assert_eq!(err, kvs::Error::InvalidPatchOffset); } #[test] fn test_patch_no_space() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); store.insert(b"bar", b"baz").unwrap(); let err = store.append(b"foo", b"baz").unwrap_err(); assert_eq!(err, kvs::Error::ValueOverflow); } #[test] fn test_patch_resize() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); let mut scratch = [0; 16]; let bucket = store.patch(b"foo", 3, b" baz bar").unwrap(); assert_eq!(bucket.val_len(), 11); let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.val_len(), 11); assert_eq!(bucket.record_len(), 14); assert_eq!(&scratch[..bucket.val_len()], b"bar baz bar"); } #[test] fn test_reset() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); store.reset().unwrap(); let mut scratch = [0; 16]; let err = store.load(b"foo", &mut scratch).unwrap_err(); assert_eq!(err, kvs::Error::KeyNotFound); } #[test] fn test_remove() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); store.remove(b"foo").unwrap(); let mut scratch = [0; 16]; let err = store.load(b"foo", &mut scratch).unwrap_err(); assert_eq!(err, kvs::Error::KeyNotFound); store.insert(b"foo", b"bar").unwrap(); let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_erase() { let mut store = tiny::create_store(); store.insert(b"foo", b"bar").unwrap(); store.erase(b"foo", 0).unwrap(); let mut scratch = [0; 16]; let err = store.load(b"foo", &mut scratch).unwrap_err(); assert_eq!(err, kvs::Error::KeyNotFound); store.insert(b"foo", b"bar").unwrap(); let bucket = store.load(b"foo", &mut scratch).unwrap(); assert_eq!(bucket.key_len(), 3); assert_eq!(bucket.val_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_hash_collision() { let mut store = tiny::create_store(); store.insert(KEY_COLLISIONS[0].as_bytes(), b"foo").unwrap(); store.insert(KEY_COLLISIONS[1].as_bytes(), b"bar").unwrap(); let mut scratch = [0; 16]; let bucket = store .load(KEY_COLLISIONS[0].as_bytes(), &mut scratch) .unwrap(); assert_eq!(bucket.val_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"foo"); let bucket = store .load(KEY_COLLISIONS[1].as_bytes(), &mut scratch) .unwrap(); assert_eq!(bucket.val_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_hash_collision_broken_chain() { let mut store = tiny::create_store(); store.insert(KEY_COLLISIONS[0].as_bytes(), b"foo").unwrap(); store.insert(KEY_COLLISIONS[1].as_bytes(), b"bar").unwrap(); store.remove(KEY_COLLISIONS[0].as_bytes()).unwrap(); let mut scratch = [0; 16]; let err = store .load(KEY_COLLISIONS[0].as_bytes(), &mut scratch) .unwrap_err(); assert_eq!(err, kvs::Error::KeyNotFound); let bucket = store .load(KEY_COLLISIONS[1].as_bytes(), &mut scratch) .unwrap(); assert_eq!(bucket.val_len(), 3); assert_eq!(&scratch[..bucket.val_len()], b"bar"); } #[test] fn test_hash_collisions() { let mut store = tiny::create_store(); for (idx, key) in KEY_COLLISIONS.iter().enumerate() { store.insert(key.as_bytes(), &[idx as u8]).unwrap(); } let mut scratch = [0; 16]; for (idx, key) in KEY_COLLISIONS.iter().enumerate() { let bucket = store.load(key.as_bytes(), &mut scratch).unwrap(); assert_eq!(bucket.val_len(), 1); assert_eq!(&scratch[..bucket.val_len()], &[idx as u8]); } } #[test] fn test_compatibility() { let data = include_bytes!("./tiny.db"); let mut store = tiny::Store::open( MemoryAdapter::new(*data), StoreConfig::new(tiny::MAGIC, tiny::MAX_HOPS).nonce(34), false, ) .unwrap(); let mut scratch = [0; 64]; for key in KEY_COLLISIONS.iter() { let bucket = store.load(key.as_bytes(), &mut scratch).unwrap(); assert_eq!(bucket.val_len(), key.len()); assert_eq!(&scratch[..bucket.val_len()], key.as_bytes()); } }