use kevy_embedded::{Config, Store};
fn reply(s: &Store, argv: &[&[u8]]) -> Vec<u8> {
let owned: Vec<Vec<u8>> = argv.iter().map(|a| a.to_vec()).collect();
let mut out = Vec::new();
s.dispatch_argv(&owned, &mut out);
out
}
enum Cmp {
Exact,
Shape,
}
use Cmp::{Exact, Shape};
struct Case {
argv: Vec<Vec<u8>>,
cmp: Cmp,
demote: &'static [&'static [u8]],
}
fn first_count(reply: &[u8]) -> i64 {
let nl = reply.windows(2).position(|w| w == b"\r\n").expect("resp header");
std::str::from_utf8(&reply[1..nl]).unwrap_or("0").parse().unwrap_or(0)
}
fn assert_match(what: &str, cmp: &Cmp, a: &[u8], b: &[u8]) {
match cmp {
Exact => assert_eq!(
a,
b,
"transparency broken for `{what}`: {:?} vs {:?}",
String::from_utf8_lossy(a),
String::from_utf8_lossy(b)
),
Shape => {
assert_eq!(a.first(), b.first(), "reply kind differs for `{what}`");
if a.first() == Some(&b'*') {
assert_eq!(first_count(a), first_count(b), "element count differs for `{what}`");
}
}
}
}
fn cases() -> Vec<Case> {
fn c(argv: &[&[u8]], cmp: Cmp, demote: &'static [&'static [u8]]) -> Case {
Case { argv: argv.iter().map(|a| a.to_vec()).collect(), cmp, demote }
}
const NONE: &[&[u8]] = &[];
vec![
c(&[b"SET", b"s:small", b"v1"], Exact, NONE),
c(&[b"SET", b"s:bulk", &[b'a'; 4096]], Exact, NONE),
c(&[b"GET", b"s:bulk"], Exact, &[b"s:bulk"]),
c(&[b"STRLEN", b"s:bulk"], Exact, NONE),
c(&[b"GETRANGE", b"s:bulk", b"0", b"9"], Exact, &[b"s:bulk"]),
c(&[b"SETRANGE", b"s:bulk", b"10", b"XY"], Exact, NONE),
c(&[b"APPEND", b"s:bulk", b"tail"], Exact, &[b"s:bulk"]),
c(&[b"SET", b"s:bulk", b"nope", b"NX"], Exact, &[b"s:bulk"]), c(&[b"SET", b"s:bulk", b"xxv", b"XX"], Exact, NONE), c(&[b"SET", b"s:bulk", &[b'b'; 2500]], Exact, NONE),
c(&[b"LPUSH", b"s:bulk", b"x"], Exact, &[b"s:bulk"]), c(&[b"HSET", b"s:bulk", b"f", b"v"], Exact, NONE), c(&[b"HSET", b"h:row", b"name", b"ada", b"dept", b"eng", b"age", b"36"], Exact, NONE),
c(&[b"HGET", b"h:row", b"name"], Exact, &[b"h:row"]), c(&[b"HSET", b"h:row", b"age", b"37"], Exact, NONE), c(&[b"HGETALL", b"h:row"], Shape, NONE), c(&[b"HGETALL", b"h:row"], Shape, &[b"h:row"]),
c(&[b"HDEL", b"h:row", b"dept"], Exact, NONE), c(&[b"HLEN", b"h:row"], Exact, NONE),
c(&[b"HSET", b"h:ttl", b"f1", b"v1", b"f2", b"v2"], Exact, NONE),
c(&[b"HPEXPIRE", b"h:ttl", b"60000", b"FIELDS", b"1", b"f1"], Exact, NONE),
c(&[b"HPERSIST", b"h:ttl", b"FIELDS", b"1", b"f1"], Exact, &[b"h:ttl"]),
c(&[b"SET", b"t:k", &[b'b'; 1024]], Exact, NONE),
c(&[b"PEXPIRE", b"t:k", b"600000"], Exact, &[b"t:k"]), c(&[b"PERSIST", b"t:k"], Exact, NONE),
c(&[b"TYPE", b"t:k"], Exact, NONE), c(&[b"EXISTS", b"s:bulk", b"h:row", b"absent"], Exact, NONE),
c(&[b"RENAME", b"t:k", b"t:k2"], Exact, NONE), c(&[b"COPY", b"t:k2", b"t:k3"], Exact, NONE), c(&[b"DEL", b"t:k3"], Exact, &[b"t:k3"]), c(&[b"DBSIZE"], Exact, NONE), c(&[b"KEYS", b"*"], Shape, NONE), c(&[b"SCAN", b"0"], Shape, NONE), c(&[b"RANDOMKEY"], Shape, NONE),
c(&[b"MEMORY", b"USAGE", b"s:bulk"], Shape, NONE),
c(&[b"RPUSH", b"l:q", b"a", b"b"], Exact, NONE),
c(&[b"LRANGE", b"l:q", b"0", b"-1"], Exact, NONE),
c(&[b"ZADD", b"z:s", b"1", b"m1"], Exact, NONE),
c(&[b"ZSCORE", b"z:s", b"m1"], Exact, NONE),
c(&[b"FLUSHALL"], Exact, NONE), c(&[b"DBSIZE"], Exact, NONE),
]
}
fn run_pair(a: &Store, b: &Store, demote: bool) -> usize {
let mut checked = 0;
for case in cases() {
let what = case
.argv
.iter()
.map(|x| String::from_utf8_lossy(x))
.collect::<Vec<_>>()
.join(" ");
if demote {
for key in case.demote {
assert!(
b.debug_force_demote(key),
"B9 marker must demote a warm spillable key: `{}` before `{what}`",
String::from_utf8_lossy(key)
);
}
}
let refs: Vec<&[u8]> = case.argv.iter().map(|v| v.as_slice()).collect();
let ra = reply(a, &refs);
let rb = reply(b, &refs);
assert_match(&what, &case.cmp, &ra, &rb);
checked += 1;
}
checked
}
#[test]
fn transparency_harness_self_check_untiered() {
let a = Store::open(Config::default().with_ttl_reaper_manual()).expect("open a");
let b = Store::open(Config::default().with_ttl_reaper_manual()).expect("open b");
let checked = run_pair(&a, &b, false);
assert!(checked >= 30, "sequence unexpectedly short: {checked}");
}
#[test]
fn transparency_tiered_vs_untiered() {
let dir = kevy_tmpdir::TmpDir::new("tier-transparency");
let a = Store::open(Config::default().with_ttl_reaper_manual()).expect("open untiered");
let b = Store::open(
Config::default()
.with_ttl_reaper_manual()
.with_persist(dir.path())
.with_tier_budget(u64::MAX),
)
.expect("open tiered");
let checked = run_pair(&a, &b, true);
assert!(checked >= 30, "sequence unexpectedly short: {checked}");
let (demotions, promotions) = b.tier_counters();
assert_eq!((demotions, promotions), (10, 5), "got {demotions}/{promotions}");
}
#[test]
fn demote_promote_move_tier_counters_only_and_emit_no_events() {
let dir = kevy_tmpdir::TmpDir::new("tier-b12");
let s = Store::open(
Config::default()
.with_ttl_reaper_manual()
.with_persist(dir.path())
.with_tier_budget(u64::MAX),
)
.expect("open tiered");
s.set(b"cold:k", &[b'x'; 4096]).unwrap();
s.with(|st| {
st.set_notify_capture(true, true, true);
st.take_notify_events();
});
assert!(s.debug_force_demote(b"cold:k"));
assert_eq!(s.get(b"cold:k").unwrap().unwrap().len(), 4096);
assert_eq!(s.get(b"cold:k").unwrap().unwrap().len(), 4096);
let (demotions, promotions) = s.tier_counters();
assert_eq!((demotions, promotions), (1, 1));
s.with(|st| {
assert_eq!(st.evictions_total(), 0, "demotion must not count as eviction");
assert!(
st.take_notify_events().is_empty(),
"demote/promote must emit zero keyspace events"
);
});
}