use proptest::{
collection::vec,
prop_assert, prop_oneof, proptest, strategy,
strategy::Strategy,
test_runner::{Config, TestCaseResult},
};
use std::collections::{HashMap, HashSet};
use metaldb::{
access::{Access, AccessExt},
Database, DatabaseExt, IndexAddress, IndexType, TemporaryDB,
};
mod work;
use self::work::*;
const ACTIONS_MAX_LEN: usize = 20;
#[derive(Debug, Clone)]
enum Action {
WorkOnIndex {
addr: IndexAddress,
index_type: IndexType,
value: Option<Vec<u8>>,
},
FlushFork,
}
fn generate_action() -> impl Strategy<Value = Action> + Clone {
let work = (generate_address(), generate_index_type(), generate_value()).prop_map(
|(addr, index_type, value)| Action::WorkOnIndex {
addr,
index_type,
value,
},
);
prop_oneof![
4 => work,
1 => strategy::Just(Action::FlushFork),
]
}
fn check_index_does_not_exist<S: Access>(snapshot: S, addr: IndexAddress) -> TestCaseResult {
if let Some(index_type) = snapshot.index_type(addr) {
prop_assert!(false, "{:?}", index_type);
}
Ok(())
}
fn apply_actions(
db: &TemporaryDB,
initial_actions: Vec<Action>,
main_actions: Vec<Action>,
) -> TestCaseResult {
let fork = db.fork();
let mut initial_data = HashMap::new();
for action in initial_actions {
if let Action::WorkOnIndex {
addr,
index_type,
value,
} = action
{
let real_type = work_on_index(&fork, addr.clone(), index_type, value.clone());
let entry = initial_data.entry(addr).or_insert_with(|| IndexData {
ty: real_type,
values: vec![],
});
if let Some(value) = value {
entry.values.push(value);
} else {
entry.values.clear();
}
}
}
let patch = fork.into_patch();
for (addr, data) in &initial_data {
data.check(&patch, addr.to_owned())?;
}
db.merge(patch).unwrap();
let mut fork = db.fork();
let mut index_data = initial_data.clone();
let mut new_indexes = HashSet::new();
for action in main_actions {
match action {
Action::WorkOnIndex {
addr,
index_type,
value,
} => {
let real_type = work_on_index(&fork, addr.clone(), index_type, value.clone());
let entry = index_data.entry(addr.clone()).or_insert_with(|| {
new_indexes.insert(addr);
IndexData {
ty: real_type,
values: vec![],
}
});
if let Some(value) = value {
entry.values.push(value);
} else {
entry.values.clear();
}
}
Action::FlushFork => fork.flush(),
}
}
let backup = db.merge_with_backup(fork.into_patch()).unwrap();
let new_snapshot = db.snapshot();
for (addr, data) in &index_data {
data.check(&new_snapshot, addr.to_owned())?;
}
for (addr, data) in &initial_data {
data.check(&backup, addr.to_owned())?;
}
for new_addr in &new_indexes {
check_index_does_not_exist(&backup, new_addr.to_owned())?;
}
db.merge(backup).unwrap();
let snapshot = db.snapshot();
for new_addr in new_indexes {
check_index_does_not_exist(&snapshot, new_addr)?;
}
Ok(())
}
#[test]
fn backup_with_honest_db_initialization() {
let config = Config::with_cases(Config::default().cases / 4);
let actions = vec(generate_action(), 1..ACTIONS_MAX_LEN);
proptest!(config, |(init_actions in actions.clone(), main_actions in actions)| {
let db = TemporaryDB::new();
apply_actions(&db, init_actions, main_actions)?;
});
}
#[test]
fn backup_with_db_clearing() {
let db = TemporaryDB::new();
let actions = vec(generate_action(), 1..ACTIONS_MAX_LEN);
proptest!(|(init_actions in actions.clone(), main_actions in actions)| {
apply_actions(&db, init_actions, main_actions)?;
db.clear().unwrap();
});
}