From 45332158dfebdd55c4583c6e0bc1f1cd6e98df57 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Tue, 3 Nov 2020 21:27:32 +0100 Subject: [PATCH] Add driver for new store --- libraries/persistent_store/src/driver.rs | 630 +++++++++++++++++++++++ libraries/persistent_store/src/lib.rs | 6 + libraries/persistent_store/src/store.rs | 210 +++++++- 3 files changed, 844 insertions(+), 2 deletions(-) create mode 100644 libraries/persistent_store/src/driver.rs diff --git a/libraries/persistent_store/src/driver.rs b/libraries/persistent_store/src/driver.rs new file mode 100644 index 0000000..fd83ab1 --- /dev/null +++ b/libraries/persistent_store/src/driver.rs @@ -0,0 +1,630 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::format::{Format, Position}; +#[cfg(test)] +use crate::StoreUpdate; +use crate::{ + BufferCorruptFunction, BufferOptions, BufferStorage, Nat, Store, StoreError, StoreHandle, + StoreModel, StoreOperation, StoreResult, +}; + +/// Tracks the store behavior against its model and its storage. +#[derive(Clone)] +pub enum StoreDriver { + /// When the store is running. + On(StoreDriverOn), + + /// When the store is off. + Off(StoreDriverOff), +} + +/// Keeps a power-on store and its model in sync. +#[derive(Clone)] +pub struct StoreDriverOn { + /// The store being tracked. + store: Store, + + /// The model associated to the store. + model: StoreModel, +} + +/// Keeps a power-off store and its potential models in sync. +#[derive(Clone)] +pub struct StoreDriverOff { + /// The storage of the store being tracked. + storage: BufferStorage, + + /// The last valid model before power off. + model: StoreModel, + + /// In case of interrupted operation, the invariant after completion. + complete: Option, +} + +/// The invariant a store must satisfy if an interrupted operation completes. +#[derive(Clone)] +struct Complete { + /// The model after the operation completes. + model: StoreModel, + + /// The entries that should be deleted after the operation completes. + deleted: Vec, +} + +/// Specifies an interruption. +pub struct StoreInterruption<'a> { + /// After how many storage operations the interruption should happen. + pub delay: usize, + + /// How the interrupted operation should be corrupted. + pub corrupt: BufferCorruptFunction<'a>, +} + +/// Possible ways a driver operation may fail. +#[derive(Debug)] +pub enum StoreInvariant { + /// The store reached its lifetime. + /// + /// This is not simulated by the model. So the operation should be ignored. + NoLifetime, + + /// The store returned an unexpected error. + StoreError(StoreError), + + /// The store did not recover an interrupted operation. + Interrupted { + /// The reason why the store didn't rollback the operation. + rollback: Box, + + /// The reason why the store didn't complete the operation. + complete: Box, + }, + + /// The store returned a different result than the model. + DifferentResult { + /// The result of the store. + store: StoreResult<()>, + + /// The result of the model. + model: StoreResult<()>, + }, + + /// The store did not wipe an entry. + NotWiped { + /// The key of the entry that has not been wiped. + key: usize, + + /// The value of the entry in the storage. + value: Vec, + }, + + /// The store has an entry not present in the model. + OnlyInStore { + /// The key of the additional entry. + key: usize, + }, + + /// The store has a different value than the model for an entry. + DifferentValue { + /// The key of the entry with a different value. + key: usize, + + /// The value of the entry in the store. + store: Box<[u8]>, + + /// The value of the entry in the model. + model: Box<[u8]>, + }, + + /// The store is missing an entry from the model. + OnlyInModel { + /// The key of the missing entry. + key: usize, + }, + + /// The store reports a different capacity than the model. + DifferentCapacity { + /// The capacity according to the store. + store: usize, + + /// The capacity according to the model. + model: usize, + }, + + /// The store failed to track the number of erase cycles correctly. + DifferentErase { + /// The first page in physical storage order with a wrong value. + page: usize, + + /// How many times the page has been erased according to the store. + store: usize, + + /// How many times the page has been erased according to the model. + model: usize, + }, + + /// The store failed to track the number of word writes correctly. + DifferentWrite { + /// The first page in physical storage order with a wrong value. + page: usize, + + /// The first word in the page with a wrong value. + word: usize, + + /// How many times the word has been written according to the store. + /// + /// This value is exact only for the metadata of the page. For the content of the page, it + /// is set to: + /// - 0 if the word is after the tail. Such word should not have been written. + /// - 1 if the word is before the tail. Such word may or may not have been written. + store: usize, + + /// How many times the word has been written according to the model. + /// + /// This value is exact only for the metadata of the page. For the content of the page, it + /// is set to: + /// - 0 if the word was not written. + /// - 1 if the word was written. + model: usize, + }, +} + +impl StoreDriver { + /// Provides read-only access to the storage. + pub fn storage(&self) -> &BufferStorage { + match self { + StoreDriver::On(x) => x.store().storage(), + StoreDriver::Off(x) => x.storage(), + } + } + + /// Extracts the power-on version of the driver. + pub fn on(self) -> Option { + match self { + StoreDriver::On(x) => Some(x), + StoreDriver::Off(_) => None, + } + } + + /// Powers on the store if not already on. + pub fn power_on(self) -> Result { + match self { + StoreDriver::On(x) => Ok(x), + StoreDriver::Off(x) => x.power_on(), + } + } + + /// Extracts the power-off version of the driver. + pub fn off(self) -> Option { + match self { + StoreDriver::On(_) => None, + StoreDriver::Off(x) => Some(x), + } + } +} + +impl StoreDriverOff { + /// Starts a simulation with a clean storage given its configuration. + pub fn new(options: BufferOptions, num_pages: usize) -> StoreDriverOff { + let storage = vec![0xff; num_pages * options.page_size].into_boxed_slice(); + let storage = BufferStorage::new(storage, options); + StoreDriverOff::new_dirty(storage) + } + + /// Starts a simulation from an existing storage. + pub fn new_dirty(storage: BufferStorage) -> StoreDriverOff { + let format = Format::new(&storage).unwrap(); + StoreDriverOff { + storage, + model: StoreModel::new(format), + complete: None, + } + } + + /// Provides read-only access to the storage. + pub fn storage(&self) -> &BufferStorage { + &self.storage + } + + /// Provides mutable access to the storage. + pub fn storage_mut(&mut self) -> &mut BufferStorage { + &mut self.storage + } + + /// Provides read-only access to the model. + pub fn model(&self) -> &StoreModel { + &self.model + } + + /// Powers on the store without interruption. + pub fn power_on(self) -> Result { + Ok(self + .partial_power_on(StoreInterruption::none()) + .map_err(|x| x.1)? + .on() + .unwrap()) + } + + /// Powers on the store with a possible interruption. + pub fn partial_power_on( + mut self, + interruption: StoreInterruption, + ) -> Result { + self.storage.arm_interruption(interruption.delay); + Ok(match Store::new(self.storage) { + Ok(mut store) => { + store.storage_mut().disarm_interruption(); + let mut error = None; + if let Some(complete) = self.complete { + match StoreDriverOn::new(store, complete.model, &complete.deleted) { + Ok(driver) => return Ok(StoreDriver::On(driver)), + Err((e, x)) => { + error = Some(e); + store = x; + } + } + }; + StoreDriver::On(StoreDriverOn::new(store, self.model, &[]).map_err( + |(rollback, store)| { + let storage = store.into_storage(); + match error { + None => (storage, rollback), + Some(complete) => { + let rollback = Box::new(rollback); + let complete = Box::new(complete); + (storage, StoreInvariant::Interrupted { rollback, complete }) + } + } + }, + )?) + } + Err((StoreError::StorageError, mut storage)) => { + storage.corrupt_operation(interruption.corrupt); + StoreDriver::Off(StoreDriverOff { storage, ..self }) + } + Err((error, mut storage)) => { + storage.reset_interruption(); + return Err((storage, StoreInvariant::StoreError(error))); + } + }) + } + + /// Returns a mapping from delay time to number of modified bits. + pub fn delay_map(&self) -> Result, (usize, BufferStorage)> { + let mut result = Vec::new(); + loop { + let delay = result.len(); + let mut storage = self.storage.clone(); + storage.arm_interruption(delay); + match Store::new(storage) { + Err((StoreError::StorageError, x)) => storage = x, + Err((StoreError::InvalidStorage, mut storage)) => { + storage.reset_interruption(); + return Err((delay, storage)); + } + Ok(_) | Err(_) => break, + } + result.push(count_modified_bits(&mut storage)); + } + result.push(0); + Ok(result) + } +} + +impl StoreDriverOn { + /// Provides read-only access to the store. + pub fn store(&self) -> &Store { + &self.store + } + + /// Extracts the store. + pub fn into_store(self) -> Store { + self.store + } + + /// Provides mutable access to the store. + pub fn store_mut(&mut self) -> &mut Store { + &mut self.store + } + + /// Provides read-only access to the model. + pub fn model(&self) -> &StoreModel { + &self.model + } + + /// Applies a store operation to the store and model without interruption. + pub fn apply(&mut self, operation: StoreOperation) -> Result<(), StoreInvariant> { + let (deleted, store_result) = self.store.apply(&operation); + let model_result = self.model.apply(operation); + if store_result != model_result { + return Err(StoreInvariant::DifferentResult { + store: store_result, + model: model_result, + }); + } + self.check_deleted(&deleted)?; + Ok(()) + } + + /// Applies a store operation to the store and model with a possible interruption. + pub fn partial_apply( + mut self, + operation: StoreOperation, + interruption: StoreInterruption, + ) -> Result<(Option, StoreDriver), (Store, StoreInvariant)> { + self.store + .storage_mut() + .arm_interruption(interruption.delay); + let (deleted, store_result) = self.store.apply(&operation); + Ok(match store_result { + Err(StoreError::NoLifetime) => return Err((self.store, StoreInvariant::NoLifetime)), + Ok(()) | Err(StoreError::NoCapacity) | Err(StoreError::InvalidArgument) => { + self.store.storage_mut().disarm_interruption(); + let model_result = self.model.apply(operation); + if store_result != model_result { + return Err(( + self.store, + StoreInvariant::DifferentResult { + store: store_result, + model: model_result, + }, + )); + } + if store_result.is_ok() { + if let Err(invariant) = self.check_deleted(&deleted) { + return Err((self.store, invariant)); + } + } + (store_result.err(), StoreDriver::On(self)) + } + Err(StoreError::StorageError) => { + let mut driver = StoreDriverOff { + storage: self.store.into_storage(), + model: self.model, + complete: None, + }; + driver.storage.corrupt_operation(interruption.corrupt); + let mut model = driver.model.clone(); + if model.apply(operation).is_ok() { + driver.complete = Some(Complete { model, deleted }); + } + (None, StoreDriver::Off(driver)) + } + Err(error) => return Err((self.store, StoreInvariant::StoreError(error))), + }) + } + + /// Returns a mapping from delay time to number of modified bits. + pub fn delay_map( + &self, + operation: &StoreOperation, + ) -> Result, (usize, BufferStorage)> { + let mut result = Vec::new(); + loop { + let delay = result.len(); + let mut store = self.store.clone(); + store.storage_mut().arm_interruption(delay); + match store.apply(operation).1 { + Err(StoreError::StorageError) => (), + Err(StoreError::InvalidStorage) => return Err((delay, store.into_storage())), + Ok(()) | Err(_) => break, + } + result.push(count_modified_bits(store.storage_mut())); + } + result.push(0); + Ok(result) + } + + /// Powers off the store. + pub fn power_off(self) -> StoreDriverOff { + StoreDriverOff { + storage: self.store.into_storage(), + model: self.model, + complete: None, + } + } + + /// Applies an insertion to the store and model without interruption. + #[cfg(test)] + pub fn insert(&mut self, key: usize, value: &[u8]) -> Result<(), StoreInvariant> { + let value = value.to_vec(); + let updates = vec![StoreUpdate::Insert { key, value }]; + self.apply(StoreOperation::Transaction { updates }) + } + + /// Applies a deletion to the store and model without interruption. + #[cfg(test)] + pub fn remove(&mut self, key: usize) -> Result<(), StoreInvariant> { + let updates = vec![StoreUpdate::Remove { key }]; + self.apply(StoreOperation::Transaction { updates }) + } + + /// Checks that the store and model are in sync. + pub fn check(&self) -> Result<(), StoreInvariant> { + self.recover_check(&[]) + } + + /// Starts a simulation from a power-off store. + /// + /// Checks that the store and model are in sync and that the given deleted entries are wiped. + fn new( + store: Store, + model: StoreModel, + deleted: &[StoreHandle], + ) -> Result)> { + let driver = StoreDriverOn { store, model }; + match driver.recover_check(deleted) { + Ok(()) => Ok(driver), + Err(error) => Err((error, driver.store)), + } + } + + /// Checks that the store and model are in sync and that the given entries are wiped. + fn recover_check(&self, deleted: &[StoreHandle]) -> Result<(), StoreInvariant> { + self.check_deleted(deleted)?; + self.check_model()?; + self.check_storage()?; + Ok(()) + } + + /// Checks that the given entries are wiped from the storage. + fn check_deleted(&self, deleted: &[StoreHandle]) -> Result<(), StoreInvariant> { + for handle in deleted { + let value = self.store.inspect_value(&handle); + if !value.iter().all(|&x| x == 0x00) { + return Err(StoreInvariant::NotWiped { + key: handle.get_key(), + value, + }); + } + } + Ok(()) + } + + /// Checks that the store and model are in sync. + fn check_model(&self) -> Result<(), StoreInvariant> { + let mut model_content = self.model.content().clone(); + for handle in self.store.iter().unwrap() { + let handle = handle.unwrap(); + let model_value = match model_content.remove(&handle.get_key()) { + None => { + return Err(StoreInvariant::OnlyInStore { + key: handle.get_key(), + }) + } + Some(x) => x, + }; + let store_value = handle.get_value(&self.store).unwrap().into_boxed_slice(); + if store_value != model_value { + return Err(StoreInvariant::DifferentValue { + key: handle.get_key(), + store: store_value, + model: model_value, + }); + } + } + if let Some(&key) = model_content.keys().next() { + return Err(StoreInvariant::OnlyInModel { key }); + } + let store_capacity = self.store.capacity().unwrap().remaining(); + let model_capacity = self.model.capacity().remaining(); + if store_capacity != model_capacity { + return Err(StoreInvariant::DifferentCapacity { + store: store_capacity, + model: model_capacity, + }); + } + Ok(()) + } + + /// Checks that the store is tracking lifetime correctly. + fn check_storage(&self) -> Result<(), StoreInvariant> { + let format = self.model.format(); + let storage = self.store.storage(); + let num_words = format.page_size() / format.word_size(); + let head = self.store.head().unwrap(); + let tail = self.store.tail().unwrap(); + for page in 0..format.num_pages() { + // Check the erase cycle of the page. + let store_erase = head.cycle(format) + (page < head.page(format)) as Nat; + let model_erase = storage.get_page_erases(page as usize); + if store_erase as usize != model_erase { + return Err(StoreInvariant::DifferentErase { + page: page as usize, + store: store_erase as usize, + model: model_erase, + }); + } + let page_pos = Position::new(format, store_erase, page, 0); + + // Check the init word of the page. + let mut store_write = (page_pos < tail) as usize; + if page == 0 && tail == Position::new(format, 0, 0, 0) { + // When the store is initialized and nothing written yet, the first page is still + // initialized. + store_write = 1; + } + let model_write = storage.get_word_writes((page * num_words) as usize); + if store_write != model_write { + return Err(StoreInvariant::DifferentWrite { + page: page as usize, + word: 0, + store: store_write, + model: model_write, + }); + } + + // Check the compact info of the page. + let model_write = storage.get_word_writes((page * num_words + 1) as usize); + let store_write = 0; + if store_write != model_write { + return Err(StoreInvariant::DifferentWrite { + page: page as usize, + word: 1, + store: store_write, + model: model_write, + }); + } + + // Check the content of the page. We only check cases where the model says a word was + // written while the store doesn't think it should be the case. This is because the + // model doesn't count writes to the same value. Also we only check whether a word is + // written and not how many times. This is because this is hard to rebuild in the store. + for word in 2..num_words { + let store_write = (page_pos + (word - 2) < tail) as usize; + let model_write = + (storage.get_word_writes((page * num_words + word) as usize) > 0) as usize; + if store_write < model_write { + return Err(StoreInvariant::DifferentWrite { + page: page as usize, + word: word as usize, + store: store_write, + model: model_write, + }); + } + } + } + Ok(()) + } +} + +impl<'a> StoreInterruption<'a> { + /// Builds an interruption that never triggers. + pub fn none() -> StoreInterruption<'a> { + StoreInterruption { + delay: usize::max_value(), + corrupt: Box::new(|_, _| {}), + } + } +} + +/// Counts the number of bits modified by an interrupted operation. +/// +/// # Panics +/// +/// Panics if an interruption did not trigger. +fn count_modified_bits(storage: &mut BufferStorage) -> usize { + let mut modified_bits = 0; + storage.corrupt_operation(Box::new(|before, after| { + modified_bits = before + .iter() + .zip(after.iter()) + .map(|(x, y)| (x ^ y).count_ones() as usize) + .sum(); + })); + // We should never write the same slice or erase an erased page. + assert!(modified_bits > 0); + modified_bits +} diff --git a/libraries/persistent_store/src/lib.rs b/libraries/persistent_store/src/lib.rs index 27bdcd1..9b87bd4 100644 --- a/libraries/persistent_store/src/lib.rs +++ b/libraries/persistent_store/src/lib.rs @@ -349,6 +349,8 @@ extern crate alloc; mod buffer; +#[cfg(feature = "std")] +mod driver; mod format; #[cfg(feature = "std")] mod model; @@ -357,6 +359,10 @@ mod store; pub use self::buffer::{BufferCorruptFunction, BufferOptions, BufferStorage}; #[cfg(feature = "std")] +pub use self::driver::{ + StoreDriver, StoreDriverOff, StoreDriverOn, StoreInterruption, StoreInvariant, +}; +#[cfg(feature = "std")] pub use self::model::{StoreModel, StoreOperation}; pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult}; pub use self::store::{ diff --git a/libraries/persistent_store/src/store.rs b/libraries/persistent_store/src/store.rs index 725d91a..3d61565 100644 --- a/libraries/persistent_store/src/store.rs +++ b/libraries/persistent_store/src/store.rs @@ -18,9 +18,11 @@ use crate::format::{ }; #[cfg(feature = "std")] pub use crate::model::{StoreModel, StoreOperation}; -#[cfg(feature = "std")] -pub use crate::BufferStorage; use crate::{usize_to_nat, Nat, Storage, StorageError, StorageIndex}; +#[cfg(feature = "std")] +pub use crate::{ + BufferStorage, StoreDriver, StoreDriverOff, StoreDriverOn, StoreInterruption, StoreInvariant, +}; use alloc::vec::Vec; use core::cmp::{max, min, Ordering}; #[cfg(feature = "std")] @@ -1233,3 +1235,207 @@ fn is_write_needed(source: &[u8], target: &[u8]) -> StoreResult { } Ok(false) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::BufferOptions; + + #[derive(Clone)] + struct Config { + word_size: usize, + page_size: usize, + num_pages: usize, + max_word_writes: usize, + max_page_erases: usize, + } + + impl Config { + fn new_driver(&self) -> StoreDriverOff { + let options = BufferOptions { + word_size: self.word_size, + page_size: self.page_size, + max_word_writes: self.max_word_writes, + max_page_erases: self.max_page_erases, + strict_write: true, + }; + StoreDriverOff::new(options, self.num_pages) + } + } + + const MINIMAL: Config = Config { + word_size: 4, + page_size: 64, + num_pages: 5, + max_word_writes: 2, + max_page_erases: 9, + }; + + const NORDIC: Config = Config { + word_size: 4, + page_size: 0x1000, + num_pages: 20, + max_word_writes: 2, + max_page_erases: 10000, + }; + + const TITAN: Config = Config { + word_size: 4, + page_size: 0x800, + num_pages: 10, + max_word_writes: 2, + max_page_erases: 10000, + }; + + #[test] + fn nordic_capacity() { + let driver = NORDIC.new_driver().power_on().unwrap(); + assert_eq!(driver.model().capacity().total, 19123); + } + + #[test] + fn titan_capacity() { + let driver = TITAN.new_driver().power_on().unwrap(); + assert_eq!(driver.model().capacity().total, 4315); + } + + #[test] + fn minimal_virt_page_size() { + // Make sure a virtual page has 14 words. We use this property in the other tests below to + // know whether entries are spanning, starting, and ending pages. + assert_eq!(MINIMAL.new_driver().model().format().virt_page_size(), 14); + } + + #[test] + fn init_ok() { + assert!(MINIMAL.new_driver().power_on().is_ok()); + } + + #[test] + fn insert_ok() { + let mut driver = MINIMAL.new_driver().power_on().unwrap(); + // Empty entry. + driver.insert(0, &[]).unwrap(); + driver.insert(1, &[]).unwrap(); + driver.check().unwrap(); + // Last word is erased but last bit is not user data. + driver.insert(0, &[0xff]).unwrap(); + driver.insert(1, &[0xff]).unwrap(); + driver.check().unwrap(); + // Last word is erased and last bit is user data. + driver.insert(0, &[0xff, 0xff, 0xff, 0xff]).unwrap(); + driver.insert(1, &[0xff, 0xff, 0xff, 0xff]).unwrap(); + driver.insert(2, &[0x5c; 6]).unwrap(); + driver.check().unwrap(); + // Entry spans 2 pages. + assert_eq!(driver.store().tail().unwrap().get(), 13); + driver.insert(3, &[0x5c; 8]).unwrap(); + driver.check().unwrap(); + assert_eq!(driver.store().tail().unwrap().get(), 16); + // Entry ends a page. + driver.insert(2, &[0x93; (28 - 16 - 1) * 4]).unwrap(); + driver.check().unwrap(); + assert_eq!(driver.store().tail().unwrap().get(), 28); + // Entry starts a page. + driver.insert(3, &[0x81; 10]).unwrap(); + driver.check().unwrap(); + } + + #[test] + fn remove_ok() { + let mut driver = MINIMAL.new_driver().power_on().unwrap(); + // Remove absent entry. + driver.remove(0).unwrap(); + driver.remove(1).unwrap(); + driver.check().unwrap(); + // Remove last inserted entry. + driver.insert(0, &[0x5c; 6]).unwrap(); + driver.remove(0).unwrap(); + driver.check().unwrap(); + // Remove empty entries. + driver.insert(0, &[]).unwrap(); + driver.insert(1, &[]).unwrap(); + driver.remove(0).unwrap(); + driver.remove(1).unwrap(); + driver.check().unwrap(); + // Remove entry with flipped bit. + driver.insert(0, &[0xff]).unwrap(); + driver.insert(1, &[0xff; 4]).unwrap(); + driver.remove(0).unwrap(); + driver.remove(1).unwrap(); + driver.check().unwrap(); + // Write some entries with one spanning 2 pages. + driver.insert(2, &[0x93; 9]).unwrap(); + assert_eq!(driver.store().tail().unwrap().get(), 13); + driver.insert(3, &[0x81; 10]).unwrap(); + assert_eq!(driver.store().tail().unwrap().get(), 17); + driver.insert(4, &[0x76; 11]).unwrap(); + driver.check().unwrap(); + // Remove the entry spanning 2 pages. + driver.remove(3).unwrap(); + driver.check().unwrap(); + // Write some entries with one ending a page and one starting the next. + assert_eq!(driver.store().tail().unwrap().get(), 21); + driver.insert(2, &[0xd7; (28 - 21 - 1) * 4]).unwrap(); + assert_eq!(driver.store().tail().unwrap().get(), 28); + driver.insert(4, &[0xe2; 21]).unwrap(); + driver.check().unwrap(); + // Remove them. + driver.remove(2).unwrap(); + driver.remove(4).unwrap(); + driver.check().unwrap(); + } + + #[test] + fn prepare_ok() { + let mut driver = MINIMAL.new_driver().power_on().unwrap(); + + // Don't compact if enough immediate capacity. + assert_eq!(driver.store().immediate_capacity().unwrap(), 39); + assert_eq!(driver.store().capacity().unwrap().remaining(), 34); + assert_eq!(driver.store().head().unwrap().get(), 0); + driver.store_mut().prepare(34).unwrap(); + assert_eq!(driver.store().head().unwrap().get(), 0); + + // Fill the store. + for key in 0..4 { + driver.insert(key, &[0x38; 28]).unwrap(); + } + driver.check().unwrap(); + assert_eq!(driver.store().immediate_capacity().unwrap(), 7); + assert_eq!(driver.store().capacity().unwrap().remaining(), 2); + // Removing entries increases available capacity but not immediate capacity. + driver.remove(0).unwrap(); + driver.remove(2).unwrap(); + driver.check().unwrap(); + assert_eq!(driver.store().immediate_capacity().unwrap(), 7); + assert_eq!(driver.store().capacity().unwrap().remaining(), 18); + + // Prepare for next write (7 words data + 1 word overhead). + assert_eq!(driver.store().head().unwrap().get(), 0); + driver.store_mut().prepare(8).unwrap(); + driver.check().unwrap(); + assert_eq!(driver.store().head().unwrap().get(), 16); + // The available capacity did not change, but the immediate capacity is above 8. + assert_eq!(driver.store().immediate_capacity().unwrap(), 14); + assert_eq!(driver.store().capacity().unwrap().remaining(), 18); + } + + #[test] + fn reboot_ok() { + let mut driver = MINIMAL.new_driver().power_on().unwrap(); + + // Do some operations and reboot. + driver.insert(0, &[0x38; 24]).unwrap(); + driver.insert(1, &[0x5c; 13]).unwrap(); + driver = driver.power_off().power_on().unwrap(); + driver.check().unwrap(); + + // Do more operations and reboot. + driver.insert(2, &[0x93; 1]).unwrap(); + driver.remove(0).unwrap(); + driver.insert(3, &[0xde; 9]).unwrap(); + driver = driver.power_off().power_on().unwrap(); + driver.check().unwrap(); + } +}