use infigraph_core::graph::GraphStore;
use std::time::{Duration, Instant};
use tempfile::TempDir;
fn make_store() -> (TempDir, GraphStore) {
let dir = TempDir::new().unwrap();
let db_path = dir.path().join("test.db");
let store = GraphStore::open(&db_path).unwrap();
(dir, store)
}
#[test]
fn test_write_lock_acquire_and_release() {
let (_dir, store) = make_store();
let lock = store.write_lock().unwrap();
drop(lock);
let lock2 = store.write_lock().unwrap();
drop(lock2);
}
#[test]
fn test_try_write_lock_returns_none_when_held() {
let (_dir, store) = make_store();
let _lock = store.write_lock().unwrap();
let result = store.try_write_lock().unwrap();
assert!(
result.is_none(),
"try_write_lock should return None when lock is held"
);
}
#[test]
fn test_try_write_lock_succeeds_when_free() {
let (_dir, store) = make_store();
let result = store.try_write_lock().unwrap();
assert!(
result.is_some(),
"try_write_lock should return Some when lock is free"
);
}
#[test]
fn test_write_lock_released_on_drop() {
let (_dir, store) = make_store();
{
let _lock = store.write_lock().unwrap();
}
let result = store.try_write_lock().unwrap();
assert!(result.is_some(), "lock should be released after drop");
}
#[test]
fn test_write_lock_no_perf_impact() {
let (_dir, store) = make_store();
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
let _lock = store.write_lock().unwrap();
}
let elapsed = start.elapsed();
let per_op = elapsed / iterations;
assert!(
per_op < Duration::from_millis(1),
"lock acquire+release should take < 1ms, took {:?}",
per_op
);
}
#[test]
fn test_write_lock_cross_thread_blocking() {
let dir = TempDir::new().unwrap();
let db_path = dir.path().join("cross_thread.db");
let store = GraphStore::open(&db_path).unwrap();
let lock = store.write_lock().unwrap();
let lock_path = db_path.with_extension("lock");
let handle = std::thread::spawn(move || {
let file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock_path)
.unwrap();
use fs2::FileExt;
let result = file.try_lock_exclusive();
matches!(result, Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock || e.raw_os_error() == Some(33))
});
let was_blocked = handle.join().unwrap();
assert!(was_blocked, "second thread should be blocked by held lock");
drop(lock);
}
#[test]
fn test_write_lock_different_stores_same_path() {
let dir = TempDir::new().unwrap();
let lock_path = dir.path().join("shared.lock");
use fs2::FileExt;
let file1 = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock_path)
.unwrap();
file1.lock_exclusive().unwrap();
let file2 = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock_path)
.unwrap();
let result = file2.try_lock_exclusive();
assert!(
result.is_err(),
"second fd should fail to lock when first holds it"
);
drop(file1);
}