use kernel::io::IoMode;
use kernel::store::{Config, Store, SyncMode};
fn cfg() -> Config { Config { budget_bytes: 32 << 20, io: IoMode::Buffered, sync: SyncMode::Full } }
#[test]
fn a_committed_write_is_there_after_reopening() {
let d = tempfile::tempdir().unwrap();
{ let mut s = Store::create(d.path(), cfg()).unwrap();
for i in 0..5000u64 { s.put(&i.to_be_bytes(), &i.to_le_bytes()).unwrap(); }
s.commit().unwrap(); }
let s = Store::open(d.path(), cfg()).unwrap();
for i in 0..5000u64 {
assert_eq!(s.get(&i.to_be_bytes()).unwrap().as_deref(), Some(&i.to_le_bytes()[..]));
}
}
#[test]
fn an_uncommitted_write_is_not_there_after_reopening() {
let d = tempfile::tempdir().unwrap();
{ let mut s = Store::create(d.path(), cfg()).unwrap();
s.put(b"committed", b"1").unwrap();
s.commit().unwrap();
s.put(b"dangling", b"2").unwrap(); drop(s); }
let s = Store::open(d.path(), cfg()).unwrap();
assert_eq!(s.get(b"committed").unwrap().as_deref(), Some(&b"1"[..]));
assert_eq!(s.get(b"dangling").unwrap(), None);
}
#[test]
fn a_rejected_oversized_put_has_no_effect_on_a_commit_right_after_it() {
let d = tempfile::tempdir().unwrap();
let mut s = Store::create(d.path(), cfg()).unwrap();
s.put(b"a", b"1").unwrap();
s.commit().unwrap();
let oversized = vec![9u8; 20_000];
s.put(b"oversized", &oversized).unwrap();
let huge_key = vec![b'k'; kernel::page::MAX_RECORD_LEN];
match s.put(&huge_key, b"v") {
Err(kernel::Error::TooLarge) => {}
other => panic!("expected Err(TooLarge), got {other:?}"),
}
s.put(b"z", b"2").unwrap();
s.commit().unwrap();
drop(s);
let s2 = Store::open(d.path(), cfg()).unwrap();
assert_eq!(s2.get(b"a").unwrap().as_deref(), Some(&b"1"[..]));
assert_eq!(
s2.get(b"z").unwrap().as_deref(), Some(&b"2"[..]),
"a row committed right after a rejected oversized put must survive a crash -- the \
rejected attempt must not have touched the WAL at all"
);
assert_eq!(s2.get(b"oversized").unwrap().as_deref(), Some(&vec![9u8; 20_000][..]));
}
#[test]
fn a_checkpoint_places_the_barrier_its_mode_calls_for() {
for (mode, want_data, want_full) in [
(SyncMode::Full, 0u64, 2u64),
(SyncMode::Normal, 2, 0),
(SyncMode::Off, 0, 0),
] {
let d = tempfile::tempdir().unwrap();
let cfg = Config { budget_bytes: 16 << 20, io: IoMode::Buffered, sync: mode };
let mut s = Store::create(d.path(), cfg).unwrap();
let before = s.pool_stats();
s.put(b"k", b"v").unwrap();
s.commit().unwrap();
s.checkpoint().unwrap();
let after = s.pool_stats();
assert_eq!(after.sync_data_calls - before.sync_data_calls, want_data,
"{mode:?}: wrong number of data barriers at checkpoint");
assert_eq!(after.sync_full_calls - before.sync_full_calls, want_full,
"{mode:?}: wrong number of full barriers at checkpoint");
}
}
#[test]
fn reopening_after_a_checkpoint_finds_everything() {
let d = tempfile::tempdir().unwrap();
{ let mut s = Store::create(d.path(), cfg()).unwrap();
for i in 0..20_000u64 { s.put(&i.to_be_bytes(), b"v").unwrap(); }
s.commit().unwrap(); s.checkpoint().unwrap();
for i in 20_000..21_000u64 { s.put(&i.to_be_bytes(), b"v").unwrap(); }
s.commit().unwrap(); }
let s = Store::open(d.path(), cfg()).unwrap();
assert_eq!(s.scan(&[]).unwrap().count(), 21_000);
}
#[test]
fn an_abandoned_write_never_becomes_committed_by_a_later_commit() {
let dir = tempfile::tempdir().unwrap();
let cfg = || Config {
budget_bytes: 1 << 20,
io: kernel::io::IoMode::Buffered,
sync: kernel::store::SyncMode::Full,
};
{
let mut store = Store::open(dir.path(), cfg()).unwrap();
store.put(b"k", b"old").unwrap();
store.commit().unwrap();
store.put(b"k", &vec![7u8; 300 * 1024]).unwrap();
}
{
let mut store = Store::open(dir.path(), cfg()).unwrap();
assert_eq!(store.get(b"k").unwrap().as_deref(), Some(&b"old"[..]));
store.put(b"other", b"x").unwrap();
store.commit().unwrap();
}
let store = Store::open(dir.path(), cfg()).unwrap();
assert_eq!(
store.get(b"k").unwrap().as_deref(),
Some(&b"old"[..]),
"the abandoned overwrite came back after an unrelated commit"
);
assert_eq!(store.get(b"other").unwrap().as_deref(), Some(&b"x"[..]));
}