use crate::log_base::{LogHead, prev_path, read_log_head, snapshot_id};
use crate::tests_rewrite::apply_for_test;
use crate::{Aof, Argv, Fsync, load_snapshot, replay_aof, settle_snapshot, write_snapshot_tmp};
use kevy_store::Store;
use std::path::{Path, PathBuf};
fn rpush(store: &mut Store, aof: &mut Aof, item: &[u8]) {
store.rpush(b"l", &[item]).unwrap();
aof.append(&Argv::from(vec![b"RPUSH".to_vec(), b"l".to_vec(), item.to_vec()])).unwrap();
}
fn restore(snap: &Path, log: &Path) -> Vec<Vec<u8>> {
let mut store = Store::new();
if settle_snapshot(snap, Some(log)).unwrap() {
load_snapshot(&mut store, snap).unwrap();
}
replay_aof(log, |args| apply_for_test(&mut store, &args)).unwrap();
store.lrange(b"l", 0, -1).unwrap()
}
fn files(name: &str) -> (PathBuf, PathBuf, PathBuf) {
let dir = kevy_tmpdir::unique_dir(name);
(dir.join("dump-0.rdb"), dir.join("aof-0.aof"), dir)
}
fn save(store: &Store, aof: &mut Aof, snap: &Path) {
let reset = aof.begin_view_rewrite().unwrap();
let tmp = write_snapshot_tmp(store, snap).unwrap();
aof.commit_snapshot(&tmp, snap, &reset).unwrap();
}
fn items(s: &[&str]) -> Vec<Vec<u8>> {
s.iter().map(|i| i.as_bytes().to_vec()).collect()
}
#[test]
fn a_rewrite_after_a_snapshot_restores_each_write_once() {
let (snap, log, dir) = files("pair-save-rewrite");
let mut store = Store::new();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
for i in ["a", "b", "c"] {
rpush(&mut store, &mut aof, i.as_bytes());
}
save(&store, &mut aof, &snap);
rpush(&mut store, &mut aof, b"d");
aof.rewrite_from(&store).unwrap();
rpush(&mut store, &mut aof, b"e");
drop(aof);
assert_eq!(read_log_head(&log).unwrap(), LogHead::StandsAlone);
assert_eq!(restore(&snap, &log), items(&["a", "b", "c", "d", "e"]));
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_snapshot_after_a_rewrite_restores_each_write_once() {
let (snap, log, dir) = files("pair-rewrite-save");
let mut store = Store::new();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
for i in ["a", "b"] {
rpush(&mut store, &mut aof, i.as_bytes());
}
aof.rewrite_from(&store).unwrap();
rpush(&mut store, &mut aof, b"c");
save(&store, &mut aof, &snap);
rpush(&mut store, &mut aof, b"d");
drop(aof);
let id = snapshot_id(&snap).unwrap().expect("a committed snapshot carries its id");
assert_eq!(read_log_head(&log).unwrap(), LogHead::After(id));
assert_eq!(restore(&snap, &log), items(&["a", "b", "c", "d"]));
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_crash_before_the_log_reset_restores_the_previous_pair() {
let (snap, log, dir) = files("pair-crash-mid");
let mut store = Store::new();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
rpush(&mut store, &mut aof, b"a");
save(&store, &mut aof, &snap);
rpush(&mut store, &mut aof, b"b");
aof.sync_now().unwrap();
let tmp = write_snapshot_tmp(&store, &snap).unwrap();
std::fs::rename(&snap, prev_path(&snap)).unwrap();
std::fs::rename(&tmp, &snap).unwrap();
drop(aof);
assert_eq!(restore(&snap, &log), items(&["a", "b"]));
assert!(!prev_path(&snap).exists(), "the kept snapshot is back in place");
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_crash_after_the_log_reset_keeps_the_new_pair() {
let (snap, log, dir) = files("pair-crash-late");
let mut store = Store::new();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
rpush(&mut store, &mut aof, b"a");
save(&store, &mut aof, &snap);
let stale = std::fs::read(&snap).unwrap();
rpush(&mut store, &mut aof, b"b");
save(&store, &mut aof, &snap);
rpush(&mut store, &mut aof, b"c");
drop(aof);
std::fs::write(prev_path(&snap), stale).unwrap();
assert_eq!(restore(&snap, &log), items(&["a", "b", "c"]));
assert!(!prev_path(&snap).exists());
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_log_whose_snapshot_is_not_there_is_refused() {
let (snap, log, dir) = files("pair-missing");
let mut store = Store::new();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
rpush(&mut store, &mut aof, b"a");
save(&store, &mut aof, &snap);
drop(aof);
let other = dir.join("other.rdb");
crate::save_snapshot(&store, &other).unwrap();
std::fs::rename(&other, &snap).unwrap();
let err = settle_snapshot(&snap, Some(&log)).unwrap_err();
assert!(err.to_string().contains("is snapshot"), "{err}");
std::fs::remove_file(&snap).unwrap();
let err = settle_snapshot(&snap, Some(&log)).unwrap_err();
assert!(err.to_string().contains("is missing"), "{err}");
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn a_log_without_a_base_restores_over_the_snapshot() {
let (snap, log, dir) = files("pair-legacy");
let mut store = Store::new();
store.rpush(b"l", &[b"a"]).unwrap();
crate::save_snapshot(&store, &snap).unwrap();
let mut aof = Aof::open(&log, Fsync::No).unwrap();
rpush(&mut store, &mut aof, b"b");
drop(aof);
assert_eq!(read_log_head(&log).unwrap(), LogHead::Legacy);
assert_eq!(restore(&snap, &log), items(&["a", "b"]));
assert!(settle_snapshot(&snap, None).unwrap());
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn the_base_frame_is_not_replayed() {
let (_, log, dir) = files("pair-replay");
let mut store = Store::new();
store.rpush(b"l", &[b"a"]).unwrap();
Aof::open(&log, Fsync::No).unwrap().rewrite_from(&store).unwrap();
let mut seen = Vec::new();
let report = replay_aof(&log, |args| seen.push(args[0].to_vec())).unwrap();
assert_eq!(seen, [b"RPUSH".to_vec()]);
assert_eq!(report.commands, 1);
std::fs::remove_dir_all(&dir).unwrap();
}