use std::path::{Path, PathBuf};
use super::Db;
use super::block_cache::BlockKey;
use super::flush_tests::{PATH, key, open, value};
use crate::log::fault::FaultStorage;
use crate::storage::Storage as _;
use crate::{DbOptions, Error, KeyspaceId, ReadOptions, StorageMode, WriteBatch, WriteOptions};
use tessarikv_types::{SequenceNumber, encode_internal, seek_trailer};
async fn flushed(db: &Db) {
for index in 0..10 {
let mut batch = WriteBatch::new();
batch
.put(KeyspaceId::DEFAULT, &key(index), &value(index))
.expect("entry");
db.write(&batch, &WriteOptions::default())
.await
.expect("written");
}
db.flush().await.expect("flushed");
}
fn tables(storage: &FaultStorage) -> Vec<PathBuf> {
storage
.list(Path::new(PATH))
.expect("listable")
.into_iter()
.filter(|name| name.ends_with(".sst"))
.map(|name| Path::new(PATH).join(name))
.collect()
}
fn damage(storage: &FaultStorage, path: &Path, at: usize) {
let mut bytes = storage.bytes(path);
let byte = bytes.get_mut(at).expect("inside the file");
*byte ^= 0xff;
let mut file = storage.create(path).expect("replaced");
file.append(&bytes).expect("written");
}
#[tokio::test]
async fn a_whole_database_verifies() {
let storage = FaultStorage::default();
let db = open(&storage).await;
flushed(&db).await;
assert!(!tables(&storage).is_empty(), "a table file was written");
db.verify_checksums().await.expect("every block verifies");
}
#[tokio::test]
async fn a_damaged_block_on_storage_is_found_even_while_its_copy_is_cached() {
let storage = FaultStorage::default();
let db = open(&storage).await;
flushed(&db).await;
let read = db
.get(KeyspaceId::DEFAULT, &key(5), &ReadOptions::default())
.await
.expect("read");
assert_eq!(read, Some(value(5)));
let [table] = tables(&storage).try_into().expect("one table file");
let mut seek = Vec::new();
encode_internal(&key(5), seek_trailer(SequenceNumber::MAX), &mut seek);
let view = db.view.load();
let file = view.all_files().first().expect("a table");
let offset = file.table.offset_of(&seek).expect("indexed");
let installer = db.installer.as_ref().expect("on storage");
let block = BlockKey {
file: file.meta.id,
offset,
};
assert!(
installer.cache.get(block).is_some(),
"the damaged block's good copy is cached"
);
damage(
&storage,
&table,
usize::try_from(offset).expect("small") + 1,
);
let verified = db.verify_checksums().await;
match verified {
Err(Error::Corruption {
what: "table",
path: Some(path),
..
}) => assert_eq!(path, table),
other => panic!("expected the damaged table, got {other:?}"),
}
}
#[tokio::test]
async fn a_database_in_memory_has_nothing_to_verify() {
let options = DbOptions::builder()
.storage(StorageMode::Memory)
.build()
.expect("valid");
let db = Db::open("verify", options).await.expect("opened");
db.verify_checksums().await.expect("nothing to verify");
}