use std::io::{BufRead, BufReader};
use std::process::Command;
use tempfile::tempdir;
use vantadb::config::VantaConfig;
use vantadb::error::VantaError;
use vantadb::storage::StorageEngine;
#[test]
fn test_exclusive_writer_lock_prevents_second_writer() {
let dir = tempdir().expect("Failed to create temp directory");
let path_str = dir.path().to_str().unwrap();
let config1 = VantaConfig {
read_only: false,
..Default::default()
};
let _engine1 = StorageEngine::open_with_config(path_str, Some(config1))
.expect("First writer should open without issues");
let config2 = VantaConfig {
read_only: false,
..Default::default()
};
let engine2_res = StorageEngine::open_with_config(path_str, Some(config2));
assert!(
engine2_res.is_err(),
"Second writer should NOT be able to open the locked database"
);
match engine2_res.err().unwrap() {
VantaError::DatabaseBusy(msg) => {
assert!(
msg.contains("locked by another process"),
"Expected 'locked by another process' in error message, got: {}",
msg
);
}
other => panic!(
"Expected VantaError::DatabaseBusy for lock failure, got: {:?}",
other
),
}
drop(_engine1);
let config3 = VantaConfig {
read_only: false,
..Default::default()
};
let engine3_res = StorageEngine::open_with_config(path_str, Some(config3));
assert!(
engine3_res.is_ok(),
"Should be able to open after releasing the lock"
);
}
#[test]
fn test_exclusive_writer_lock_prevents_second_writer_multi_process() {
let dir = tempdir().expect("Failed to create temp directory");
let path_str = dir.path().to_str().unwrap();
let helper_path = env!("CARGO_BIN_EXE_lock_helper");
let mut p1 = Command::new(helper_path)
.arg(path_str)
.arg("5000") .stdout(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn lock_helper P1");
let stdout = p1.stdout.take().expect("Failed to open stdout of P1");
let mut reader = BufReader::new(stdout);
let mut first_line = String::new();
reader
.read_line(&mut first_line)
.expect("Failed to read from P1");
assert!(
first_line.contains("LOCK_HELPER: SUCCESS_LOCK"),
"Expected P1 to lock database successfully, got: {}",
first_line
);
let config2 = VantaConfig {
read_only: false,
..Default::default()
};
let engine_res = StorageEngine::open_with_config(path_str, Some(config2));
assert!(
engine_res.is_err(),
"Current process should NOT be able to open the database while P1 holds the lock"
);
if let Err(VantaError::DatabaseBusy(msg)) = engine_res {
assert!(
msg.contains("locked by another process"),
"Expected lock failure message, got: {}",
msg
);
} else {
panic!("Expected VantaError::DatabaseBusy for lock failure");
}
let p2_output = Command::new(helper_path)
.arg(path_str)
.arg("1000")
.output()
.expect("Failed to execute lock_helper P2");
assert_eq!(
p2_output.status.code(),
Some(2),
"P2 should fail with exit code 2 due to locking conflict"
);
let p2_stdout = String::from_utf8_lossy(&p2_output.stdout);
assert!(
p2_stdout.contains("LOCK_HELPER: FAILED_LOCK"),
"P2 stdout should report lock failure, got: {}",
p2_stdout
);
p1.kill().expect("Failed to kill P1");
let _ = p1.wait();
let config3 = VantaConfig {
read_only: false,
..Default::default()
};
let engine3_res = StorageEngine::open_with_config(path_str, Some(config3));
assert!(
engine3_res.is_ok(),
"Should be able to open database after P1 was killed and the lock was released"
);
}