use super::{DurableStore, EngineReadAccountingGuard};
use crate::durability::bridge::block_on;
use crate::durability::tests_fixtures;
const PAGE: usize = 64;
const ROWS: u64 = 256;
const STREAM: &str = "liminal/p0-60/paged-read-shape";
fn seeded() -> Result<impl DurableStore, Box<dyn std::error::Error>> {
tests_fixtures::copied("paged-read")
}
fn read_whole_stream(
store: &impl DurableStore,
page: usize,
) -> Result<usize, Box<dyn std::error::Error>> {
let mut offset = 0_u64;
let mut seen = 0_usize;
loop {
let entries = block_on(store.read_from(STREAM, offset, page))??;
if entries.is_empty() {
return Ok(seen);
}
for entry in &entries {
assert_eq!(entry.sequence, offset, "paged read must stay contiguous");
offset += 1;
}
seen = seen
.checked_add(entries.len())
.ok_or("row counter overflowed")?;
}
}
#[test]
fn a_bounded_read_never_scans_beyond_its_page() -> Result<(), Box<dyn std::error::Error>> {
let store = seeded()?;
let accounting = EngineReadAccountingGuard::start();
let seen = read_whole_stream(&store, PAGE)?;
let walk = accounting.snapshot();
drop(accounting);
assert_eq!(
u64::try_from(seen)?,
ROWS,
"the walk must deliver every row"
);
assert!(
!walk.counter_overflow_observed,
"a saturated counter is not a measurement"
);
assert!(walk.calls > 0, "the walk must have reached the store");
assert_eq!(
u64::try_from(walk.engine_entries)?,
ROWS,
"a full stream read must scan each row exactly once instead of \
re-scanning every suffix once per page"
);
let accounting = EngineReadAccountingGuard::start();
let head = block_on(store.read_from(STREAM, 0, PAGE))??;
let head_read = accounting.snapshot();
drop(accounting);
assert_eq!(head.len(), PAGE, "a full page returns its limit");
assert_eq!(
head_read.engine_entries, PAGE,
"the engine must be asked for one page, not for the whole stream"
);
let middle_offset = ROWS / 2;
let accounting = EngineReadAccountingGuard::start();
let middle = block_on(store.read_from(STREAM, middle_offset, PAGE))??;
let middle_read = accounting.snapshot();
drop(accounting);
assert_eq!(
middle.len(),
PAGE,
"a full page mid-stream returns its limit"
);
assert_eq!(
middle_read.engine_entries, PAGE,
"a mid-stream page must not scan the rows that follow it"
);
let accounting = EngineReadAccountingGuard::start();
let past = block_on(store.read_from(STREAM, ROWS, PAGE))??;
let past_read = accounting.snapshot();
drop(accounting);
assert!(past.is_empty(), "past the head is end of stream");
assert_eq!(
past_read.engine_entries, 0,
"an end-of-stream page must not scan the stream"
);
Ok(())
}
#[test]
fn page_size_never_changes_the_answer() -> Result<(), Box<dyn std::error::Error>> {
let store = seeded()?;
let whole = block_on(store.read_from(STREAM, 0, usize::MAX))??;
assert_eq!(u64::try_from(whole.len())?, ROWS);
for page in [1_usize, 7, 64, 255, 256, 257] {
let mut offset = 0_u64;
let mut collected = Vec::new();
loop {
let entries = block_on(store.read_from(STREAM, offset, page))??;
if entries.is_empty() {
break;
}
assert!(entries.len() <= page, "a page never exceeds its limit");
offset = offset
.checked_add(u64::try_from(entries.len())?)
.ok_or("offset overflowed")?;
collected.extend(entries);
}
assert_eq!(collected, whole, "page size {page} changed the answer");
}
assert!(
block_on(store.read_from(STREAM, 0, 0))??.is_empty(),
"a zero limit reads nothing"
);
for offset in [0_u64, 1, 63, 64, 65, 128, 255] {
let suffix = block_on(store.read_from(STREAM, offset, usize::MAX))??;
assert_eq!(
suffix,
whole[usize::try_from(offset)?..],
"suffix from {offset} diverged"
);
}
Ok(())
}