use rand::seq::SliceRandom;
use rand::SeedableRng;
use seerdb::{DBOptions, DB};
use std::collections::BTreeMap;
use tempfile::TempDir;
fn collect_all(db: &DB) -> BTreeMap<Vec<u8>, Vec<u8>> {
let mut result = BTreeMap::new();
if let Ok(iter) = db.iter() {
for item in iter {
if let Ok((k, v)) = item {
result.insert(k.to_vec(), v.to_vec());
}
}
}
result
}
fn create_db(temp_dir: &TempDir) -> DB {
DBOptions::default()
.background_compaction(false)
.background_flush(false)
.memtable_capacity(4096) .open(temp_dir.path())
.expect("Failed to open database")
}
#[test]
fn test_metamorphic_insert_delete_all_is_empty() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
let keys: Vec<Vec<u8>> = (0..100)
.map(|i| format!("key_{:04}", i).into_bytes())
.collect();
for key in &keys {
db.put(key, b"value").unwrap();
}
for key in &keys {
assert!(db.get(key).unwrap().is_some(), "Key should exist after put");
}
for key in &keys {
db.delete(key).unwrap();
}
for key in &keys {
assert!(
db.get(key).unwrap().is_none(),
"Key should not exist after delete"
);
}
let all = collect_all(&db);
assert!(all.is_empty(), "Database should be empty after delete all");
}
#[test]
fn test_metamorphic_insert_flush_delete_all_is_empty() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
let keys: Vec<Vec<u8>> = (0..100)
.map(|i| format!("key_{:04}", i).into_bytes())
.collect();
for key in &keys {
db.put(key, b"value").unwrap();
}
db.flush().unwrap();
for key in &keys {
db.delete(key).unwrap();
}
db.flush().unwrap();
for key in &keys {
assert!(
db.get(key).unwrap().is_none(),
"Key {} should not exist after delete",
String::from_utf8_lossy(key)
);
}
}
#[test]
fn test_metamorphic_insert_delete_reopen_is_empty() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().to_path_buf();
let keys: Vec<Vec<u8>> = (0..50)
.map(|i| format!("key_{:04}", i).into_bytes())
.collect();
{
let db = create_db(&temp_dir);
for key in &keys {
db.put(key, b"value").unwrap();
}
db.flush().unwrap();
for key in &keys {
db.delete(key).unwrap();
}
db.flush().unwrap();
}
{
let db = DBOptions::default()
.background_compaction(false)
.background_flush(false)
.open(&db_path)
.unwrap();
for key in &keys {
assert!(
db.get(key).unwrap().is_none(),
"Key should not exist after reopen"
);
}
}
}
#[test]
fn test_metamorphic_insertion_order_independent() {
let keys: Vec<(Vec<u8>, Vec<u8>)> = (0..100)
.map(|i| {
(
format!("key_{:04}", i).into_bytes(),
format!("value_{:04}", i).into_bytes(),
)
})
.collect();
let temp_dir1 = TempDir::new().unwrap();
let db1 = create_db(&temp_dir1);
for (k, v) in &keys {
db1.put(k, v).unwrap();
}
db1.flush().unwrap();
let temp_dir2 = TempDir::new().unwrap();
let db2 = create_db(&temp_dir2);
for (k, v) in keys.iter().rev() {
db2.put(k, v).unwrap();
}
db2.flush().unwrap();
let temp_dir3 = TempDir::new().unwrap();
let db3 = create_db(&temp_dir3);
let mut shuffled = keys.clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
shuffled.shuffle(&mut rng);
for (k, v) in &shuffled {
db3.put(k, v).unwrap();
}
db3.flush().unwrap();
let state1 = collect_all(&db1);
let state2 = collect_all(&db2);
let state3 = collect_all(&db3);
assert_eq!(state1, state2, "Sequential vs Reversed differ");
assert_eq!(state1, state3, "Sequential vs Shuffled differ");
}
#[test]
fn test_metamorphic_last_write_wins() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
db.put(b"key", b"value1").unwrap();
db.put(b"key", b"value2").unwrap();
db.put(b"key", b"value3").unwrap();
let result = db.get(b"key").unwrap().unwrap();
assert_eq!(result.as_ref(), b"value3");
db.flush().unwrap();
let result = db.get(b"key").unwrap().unwrap();
assert_eq!(result.as_ref(), b"value3");
}
#[test]
fn test_metamorphic_flush_preserves_results() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
for i in 0..100 {
let key = format!("key_{:04}", i);
let value = format!("value_{:04}", i);
db.put(key.as_bytes(), value.as_bytes()).unwrap();
}
let before_flush = collect_all(&db);
db.flush().unwrap();
let after_flush = collect_all(&db);
assert_eq!(before_flush, after_flush, "Flush changed query results");
}
#[test]
fn test_metamorphic_multiple_flushes_idempotent() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
for i in 0..50 {
db.put(
format!("key_{:04}", i).as_bytes(),
format!("value_{:04}", i).as_bytes(),
)
.unwrap();
}
db.flush().unwrap();
let after_first = collect_all(&db);
db.flush().unwrap();
let after_second = collect_all(&db);
db.flush().unwrap();
let after_third = collect_all(&db);
assert_eq!(after_first, after_second);
assert_eq!(after_second, after_third);
}
#[test]
fn test_metamorphic_compaction_preserves_results() {
let temp_dir = TempDir::new().unwrap();
let db = DBOptions::default()
.background_compaction(false)
.background_flush(false)
.memtable_capacity(1024) .open(temp_dir.path())
.unwrap();
for batch in 0..5 {
for i in 0..20 {
let key = format!("key_{:02}_{:04}", batch, i);
let value = format!("value_{:02}_{:04}", batch, i);
db.put(key.as_bytes(), value.as_bytes()).unwrap();
}
db.flush().unwrap();
}
let before_more_data = collect_all(&db);
for batch in 5..10 {
for i in 0..20 {
let key = format!("key_{:02}_{:04}", batch, i);
let value = format!("value_{:02}_{:04}", batch, i);
db.put(key.as_bytes(), value.as_bytes()).unwrap();
}
db.flush().unwrap();
}
let after_more_data = collect_all(&db);
for (key, value) in &before_more_data {
assert_eq!(
after_more_data.get(key),
Some(value),
"Original key {:?} was lost or corrupted after adding more data",
String::from_utf8_lossy(key)
);
}
for batch in 0..5 {
for i in 0..20 {
let key = format!("key_{:02}_{:04}", batch, i);
let expected = format!("value_{:02}_{:04}", batch, i);
let result = db.get(key.as_bytes()).unwrap();
assert_eq!(
result.as_ref().map(|b| b.as_ref()),
Some(expected.as_bytes()),
"Key {} has wrong value after compaction",
key
);
}
}
}
#[test]
fn test_metamorphic_reopen_preserves_results() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().to_path_buf();
let expected: BTreeMap<Vec<u8>, Vec<u8>>;
{
let db = create_db(&temp_dir);
for i in 0..100 {
db.put(
format!("key_{:04}", i).as_bytes(),
format!("value_{:04}", i).as_bytes(),
)
.unwrap();
}
db.flush().unwrap();
expected = collect_all(&db);
}
{
let db = DBOptions::default()
.background_compaction(false)
.background_flush(false)
.open(&db_path)
.unwrap();
let actual = collect_all(&db);
assert_eq!(expected, actual, "Data changed after reopen");
}
}
#[test]
fn test_metamorphic_get_nonexistent_always_none() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
assert!(db.get(b"nonexistent").unwrap().is_none());
db.put(b"key1", b"value1").unwrap();
db.put(b"key2", b"value2").unwrap();
assert!(db.get(b"nonexistent").unwrap().is_none());
db.flush().unwrap();
assert!(db.get(b"nonexistent").unwrap().is_none());
db.delete(b"key1").unwrap();
assert!(db.get(b"nonexistent").unwrap().is_none());
assert!(db.get(b"key1").unwrap().is_none()); }
#[test]
fn test_metamorphic_empty_value_vs_none() {
let temp_dir = TempDir::new().unwrap();
let db = create_db(&temp_dir);
db.put(b"empty_key", b"").unwrap();
let result = db.get(b"empty_key").unwrap();
assert!(result.is_some(), "Empty value should be Some, not None");
assert_eq!(result.unwrap().as_ref(), b"");
assert!(db.get(b"nonexistent").unwrap().is_none());
db.flush().unwrap();
let result = db.get(b"empty_key").unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().as_ref(), b"");
}