From 68a0f7ce37dd7c9387f52ea63038e83bfb0d1342 Mon Sep 17 00:00:00 2001 From: Lachlan Deakin Date: Tue, 26 Dec 2023 10:12:18 +1100 Subject: [PATCH] Add store testing utility functions and fix `MemoryStore::get_partial_values_key` --- CHANGELOG.md | 4 + src/storage/store/store_async.rs | 144 ++++++++++++++++++ src/storage/store/store_async/object_store.rs | 137 +---------------- src/storage/store/store_sync.rs | 129 ++++++++++++++++ .../store/store_sync/filesystem_store.rs | 67 +------- src/storage/store/store_sync/memory_store.rs | 103 +------------ 6 files changed, 297 insertions(+), 287 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074bfa6e..a0595d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Breaking** Removes the explicit `object_store`-based stores (e.g. `AsyncAmazonS3Store`, `AsyncHTTPStore`) - **Breaking** Removes the `object_store_impl` macro - **Breaking** Removes the `s3`, `gcp`, and `azure` crate features + - Add store testing utility functions for unified store testing + +### Fixed + - Fixed `MemoryStore::get_partial_values_key` if given an invalid byte range, now returns `InvalidByteRangeError` instead of panicking ## [0.7.3] - 2023-12-22 diff --git a/src/storage/store/store_async.rs b/src/storage/store/store_async.rs index df76dfbb..c7fb9e79 100644 --- a/src/storage/store/store_async.rs +++ b/src/storage/store/store_async.rs @@ -1,2 +1,146 @@ #[cfg(feature = "object_store")] pub mod object_store; + +#[cfg(test)] +mod test_util { + use std::error::Error; + + use crate::{ + byte_range::ByteRange, + storage::{ + AsyncListableStorageTraits, AsyncReadableStorageTraits, AsyncWritableStorageTraits, + StoreKeyRange, StoreKeyStartValue, StorePrefix, + }, + }; + + /// Create a store with the following data + /// - a/ + /// - b [0, 1, 2] + /// - c [0] + /// - d/ + /// - e + /// - f/ + /// - g + /// - h + /// - i/ + /// - j/ + /// - k [0, 1] + pub async fn store_write( + store: &T, + ) -> Result<(), Box> { + store.erase_prefix(&StorePrefix::root()).await?; + + store.set(&"a/b".try_into()?, &[0, 0, 0]).await?; + store + .set_partial_values(&[StoreKeyStartValue::new("a/b".try_into()?, 1, &[1, 2])]) + .await?; + + store.set(&"a/c".try_into()?, &[0]).await?; + store.set(&"a/d/e".try_into()?, &[]).await?; + store.set(&"a/f/g".try_into()?, &[]).await?; + store.set(&"a/f/h".try_into()?, &[]).await?; + store.set(&"i/j/k".try_into()?, &[0, 1]).await?; + + store.set(&"erase".try_into()?, &[]).await?; + store.erase(&"erase".try_into()?).await?; + + store.set(&"erase_values_0".try_into()?, &[]).await?; + store.set(&"erase_values_1".try_into()?, &[]).await?; + store + .erase_values(&["erase_values_0".try_into()?, "erase_values_1".try_into()?]) + .await?; + + store.set(&"erase_prefix/0".try_into()?, &[]).await?; + store.set(&"erase_prefix/1".try_into()?, &[]).await?; + store.erase_prefix(&"erase_prefix/".try_into()?).await?; + + Ok(()) + } + + pub async fn store_read( + store: &T, + ) -> Result<(), Box> { + assert!(store.get(&"notfound".try_into()?).await?.is_none()); + assert!(store.size_key(&"notfound".try_into()?).await?.is_none()); + assert_eq!(store.get(&"a/b".try_into()?).await?, Some(vec![0, 1, 2])); + assert_eq!(store.size_key(&"a/b".try_into()?).await?, Some(3)); + assert_eq!(store.size_key(&"a/c".try_into()?).await?, Some(1)); + assert_eq!(store.size_key(&"i/j/k".try_into()?).await?, Some(2)); + assert_eq!( + store + .get_partial_values_key( + &"a/b".try_into()?, + &[ + ByteRange::FromStart(1, Some(1)), + ByteRange::FromEnd(0, Some(1)) + ] + ) + .await?, + Some(vec![vec![1], vec![2]]) + ); + assert_eq!( + store + .get_partial_values(&[ + StoreKeyRange::new("a/b".try_into()?, ByteRange::FromStart(1, None)), + StoreKeyRange::new("a/b".try_into()?, ByteRange::FromEnd(1, Some(2))), + StoreKeyRange::new("i/j/k".try_into()?, ByteRange::FromStart(1, Some(1))), + ]) + .await?, + vec![Some(vec![1, 2]), Some(vec![0, 1]), Some(vec![1])] + ); + assert!(store + .get_partial_values(&[StoreKeyRange::new( + "a/b".try_into()?, + ByteRange::FromStart(1, Some(10)) + ),]) + .await + .is_err()); + + assert_eq!(store.size().await?, 6); + assert_eq!(store.size_prefix(&"a/".try_into()?).await?, 4); + assert_eq!(store.size_prefix(&"i/".try_into()?).await?, 2); + + Ok(()) + } + + pub async fn store_list( + store: &T, + ) -> Result<(), Box> { + assert_eq!( + store.list().await?, + &[ + "a/b".try_into()?, + "a/c".try_into()?, + "a/d/e".try_into()?, + "a/f/g".try_into()?, + "a/f/h".try_into()?, + "i/j/k".try_into()? + ] + ); + + assert_eq!( + store.list_prefix(&"a/".try_into()?).await?, + &[ + "a/b".try_into()?, + "a/c".try_into()?, + "a/d/e".try_into()?, + "a/f/g".try_into()?, + "a/f/h".try_into()? + ] + ); + assert_eq!( + store.list_prefix(&"i/".try_into()?).await?, + &["i/j/k".try_into()?] + ); + + { + let list_dir = store.list_dir(&"a/".try_into()?).await?; + assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); + assert_eq!( + list_dir.prefixes(), + &["a/d/".try_into()?, "a/f/".try_into()?,] + ); + } + Ok(()) + } +} diff --git a/src/storage/store/store_async/object_store.rs b/src/storage/store/store_async/object_store.rs index 24747a7c..cf880d39 100644 --- a/src/storage/store/store_async/object_store.rs +++ b/src/storage/store/store_async/object_store.rs @@ -249,148 +249,27 @@ impl AsyncListableStorageTraits for AsyncObjectSto #[cfg(test)] mod tests { - use crate::storage::{ - AsyncListableStorageTraits, AsyncReadableStorageTraits, AsyncWritableStorageTraits, - StoreKeyStartValue, StorePrefix, - }; - use super::*; use std::error::Error; #[tokio::test] - async fn memory_set() -> Result<(), Box> { - let store = AsyncObjectStore::new(object_store::memory::InMemory::new()); - let key = "a/b".try_into()?; - store.set(&key, &[0, 1, 2]).await?; - assert_eq!(store.get(&key).await?.unwrap(), &[0, 1, 2]); - store - .set_partial_values(&[StoreKeyStartValue::new(key.clone(), 1, &[3, 4])]) - .await?; - assert_eq!(store.get(&key).await?.unwrap(), &[0, 3, 4]); - Ok(()) - } - - #[tokio::test] - async fn memory_list() -> Result<(), Box> { - let store = AsyncObjectStore::new(object_store::memory::InMemory::new()); - - store.set(&"a/b".try_into()?, &[]).await?; - store.set(&"a/c".try_into()?, &[]).await?; - store.set(&"a/d/e".try_into()?, &[]).await?; - store.set(&"a/d/f".try_into()?, &[]).await?; - store.erase(&"a/d/e".try_into()?).await?; - assert_eq!( - store.list().await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/".try_into()?).await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/d/".try_into()?).await?, - &["a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"".try_into()?).await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - Ok(()) - } - - #[tokio::test] - async fn memory_list_dir() -> Result<(), Box> { + async fn memory() -> Result<(), Box> { let store = AsyncObjectStore::new(object_store::memory::InMemory::new()); - store.set(&"a/b".try_into()?, &[]).await?; - store.set(&"a/c".try_into()?, &[]).await?; - store.set(&"a/d/e".try_into()?, &[]).await?; - store.set(&"a/f/g".try_into()?, &[]).await?; - store.set(&"a/f/h".try_into()?, &[]).await?; - store.set(&"b/c/d".try_into()?, &[]).await?; - - let list_dir = store.list_dir(&"a/".try_into()?).await?; - - assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); - assert_eq!( - list_dir.prefixes(), - &["a/d/".try_into()?, "a/f/".try_into()?,] - ); + super::super::test_util::store_write(&store).await?; + super::super::test_util::store_read(&store).await?; + super::super::test_util::store_list(&store).await?; Ok(()) } #[tokio::test] - async fn filesystem_set() -> Result<(), Box> { + async fn filesystem() -> Result<(), Box> { let path = tempfile::TempDir::new()?; let store = AsyncObjectStore::new(object_store::local::LocalFileSystem::new_with_prefix( path.path(), )?); - - let key = "a/b".try_into()?; - store.set(&key, &[0, 1, 2]).await?; - assert_eq!(store.get(&key).await?.unwrap(), &[0, 1, 2]); - store - .set_partial_values(&[StoreKeyStartValue::new(key.clone(), 1, &[3, 4])]) - .await?; - assert_eq!(store.get(&key).await?.unwrap(), &[0, 3, 4]); - Ok(()) - } - - #[tokio::test] - async fn filesystem_list() -> Result<(), Box> { - let path = tempfile::TempDir::new()?; - let store = AsyncObjectStore::new(object_store::local::LocalFileSystem::new_with_prefix( - path.path(), - )?); - - store.set(&"a/b".try_into()?, &[]).await?; - store.set(&"a/c".try_into()?, &[]).await?; - store.set(&"a/d/e".try_into()?, &[]).await?; - store.set(&"a/d/f".try_into()?, &[]).await?; - store.erase(&"a/d/e".try_into()?).await?; - assert_eq!( - store.list().await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/".try_into()?).await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/d/".try_into()?).await?, - &["a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"".try_into()?).await?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - - // assert!(crate::storage::node_exists(&store, &"/a/b".try_into()?).await?); - // assert!(crate::storage::node_exists_listable(&store, &"/a/b".try_into()?).await?); - - Ok(()) - } - - #[tokio::test] - async fn filesystem_list_dir() -> Result<(), Box> { - let path = tempfile::TempDir::new()?; - let store = AsyncObjectStore::new(object_store::local::LocalFileSystem::new_with_prefix( - path.path(), - )?); - - store.set(&"a/b".try_into()?, &[]).await?; - store.set(&"a/c".try_into()?, &[]).await?; - store.set(&"a/d/e".try_into()?, &[]).await?; - store.set(&"a/f/g".try_into()?, &[]).await?; - store.set(&"a/f/h".try_into()?, &[]).await?; - store.set(&"b/c/d".try_into()?, &[]).await?; - - let list_dir = store.list_dir(&StorePrefix::new("a/")?).await?; - - assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); - assert_eq!( - list_dir.prefixes(), - &["a/d/".try_into()?, "a/f/".try_into()?,] - ); + super::super::test_util::store_write(&store).await?; + super::super::test_util::store_read(&store).await?; + super::super::test_util::store_list(&store).await?; Ok(()) } } diff --git a/src/storage/store/store_sync.rs b/src/storage/store/store_sync.rs index b738d4f8..f95a633e 100644 --- a/src/storage/store/store_sync.rs +++ b/src/storage/store/store_sync.rs @@ -3,3 +3,132 @@ pub mod memory_store; #[cfg(feature = "http")] pub mod http_store; + +#[cfg(test)] +mod test_util { + use std::error::Error; + + use crate::{ + byte_range::ByteRange, + storage::{ + ListableStorageTraits, ReadableStorageTraits, StoreKeyRange, StoreKeyStartValue, + StorePrefix, WritableStorageTraits, + }, + }; + + /// Create a store with the following data + /// - a/ + /// - b [0, 1, 2] + /// - c [0] + /// - d/ + /// - e + /// - f/ + /// - g + /// - h + /// - i/ + /// - j/ + /// - k [0, 1] + pub fn store_write(store: &T) -> Result<(), Box> { + store.erase_prefix(&StorePrefix::root())?; + + store.set(&"a/b".try_into()?, &[0, 0, 0])?; + store.set_partial_values(&[StoreKeyStartValue::new("a/b".try_into()?, 1, &[1, 2])])?; + + store.set(&"a/c".try_into()?, &[0])?; + store.set(&"a/d/e".try_into()?, &[])?; + store.set(&"a/f/g".try_into()?, &[])?; + store.set(&"a/f/h".try_into()?, &[])?; + store.set(&"i/j/k".try_into()?, &[0, 1])?; + + store.set(&"erase".try_into()?, &[])?; + store.erase(&"erase".try_into()?)?; + + store.set(&"erase_values_0".try_into()?, &[])?; + store.set(&"erase_values_1".try_into()?, &[])?; + store.erase_values(&["erase_values_0".try_into()?, "erase_values_1".try_into()?])?; + + store.set(&"erase_prefix/0".try_into()?, &[])?; + store.set(&"erase_prefix/1".try_into()?, &[])?; + store.erase_prefix(&"erase_prefix/".try_into()?)?; + + Ok(()) + } + + pub fn store_read(store: &T) -> Result<(), Box> { + assert!(store.get(&"notfound".try_into()?)?.is_none()); + assert!(store.size_key(&"notfound".try_into()?)?.is_none()); + assert_eq!(store.get(&"a/b".try_into()?)?, Some(vec![0, 1, 2])); + assert_eq!(store.size_key(&"a/b".try_into()?)?, Some(3)); + assert_eq!(store.size_key(&"a/c".try_into()?)?, Some(1)); + assert_eq!(store.size_key(&"i/j/k".try_into()?)?, Some(2)); + assert_eq!( + store.get_partial_values_key( + &"a/b".try_into()?, + &[ + ByteRange::FromStart(1, Some(1)), + ByteRange::FromEnd(0, Some(1)) + ] + )?, + Some(vec![vec![1], vec![2]]) + ); + assert_eq!( + store.get_partial_values(&[ + StoreKeyRange::new("a/b".try_into()?, ByteRange::FromStart(1, None)), + StoreKeyRange::new("a/b".try_into()?, ByteRange::FromEnd(1, Some(2))), + StoreKeyRange::new("i/j/k".try_into()?, ByteRange::FromStart(1, Some(1))), + ])?, + vec![Some(vec![1, 2]), Some(vec![0, 1]), Some(vec![1])] + ); + assert!(store + .get_partial_values(&[StoreKeyRange::new( + "a/b".try_into()?, + ByteRange::FromStart(1, Some(10)) + ),]) + .is_err()); + + assert_eq!(store.size()?, 6); + assert_eq!(store.size_prefix(&"a/".try_into()?)?, 4); + assert_eq!(store.size_prefix(&"i/".try_into()?)?, 2); + + Ok(()) + } + + pub fn store_list(store: &T) -> Result<(), Box> { + assert_eq!( + store.list()?, + &[ + "a/b".try_into()?, + "a/c".try_into()?, + "a/d/e".try_into()?, + "a/f/g".try_into()?, + "a/f/h".try_into()?, + "i/j/k".try_into()? + ] + ); + + assert_eq!( + store.list_prefix(&"a/".try_into()?)?, + &[ + "a/b".try_into()?, + "a/c".try_into()?, + "a/d/e".try_into()?, + "a/f/g".try_into()?, + "a/f/h".try_into()? + ] + ); + assert_eq!( + store.list_prefix(&"i/".try_into()?)?, + &["i/j/k".try_into()?] + ); + + { + let list_dir = store.list_dir(&"a/".try_into()?)?; + assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); + assert_eq!( + list_dir.prefixes(), + &["a/d/".try_into()?, "a/f/".try_into()?,] + ); + } + Ok(()) + } +} diff --git a/src/storage/store/store_sync/filesystem_store.rs b/src/storage/store/store_sync/filesystem_store.rs index 896381f8..3b920809 100644 --- a/src/storage/store/store_sync/filesystem_store.rs +++ b/src/storage/store/store_sync/filesystem_store.rs @@ -406,71 +406,12 @@ mod tests { use std::error::Error; #[test] - fn filesystem_set() -> Result<(), Box> { - let path = tempfile::TempDir::new()?; - let store = FilesystemStore::new(path.path())?; - let key = "a/b".try_into()?; - store.set(&key, &[0, 1, 2])?; - assert_eq!(store.get(&key)?.unwrap(), &[0, 1, 2]); - store.set_partial_values(&[StoreKeyStartValue::new(key.clone(), 1, &[3, 4])])?; - assert_eq!(store.get(&key)?.unwrap(), &[0, 3, 4]); - Ok(()) - } - - #[test] - fn filesystem_list() -> Result<(), Box> { - let path = tempfile::TempDir::new()?; - let store = FilesystemStore::new(path.path())?; - - store.set(&"a/b".try_into()?, &[])?; - store.set(&"a/c".try_into()?, &[])?; - store.set(&"a/d/e".try_into()?, &[])?; - store.set(&"a/d/f".try_into()?, &[])?; - store.erase(&"a/d/e".try_into()?)?; - assert_eq!( - store.list()?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/".try_into()?)?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/d/".try_into()?)?, - &["a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"".try_into()?)?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - - assert!(crate::storage::node_exists(&store, &"/a/b".try_into()?)?); - assert!(crate::storage::node_exists_listable( - &store, - &"/a/b".try_into()? - )?); - - Ok(()) - } - - #[test] - fn filesystem_list_dir() -> Result<(), Box> { + fn filesystem() -> Result<(), Box> { let path = tempfile::TempDir::new()?; let store = FilesystemStore::new(path.path())?.sorted(); - store.set(&"a/b".try_into()?, &[])?; - store.set(&"a/c".try_into()?, &[])?; - store.set(&"a/d/e".try_into()?, &[])?; - store.set(&"a/f/g".try_into()?, &[])?; - store.set(&"a/f/h".try_into()?, &[])?; - store.set(&"b/c/d".try_into()?, &[])?; - - let list_dir = store.list_dir(&StorePrefix::new("a/")?)?; - - assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); - assert_eq!( - list_dir.prefixes(), - &["a/d/".try_into()?, "a/f/".try_into()?,] - ); + super::super::test_util::store_write(&store)?; + super::super::test_util::store_read(&store)?; + super::super::test_util::store_list(&store)?; Ok(()) } } diff --git a/src/storage/store/store_sync/memory_store.rs b/src/storage/store/store_sync/memory_store.rs index d9983778..7b9706bf 100644 --- a/src/storage/store/store_sync/memory_store.rs +++ b/src/storage/store/store_sync/memory_store.rs @@ -5,7 +5,7 @@ use std::sync::Mutex; use crate::{ array::MaybeBytes, - byte_range::{ByteOffset, ByteRange}, + byte_range::{ByteOffset, ByteRange, InvalidByteRangeError}, storage::{ store_lock::{DefaultStoreLocks, StoreKeyMutex, StoreLocks}, store_set_partial_values, ListableStorageTraits, ReadableStorageTraits, @@ -101,6 +101,9 @@ impl ReadableStorageTraits for MemoryStore { for byte_range in byte_ranges { let start = usize::try_from(byte_range.start(data.len() as u64)).unwrap(); let end = usize::try_from(byte_range.end(data.len() as u64)).unwrap(); + if end > data.len() { + return Err(InvalidByteRangeError.into()); + } let bytes = data[start..end].to_vec(); out.push(bytes); } @@ -219,101 +222,11 @@ mod tests { use std::error::Error; #[test] - fn memory_set() -> Result<(), Box> { - let store = MemoryStore::new(); - let key = "a/b".try_into()?; - store.set(&key, &[0, 1, 2])?; - assert_eq!(store.get(&key)?.unwrap(), &[0, 1, 2]); - store.set_partial_values(&[StoreKeyStartValue::new(key.clone(), 1, &[3, 4])])?; - assert_eq!(store.get(&key)?.unwrap(), &[0, 3, 4]); - - assert_eq!( - store - .get_partial_values_key(&key, &[ByteRange::FromStart(1, None)])? - .unwrap() - .first() - .unwrap(), - &[3, 4] - ); - - assert!(store - .get_partial_values_key(&"a/b/c".try_into()?, &[ByteRange::FromStart(1, None)])? - .is_none()); - - assert_eq!( - store - .get_partial_values(&[StoreKeyRange::new( - key.clone(), - ByteRange::FromStart(1, None) - )])? - .first() - .unwrap() - .as_ref() - .unwrap(), - &[3, 4] - ); - Ok(()) - } - - #[test] - fn memory_list() -> Result<(), Box> { - let store = MemoryStore::new(); - - store.set(&"a/b".try_into()?, &[0])?; - store.set(&"a/c".try_into()?, &[0, 0])?; - store.set(&"a/d/e".try_into()?, &[])?; - store.set(&"a/d/f".try_into()?, &[])?; - store.erase(&"a/d/e".try_into()?)?; - assert_eq!( - store.list()?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/".try_into()?)?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"a/d/".try_into()?)?, - &["a/d/f".try_into()?] - ); - assert_eq!( - store.list_prefix(&"".try_into()?)?, - &["a/b".try_into()?, "a/c".try_into()?, "a/d/f".try_into()?] - ); - - assert_eq!(store.list_prefix(&"b/".try_into()?)?, &[]); - - assert_eq!(store.size()?, 3); - assert_eq!(store.size_prefix(&"a/".try_into().unwrap())?, 3); - assert_eq!(store.size_key(&"a/b".try_into().unwrap())?, Some(1)); - Ok(()) - } - - #[test] - fn memory_list_dir() -> Result<(), Box> { + fn memory() -> Result<(), Box> { let store = MemoryStore::new(); - store.set(&"a/b".try_into()?, &[])?; - store.set(&"a/c".try_into()?, &[])?; - store.set(&"a/d/e".try_into()?, &[])?; - store.set(&"a/f/g".try_into()?, &[])?; - store.set(&"a/f/h".try_into()?, &[])?; - store.set(&"b/c/d".try_into()?, &[])?; - - let list_dir = store.list_dir(&StorePrefix::root())?; - assert_eq!(list_dir.prefixes(), &["a/".try_into()?, "b/".try_into()?,]); - - let list_dir = store.list_dir(&"a/".try_into()?)?; - - assert_eq!(list_dir.keys(), &["a/b".try_into()?, "a/c".try_into()?,]); - assert_eq!( - list_dir.prefixes(), - &["a/d/".try_into()?, "a/f/".try_into()?,] - ); - - store.erase_prefix(&"b/".try_into()?)?; - let list_dir = store.list_dir(&StorePrefix::root())?; - assert_eq!(list_dir.prefixes(), &["a/".try_into()?,]); - + super::super::test_util::store_write(&store)?; + super::super::test_util::store_read(&store)?; + super::super::test_util::store_list(&store)?; Ok(()) } }