use std::path::PathBuf;
use emdb::{Emdb, Result};
fn tmp_path(label: &str) -> PathBuf {
let mut p = std::env::temp_dir();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0_u128, |d| d.as_nanos());
let tid = std::thread::current().id();
p.push(format!("emdb-checkpoint-{label}-{nanos}-{tid:?}.emdb"));
p
}
fn cleanup(path: &PathBuf) {
let _ = std::fs::remove_file(path);
let display = path.display();
let _ = std::fs::remove_file(format!("{display}.lock"));
let _ = std::fs::remove_file(format!("{display}.compact.tmp"));
}
#[test]
fn checkpoint_on_fresh_database_is_a_noop_for_observable_state() -> Result<()> {
let path = tmp_path("fresh");
cleanup(&path);
let db = Emdb::open(&path)?;
db.checkpoint()?;
assert_eq!(db.len()?, 0);
assert!(db.is_empty()?);
drop(db);
cleanup(&path);
Ok(())
}
#[test]
fn checkpoint_after_inserts_lets_reopen_skip_the_log() -> Result<()> {
let path = tmp_path("after-inserts");
cleanup(&path);
{
let db = Emdb::open(&path)?;
db.insert("k1", "v1")?;
db.insert("k2", "v2")?;
db.insert("k3", "v3")?;
db.flush()?;
db.checkpoint()?;
}
let reopened = Emdb::open(&path)?;
assert_eq!(reopened.get("k1")?, Some(b"v1".to_vec()));
assert_eq!(reopened.get("k2")?, Some(b"v2".to_vec()));
assert_eq!(reopened.get("k3")?, Some(b"v3".to_vec()));
assert_eq!(reopened.len()?, 3);
drop(reopened);
cleanup(&path);
Ok(())
}
#[test]
fn repeated_checkpoint_is_idempotent() -> Result<()> {
let path = tmp_path("idempotent");
cleanup(&path);
let db = Emdb::open(&path)?;
db.insert("k", "v")?;
db.flush()?;
for _ in 0..6 {
db.checkpoint()?;
}
drop(db);
let reopened = Emdb::open(&path)?;
assert_eq!(reopened.get("k")?, Some(b"v".to_vec()));
drop(reopened);
cleanup(&path);
Ok(())
}
#[test]
fn drop_time_backstop_persists_header_when_checkpoint_was_never_called() -> Result<()> {
let path = tmp_path("drop-backstop");
cleanup(&path);
{
let db = Emdb::open(&path)?;
db.insert("user:1", "alice")?;
db.insert("user:2", "bob")?;
db.flush()?;
}
let reopened = Emdb::open(&path)?;
assert_eq!(reopened.get("user:1")?, Some(b"alice".to_vec()));
assert_eq!(reopened.get("user:2")?, Some(b"bob".to_vec()));
drop(reopened);
cleanup(&path);
Ok(())
}
#[test]
fn checkpoint_round_trip_preserves_every_record() -> Result<()> {
let path = tmp_path("round-trip");
cleanup(&path);
const N: u32 = 5_000;
{
let db = Emdb::open(&path)?;
let pairs: Vec<(String, String)> = (0..N)
.map(|i| (format!("key:{i:06}"), format!("value-{i}")))
.collect();
db.insert_many(pairs.iter().map(|(k, v)| (k.as_str(), v.as_str())))?;
db.flush()?;
db.checkpoint()?;
}
let db = Emdb::open(&path)?;
assert_eq!(db.len()?, N as usize);
for i in 0..N {
let key = format!("key:{i:06}");
let want = format!("value-{i}");
assert_eq!(
db.get(&key)?,
Some(want.into_bytes()),
"record {i} missing after reopen"
);
}
drop(db);
cleanup(&path);
Ok(())
}