use std::time::Duration;
use obj::{Db, Document, Error, LockKind};
use serde::{Deserialize, Serialize};
use tempfile::TempDir;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Note {
body: String,
}
impl Document for Note {
const COLLECTION: &'static str = "notes";
const VERSION: u32 = 1;
}
#[test]
fn open_creates_lock_sidecar() {
let dir = TempDir::new().expect("tmp");
let path = dir.path().join("app.obj");
let lock_path = {
let mut buf = path.as_os_str().to_os_string();
buf.push("-lock");
std::path::PathBuf::from(buf)
};
{
let _db = Db::open(&path).expect("open");
assert!(
lock_path.exists(),
"lock sidecar must exist after Db::open: {}",
lock_path.display(),
);
let meta = std::fs::metadata(&lock_path).expect("sidecar metadata");
assert!(
meta.len() >= 128,
"lock sidecar must be sized to >= 128 bytes so the \
locked byte range exists as real content; got len={}",
meta.len(),
);
}
assert!(
lock_path.exists(),
"lock sidecar must persist across Db close",
);
}
#[test]
fn second_writer_rejected_while_first_holds_lock() {
let dir = TempDir::new().expect("tmp");
let path = dir.path().join("contend.obj");
let db1 = Db::open(&path).expect("first open");
db1.insert(Note {
body: "first".to_owned(),
})
.expect("seed insert");
let db2 = Db::open(&path).expect("second open");
let outcome = db1.transaction(|_tx1| {
let err = db2
.transaction(|_tx2| Ok::<(), Error>(()))
.expect_err("second writer must be refused");
assert!(
matches!(
err,
Error::Busy {
kind: LockKind::Writer | LockKind::WriterInProcess,
},
),
"expected Error::Busy{{Writer|WriterInProcess}}, got {err:?}",
);
Ok::<(), Error>(())
});
outcome.expect("outer txn must commit");
db2.insert(Note {
body: "second".to_owned(),
})
.expect("second writer succeeds once lock released");
}
#[test]
fn writes_past_lock_anchor_offset_succeed() {
let dir = TempDir::new().expect("tmp");
let path = dir.path().join("growable.obj");
let db = Db::open_with(
&path,
obj::Config::default().busy_timeout(Duration::from_secs(5)),
)
.expect("open");
let blob = "x".repeat(2048);
db.transaction(|tx| {
let col = tx.collection::<Note>()?;
for _ in 0..4096u32 {
col.insert(Note { body: blob.clone() })?;
}
Ok::<(), Error>(())
})
.expect("bulk insert must not collide with lock bytes");
}