#![cfg(not(target_arch = "wasm32"))]
use regolith::{Db, Env, MemEnv, Options, WriteBatch};
use std::path::Path;
use std::sync::Arc;
fn mem_options(env: &MemEnv) -> Options {
Options {
env: Arc::new(env.clone()),
max_background_compactions: 0,
write_buffer_size: 4 * 1024,
block_cache_size: 64 * 1024,
target_file_size: 16 * 1024,
level_base_bytes: 64 * 1024,
l0_compaction_trigger: 2,
..Options::default()
}
}
fn open_mem(env: &MemEnv, path: &str) -> Db {
Db::open(Path::new(path), mem_options(env)).expect("open on MemEnv")
}
#[test]
fn full_lifecycle_runs_entirely_in_memory() {
let env = MemEnv::new();
let db = open_mem(&env, "/db");
db.put(b"alpha", b"one").unwrap();
db.put(b"bravo", b"two").unwrap();
db.put(b"charlie", b"three").unwrap();
assert_eq!(db.get(b"bravo").unwrap().as_deref(), Some(&b"two"[..]));
db.delete(b"bravo").unwrap();
assert_eq!(db.get(b"bravo").unwrap(), None);
let mut batch = WriteBatch::new();
batch.put(b"delta", b"four");
batch.delete(b"alpha");
db.write(batch).unwrap();
assert_eq!(db.get(b"alpha").unwrap(), None);
assert_eq!(db.get(b"delta").unwrap().as_deref(), Some(&b"four"[..]));
let snap = db.snapshot();
db.put(b"charlie", b"rewritten").unwrap();
assert_eq!(
snap.get(b"charlie").unwrap().as_deref(),
Some(&b"three"[..])
);
assert_eq!(
db.get(b"charlie").unwrap().as_deref(),
Some(&b"rewritten"[..])
);
drop(snap);
let scanned = db.scan(None, None).unwrap();
let keys: Vec<Vec<u8>> = scanned.iter().map(|(k, _)| k.clone()).collect();
assert_eq!(keys, vec![b"charlie".to_vec(), b"delta".to_vec()]);
let mut iter = db.iter();
iter.seek_to_first();
let mut seen = Vec::new();
while iter.valid() {
seen.push(iter.key().unwrap().to_vec());
iter.next();
}
iter.status().unwrap();
assert_eq!(seen, keys);
for i in 0..2_000u32 {
db.put(format!("k{i:06}").as_bytes(), &[b'v'; 64]).unwrap();
}
assert_eq!(
db.get(b"k001999").unwrap().as_deref(),
Some(&[b'v'; 64][..])
);
db.compact_range(None, None).unwrap();
assert_eq!(
db.get(b"k000042").unwrap().as_deref(),
Some(&[b'v'; 64][..])
);
db.close().unwrap();
drop(db);
assert!(env.file_count() > 0, "the database must live in the MemEnv");
let reopened = open_mem(&env, "/db");
assert_eq!(
reopened.get(b"k000042").unwrap().as_deref(),
Some(&[b'v'; 64][..])
);
assert_eq!(
reopened.get(b"charlie").unwrap().as_deref(),
Some(&b"rewritten"[..])
);
assert_eq!(reopened.get(b"alpha").unwrap(), None, "delete persisted");
reopened.close().unwrap();
}
#[test]
fn nothing_from_a_mem_env_database_reaches_the_real_filesystem() {
let env = MemEnv::new();
let db = open_mem(&env, "/no-such-place/db");
for i in 0..500u32 {
db.put(format!("k{i:04}").as_bytes(), b"v").unwrap();
}
db.close().unwrap();
assert!(!Path::new("/no-such-place").exists());
assert!(env.file_count() > 0);
assert!(env.total_bytes() > 0);
}
#[test]
fn capabilities_report_what_the_environment_actually_provides() {
let env = MemEnv::new();
let db = open_mem(&env, "/db");
let caps = db.capabilities();
assert!(!caps.hard_link);
assert!(!caps.sync_dir);
assert!(!caps.threads);
assert!(!caps.file_lock);
assert!(!caps.durable_sync);
assert!(caps.atomic_rename);
db.close().unwrap();
let std_caps = {
let dir = tempfile::TempDir::new().unwrap();
let db = Db::open(dir.path(), Options::default()).unwrap();
let caps = db.capabilities();
db.close().unwrap();
caps
};
assert!(std_caps.hard_link);
assert!(std_caps.atomic_rename);
assert!(std_caps.durable_sync);
}
#[test]
fn a_checkpoint_copies_bytes_when_the_environment_has_no_hard_links() {
let env = MemEnv::new();
let db = open_mem(&env, "/db");
for i in 0..500u32 {
db.put(format!("k{i:04}").as_bytes(), b"value").unwrap();
}
db.flush().unwrap();
regolith::Checkpoint::new(&db)
.unwrap()
.create("/checkpoint")
.expect("checkpoint must fall back to copying without hard links");
db.close().unwrap();
drop(db);
let restored = open_mem(&env, "/checkpoint");
assert_eq!(
restored.get(b"k0250").unwrap().as_deref(),
Some(&b"value"[..])
);
restored.close().unwrap();
}
#[test]
fn a_ttl_database_is_refused_when_the_environment_has_no_wall_clock() {
let env = MemEnv::new();
env.set_clocks(Some(0), None);
let err = regolith::DbWithTtl::open(Path::new("/ttl"), mem_options(&env), 60)
.expect_err("a TTL database needs a wall clock");
assert!(
err.to_string().contains("wall clock"),
"the error must name the missing clock, got: {err}"
);
}
#[test]
fn a_backup_is_refused_when_the_environment_has_no_wall_clock() {
let env = MemEnv::new();
env.set_clocks(Some(0), None);
let db = open_mem(&env, "/db");
db.put(b"k", b"v").unwrap();
db.flush().unwrap();
let mut backups =
regolith::BackupEngine::open_with_env("/backups", Arc::new(env.clone())).unwrap();
let err = backups
.create_backup(&db)
.expect_err("a backup records when it was taken");
assert!(
err.to_string().contains("wall clock"),
"the error must name the missing clock, got: {err}"
);
db.close().unwrap();
}
#[test]
fn handles_reopen_cleanly_and_leave_no_lock_file_behind() {
let env = MemEnv::new();
let first = open_mem(&env, "/db");
first.put(b"k", b"v").unwrap();
assert!(
Db::open("/db", mem_options(&env)).is_err(),
"a second read-write handle on one directory must be refused"
);
first.close().unwrap();
drop(first);
let second = open_mem(&env, "/db");
assert_eq!(second.get(b"k").unwrap().as_deref(), Some(&b"v"[..]));
second.close().unwrap();
assert!(
!env.exists(Path::new("/db/LOCK")),
"no LOCK file is created where there is no cross-process lock"
);
}