diff --git a/libraries/persistent_store/src/driver.rs b/libraries/persistent_store/src/driver.rs index d2baee0..5889651 100644 --- a/libraries/persistent_store/src/driver.rs +++ b/libraries/persistent_store/src/driver.rs @@ -17,7 +17,7 @@ //! [`StoreDriver`] wraps a [`Store`] and compares its behavior with its associated [`StoreModel`]. use crate::format::{Format, Position}; -#[cfg(test)] +#[cfg(feature = "std")] use crate::StoreUpdate; use crate::{ BufferCorruptFunction, BufferOptions, BufferStorage, Nat, Store, StoreError, StoreHandle, @@ -431,7 +431,7 @@ impl StoreDriverOn { } /// Applies an insertion to the store and model without interruption. - #[cfg(test)] + #[cfg(feature = "std")] pub fn insert(&mut self, key: usize, value: &[u8]) -> Result<(), StoreInvariant> { let value = value.to_vec(); let updates = vec![StoreUpdate::Insert { key, value }]; @@ -439,7 +439,7 @@ impl StoreDriverOn { } /// Applies a deletion to the store and model without interruption. - #[cfg(test)] + #[cfg(feature = "std")] pub fn remove(&mut self, key: usize) -> Result<(), StoreInvariant> { let updates = vec![StoreUpdate::Remove { key }]; self.apply(StoreOperation::Transaction { updates }) diff --git a/libraries/persistent_store/tests/store.rs b/libraries/persistent_store/tests/store.rs new file mode 100644 index 0000000..8ec8e9b --- /dev/null +++ b/libraries/persistent_store/tests/store.rs @@ -0,0 +1,51 @@ +use persistent_store::{BufferOptions, StoreDriverOff, StoreInterruption, StoreOperation}; + +#[test] +fn interrupted_overflowing_compaction() { + let options = BufferOptions { + word_size: 4, + page_size: 32, + max_word_writes: 2, + max_page_erases: 3, + strict_mode: true, + }; + let num_pages = 7; + let mut driver = StoreDriverOff::new(options, num_pages).power_on().unwrap(); + let v = driver.model().format().virt_size() as usize; + let c = driver.model().format().total_capacity() as usize; + let q = driver.model().format().virt_page_size() as usize; + + // We setup the storage such that the next 2 compactions are overflowing. This means they copy 1 + // more word than the size of a page. + let mut k = 0; + // Fill the first 2 pages with non-deleted entries. + while k < 2 * q { + driver.insert(k, &[]).unwrap(); + k += 1; + } + // Write enough deleted entries to be able to continue writing without compaction. + for _ in c..v { + driver.insert(k, &[]).unwrap(); + } + // Fill until the end of the window with deleted entries. + while k < c { + driver.insert(k, &[]).unwrap(); + driver.remove(k).unwrap(); + k += 1; + } + // Make sure we did not compact and we actually filled until the end of the window. + assert_eq!(driver.store().lifetime().unwrap().used(), v); + + // We trigger 2 interrupted overflowing compactions, which would move the last non-deleted entry + // out of the window unless additional compactions are done to restore the overflow. + for _ in 0..2 { + let interruption = StoreInterruption { + delay: 0, + corrupt: Box::new(|old, new| old.copy_from_slice(new)), + }; + match driver.partial_apply(StoreOperation::Prepare { length: 1 }, interruption) { + Ok((None, d)) => driver = d.power_on().unwrap(), + _ => unreachable!(), + } + } +}