use std::path::Path;
use page_db::{Lsn, PageError, PageFile, PageFileOptions, PageId, PageSize};
use proptest::prelude::*;
const PAGE_SIZE: usize = 4096;
const PAYLOAD: usize = PAGE_SIZE - 32;
fn open(path: &Path) -> PageFile {
PageFileOptions::new()
.page_size(PageSize::new(PAGE_SIZE).expect("valid"))
.direct_io(false)
.open(path)
.expect("open")
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn write_read_roundtrip(
slot in 0u64..256,
lsn in any::<u64>(),
payload in proptest::collection::vec(any::<u8>(), 0..=PAYLOAD),
) {
let dir = tempfile::tempdir().expect("tempdir");
let file = open(&dir.path().join("p.pages"));
let mut page = file.allocate_page();
page.set_lsn(Lsn::new(lsn));
page.payload_mut()[..payload.len()].copy_from_slice(&payload);
file.write_page(PageId::new(slot), &mut page).expect("write");
let got = file.read_page(PageId::new(slot)).expect("read");
prop_assert_eq!(got.id(), PageId::new(slot));
prop_assert_eq!(got.lsn(), Lsn::new(lsn));
prop_assert_eq!(&got.payload()[..payload.len()], &payload[..]);
}
#[test]
fn single_byte_corruption_is_always_caught(
offset in 0usize..PAGE_SIZE,
xor in 1u8..=255,
) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("p.pages");
{
let file = open(&path);
let mut page = file.allocate_page();
page.payload_mut()[0] = 0xA5;
file.write_page(PageId::new(0), &mut page).expect("write");
file.sync().expect("sync");
}
{
use std::io::{Read, Seek, SeekFrom, Write};
let mut raw = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.expect("reopen");
raw.seek(SeekFrom::Start(offset as u64)).expect("seek");
let mut b = [0u8; 1];
raw.read_exact(&mut b).expect("read");
b[0] ^= xor;
raw.seek(SeekFrom::Start(offset as u64)).expect("seek");
raw.write_all(&b).expect("write");
raw.sync_all().expect("sync");
}
let file = open(&path);
prop_assert!(file.read_page(PageId::new(0)).is_err());
}
#[test]
fn many_pages_no_cross_talk(
markers in proptest::collection::vec(any::<u8>(), 1..32),
) {
let dir = tempfile::tempdir().expect("tempdir");
let file = open(&dir.path().join("p.pages"));
for (slot, &marker) in markers.iter().enumerate() {
let mut page = file.allocate_page();
page.payload_mut()[0] = marker;
page.set_lsn(Lsn::new(slot as u64));
file.write_page(PageId::new(slot as u64), &mut page).expect("write");
}
for (slot, &marker) in markers.iter().enumerate() {
let page = file.read_page(PageId::new(slot as u64)).expect("read");
prop_assert_eq!(page.payload()[0], marker);
prop_assert_eq!(page.lsn(), Lsn::new(slot as u64));
}
}
#[test]
fn read_beyond_end_is_short_read(slot in 1u64..1024) {
let dir = tempfile::tempdir().expect("tempdir");
let file = open(&dir.path().join("p.pages"));
let is_short = matches!(
file.read_page(PageId::new(slot)),
Err(PageError::ShortRead { .. })
);
prop_assert!(is_short);
}
}