# Storage testing Use the deterministic runtime's storage backend for storage tests. It permits reproducible I/O, crashes, corruption, and recovery without real disk access. ## Basic operations ```rust #[test] fn test_storage_operations() { let runner = deterministic::Runner::default(); runner.start(|context| async move { let (blob, _) = context .open("partition_name", &0u64.to_be_bytes()) .await .expect("open blob"); blob.write_at(0, vec![1, 2, 3, 4], WriteOptions::default()) .await .expect("write"); let data = blob .read_at(0, 4, ReadOptions::default()) .await .expect("read") .coalesce(); assert_eq!(data.as_ref(), &[1, 2, 3, 4]); blob.sync().await.expect("sync"); }); } ``` ## Recovery and corruption Persist data, drop the database, and initialize it again to test clean recovery. Before reinitializing, drop every blob handle and journal snapshot, and abort and join any task that holds one. Opening a blob that is still held returns `BlobAlreadyOpen`. To simulate an interrupted write, resize a blob after writing it; to simulate corruption, overwrite a checksum or truncate data. Reinitialize and verify that replay recovers to the last valid item. ```rust let (blob, size) = context.open(&partition, &name).await.unwrap(); blob.resize(size - 1).await.unwrap(); blob.sync().await.unwrap(); drop(blob); let journal = Journal::init(context, cfg).await.unwrap(); assert_eq!(journal.size().await.unwrap(), expected_size); ``` ## What to cover - Empty and single-item stores, maximum sizes, and offset overflow. - Restart after a clean sync and after an unclean shutdown. - Truncated, corrupted, and missing data. - Multiple readers or writers and blob pruning. - Metrics for tracked, synced, and pruned data. - Hash-based conformance tests for intentionally stable storage formats. Errors from mutable operations, including `put`, `delete`, and `sync`, are unrecoverable. A caller must not use that database again after such an error.