use powdb_storage::buffer::BufferPool;
use powdb_storage::page::PageType;
fn temp_pool(name: &str, capacity: usize) -> (BufferPool, std::path::PathBuf) {
let path = std::env::temp_dir().join(format!(
"powdb_bp_integ_{name}_{}_{:?}",
std::process::id(),
std::time::Instant::now()
));
let pool = BufferPool::new(&path, capacity).unwrap();
(pool, path)
}
#[test]
fn test_new_page_returns_unique_ids() {
let (mut pool, path) = temp_pool("unique_ids", 10);
let mut ids = Vec::new();
for _ in 0..5 {
ids.push(pool.new_page(PageType::Data).unwrap());
}
ids.sort();
ids.dedup();
assert_eq!(ids.len(), 5);
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_write_and_read_back() {
let (mut pool, path) = temp_pool("write_read", 10);
let pid = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(pid).unwrap();
page.insert(b"hello buffer pool");
}
pool.mark_dirty(pid);
pool.flush_all().unwrap();
let page = pool.get_page(pid).unwrap();
assert_eq!(page.get(0).unwrap(), b"hello buffer pool");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_multiple_slots_on_page() {
let (mut pool, path) = temp_pool("multi_slot", 10);
let pid = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(pid).unwrap();
page.insert(b"row A");
page.insert(b"row B");
page.insert(b"row C");
}
pool.mark_dirty(pid);
pool.flush_all().unwrap();
let page = pool.get_page(pid).unwrap();
assert_eq!(page.get(0).unwrap(), b"row A");
assert_eq!(page.get(1).unwrap(), b"row B");
assert_eq!(page.get(2).unwrap(), b"row C");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_eviction_reloads_from_disk() {
let (mut pool, path) = temp_pool("evict_reload", 3);
let mut ids = Vec::new();
for i in 0..6 {
let pid = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(pid).unwrap();
page.insert(format!("data-{i}").as_bytes());
}
pool.mark_dirty(pid);
ids.push(pid);
}
pool.flush_all().unwrap();
let page = pool.get_page(ids[0]).unwrap();
assert_eq!(page.get(0).unwrap(), b"data-0");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_dirty_eviction_persists() {
let (mut pool, path) = temp_pool("dirty_evict_persist", 2);
let p0 = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(p0).unwrap();
page.insert(b"must survive eviction");
}
pool.mark_dirty(p0);
let _p1 = pool.new_page(PageType::Data).unwrap();
let _p2 = pool.new_page(PageType::Data).unwrap();
let page = pool.get_page(p0).unwrap();
assert_eq!(page.get(0).unwrap(), b"must survive eviction");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_pinned_page_survives_pressure() {
let (mut pool, path) = temp_pool("pin_survive", 3);
let p0 = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(p0).unwrap();
page.insert(b"pinned data");
}
pool.mark_dirty(p0);
pool.pin(p0);
for _ in 0..5 {
let _ = pool.new_page(PageType::Data);
}
let page = pool.get_page(p0).unwrap();
assert_eq!(page.get(0).unwrap(), b"pinned data");
pool.unpin(p0);
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_all_pinned_returns_error() {
let (mut pool, path) = temp_pool("all_pinned", 2);
let p0 = pool.new_page(PageType::Data).unwrap();
let p1 = pool.new_page(PageType::Data).unwrap();
pool.pin(p0);
pool.pin(p1);
let result = pool.new_page(PageType::Data);
assert!(result.is_err(), "should fail when all pages are pinned");
pool.unpin(p0);
pool.unpin(p1);
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_unpin_saturates() {
let (mut pool, path) = temp_pool("unpin_saturate", 4);
let p0 = pool.new_page(PageType::Data).unwrap();
pool.unpin(p0);
pool.unpin(p0);
let _p1 = pool.new_page(PageType::Data).unwrap();
let _p2 = pool.new_page(PageType::Data).unwrap();
let _p3 = pool.new_page(PageType::Data).unwrap();
let _p4 = pool.new_page(PageType::Data).unwrap();
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_flush_single_page() {
let (mut pool, path) = temp_pool("flush_single", 10);
let p0 = pool.new_page(PageType::Data).unwrap();
let p1 = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(p0).unwrap();
page.insert(b"page zero");
}
pool.mark_dirty(p0);
{
let page = pool.get_page_mut(p1).unwrap();
page.insert(b"page one");
}
pool.mark_dirty(p1);
pool.flush_page(p0).unwrap();
assert_eq!(pool.get_page(p0).unwrap().get(0).unwrap(), b"page zero");
assert_eq!(pool.get_page(p1).unwrap().get(0).unwrap(), b"page one");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_capacity_one() {
let (mut pool, path) = temp_pool("cap1", 1);
let p0 = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(p0).unwrap();
page.insert(b"only page");
}
pool.mark_dirty(p0);
let p1 = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(p1).unwrap();
page.insert(b"second page");
}
pool.mark_dirty(p1);
let page = pool.get_page(p0).unwrap();
assert_eq!(page.get(0).unwrap(), b"only page");
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_mark_dirty_missing_page_no_panic() {
let (mut pool, path) = temp_pool("mark_missing", 4);
pool.mark_dirty(9999);
drop(pool);
std::fs::remove_file(&path).ok();
}
#[test]
fn test_reopen_existing_file() {
let path = std::env::temp_dir().join(format!("powdb_bp_reopen_{}", std::process::id()));
let _ = std::fs::remove_file(&path);
let pid;
{
let mut pool = BufferPool::new(&path, 10).unwrap();
pid = pool.new_page(PageType::Data).unwrap();
{
let page = pool.get_page_mut(pid).unwrap();
page.insert(b"persistent");
}
pool.mark_dirty(pid);
pool.flush_all().unwrap();
}
{
let mut pool = BufferPool::new(&path, 10).unwrap();
let page = pool.get_page(pid).unwrap();
assert_eq!(page.get(0).unwrap(), b"persistent");
}
std::fs::remove_file(&path).ok();
}