use structio::KeyMap;
use structio::keymap::HashKind;
const fn rounds(n: u32) -> u32 {
if cfg!(miri) { n / 100 + 1 } else { n }
}
fn lookup(map: &KeyMap, keys: &[&'static str], key: &str) -> usize {
let doc = format!("{key}\":1,\"other\":2}}");
map.lookup(keys, doc.as_bytes())
}
fn assert_exact(keys: &'static [&'static str]) -> HashKind {
let map = KeyMap::build(keys);
assert_eq!(map.n as usize, keys.len());
for (i, k) in keys.iter().enumerate() {
assert_eq!(
lookup(&map, keys, k),
i,
"key {k:?} in {keys:?} ({:?}) resolved to the wrong field",
map.kind
);
assert_eq!(
map.lookup_sized(keys, k.as_bytes()),
i,
"sized lookup of {k:?} in {keys:?} ({:?}) resolved to the wrong field",
map.kind
);
}
map.kind
}
fn assert_rejects(keys: &'static [&'static str], strangers: &[&str]) {
let map = KeyMap::build(keys);
for s in strangers {
for i in [lookup(&map, keys, s), map.lookup_sized(keys, s.as_bytes())] {
if i < keys.len() {
assert_ne!(keys[i], *s, "{s:?} should not be a declared key");
}
}
}
}
#[test]
fn no_keys() {
assert_eq!(assert_exact(&[]), HashKind::Empty);
assert_rejects(&[], &["", "a"]);
}
#[test]
fn single_element() {
assert_eq!(assert_exact(&["only"]), HashKind::SingleElement);
}
#[test]
fn two_elements_use_one_byte() {
assert_eq!(assert_exact(&["alpha", "beta"]), HashKind::UniqueIndexTwo);
assert_eq!(assert_exact(&["x", "y"]), HashKind::UniqueIndexTwo);
assert_rejects(&["alpha", "beta"], &["gamma", "a", "", "alphaa", "bet"]);
}
#[test]
fn mod4_family() {
let plain: &[&str] = &["x", "y", "z"];
assert_eq!(assert_exact(plain), HashKind::Mod4);
assert_rejects(plain, &["w", "xx", "a", ""]);
let xor: &[&str] = &["alpha", "delta", "charlie", "bravo"];
assert_eq!(assert_exact(xor), HashKind::XorMod4);
assert_rejects(xor, &["echo", "alphaa", "d", ""]);
let minus: &[&str] = &["alpha", "beta", "gamma"];
assert_eq!(assert_exact(minus), HashKind::MinusMod4);
assert_rejects(minus, &["delta", "betaa", "c", ""]);
}
#[test]
fn unique_index_is_exact_without_a_seed() {
let kind = assert_exact(&["alpha", "bravo", "charlie", "delta", "echo", "foxtrot"]);
assert_eq!(kind, HashKind::UniqueIndex);
assert_rejects(
&["alpha", "bravo", "charlie", "delta", "echo", "foxtrot"],
&["golf", "alpha1", "alph", "", "A", "zulu"],
);
}
#[test]
fn front_hash_handles_shared_first_bytes() {
let keys: &[&str] = &["aaaa", "aaab", "aaba", "abaa", "baaa"];
assert_eq!(assert_exact(keys), HashKind::FrontHash4);
assert_rejects(keys, &["aaac", "bbbb", "aaaaa", "aaa", ""]);
}
#[test]
fn every_front_hash_width() {
let two: &[&str] = &["ab", "ba", "aa", "bb"];
assert_eq!(assert_exact(two), HashKind::FrontHash2);
assert_rejects(two, &["ac", "abb", "a", ""]);
let four: &[&str] = &["aaab", "aaba", "aaaa", "aabb"];
assert_eq!(assert_exact(four), HashKind::FrontHash4);
assert_rejects(four, &["aaac", "aaabb", "aab", ""]);
let eight: &[&str] = &["aaaaaaab", "aaaaaaba", "aaaaaaaa", "aaaaaabb"];
assert_eq!(assert_exact(eight), HashKind::FrontHash8);
assert_rejects(eight, &["aaaaaaac", "aaaaaaabb", "aaaaaab", ""]);
}
#[test]
fn length_and_byte_together() {
let keys: &[&str] = &["a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"];
assert_eq!(assert_exact(keys), HashKind::UniqueIndexSized);
assert_rejects(keys, &["aaaaaaa", "b", "", "ab"]);
}
#[test]
fn a_column_per_length() {
let keys: &[&str] = &["id", "ids", "idx"];
assert_eq!(assert_exact(keys), HashKind::UniquePerLength);
assert_rejects(keys, &["idy", "is", "idsx", "i", ""]);
}
#[test]
fn a_distinguishing_column_past_a_long_prefix() {
let keys: &[&str] = &[
"configuration_value_alpha",
"configuration_value_bravo",
"configuration_value_charlie",
"configuration_value_delta",
];
assert_eq!(assert_exact(keys), HashKind::UniqueIndex);
assert_rejects(
keys,
&["configuration_value_echo", "configuration_value_", ""],
);
}
#[test]
fn realistic_key_sets() {
let sets: &[&[&'static str]] = &[
&["id", "name", "email", "created_at", "updated_at"],
&["x", "y", "z", "w"],
&[
"latitude",
"longitude",
"altitude",
"accuracy",
"heading",
"speed",
],
&[
"type",
"properties",
"geometry",
"coordinates",
"features",
"bbox",
],
&["a"],
&["", "b"],
&["_", "__", "___"],
&["Ünïcödé", "ключ", "键"],
&["with space", "with-dash", "with.dot", "with/slash"],
];
for keys in sets {
assert_exact(keys);
}
}
const WIDE_UNHASHABLE: &[&str] = &[
"commonPrefixField000",
"commonPrefixField001",
"commonPrefixField002",
"commonPrefixField003",
"commonPrefixField004",
"commonPrefixField005",
"commonPrefixField006",
"commonPrefixField007",
"commonPrefixField008",
"commonPrefixField009",
"commonPrefixField010",
"commonPrefixField011",
"commonPrefixField012",
"commonPrefixField013",
"commonPrefixField014",
"commonPrefixField015",
"commonPrefixField016",
"commonPrefixField017",
"commonPrefixField018",
"commonPrefixField019",
"commonPrefixField020",
"commonPrefixField021",
"commonPrefixField022",
"commonPrefixField023",
"commonPrefixField024",
"commonPrefixField025",
"commonPrefixField026",
"commonPrefixField027",
"commonPrefixField028",
"commonPrefixField029",
"commonPrefixField030",
"commonPrefixField031",
"commonPrefixField032",
"commonPrefixField033",
"commonPrefixField034",
"commonPrefixField035",
"commonPrefixField036",
"commonPrefixField037",
"commonPrefixField038",
"commonPrefixField039",
"commonPrefixField040",
"commonPrefixField041",
"commonPrefixField042",
"commonPrefixField043",
"commonPrefixField044",
"commonPrefixField045",
"commonPrefixField046",
"commonPrefixField047",
"commonPrefixField048",
"commonPrefixField049",
"commonPrefixField050",
"commonPrefixField051",
"commonPrefixField052",
"commonPrefixField053",
"commonPrefixField054",
"commonPrefixField055",
"commonPrefixField056",
"commonPrefixField057",
"commonPrefixField058",
"commonPrefixField059",
"commonPrefixField060",
"commonPrefixField061",
"commonPrefixField062",
"commonPrefixField063",
"commonPrefixField064",
"commonPrefixField065",
"commonPrefixField066",
"commonPrefixField067",
"commonPrefixField068",
"commonPrefixField069",
"commonPrefixField070",
"commonPrefixField071",
"commonPrefixField072",
"commonPrefixField073",
"commonPrefixField074",
"commonPrefixField075",
"commonPrefixField076",
"commonPrefixField077",
"commonPrefixField078",
"commonPrefixField079",
"commonPrefixField080",
"commonPrefixField081",
"commonPrefixField082",
"commonPrefixField083",
"commonPrefixField084",
"commonPrefixField085",
"commonPrefixField086",
"commonPrefixField087",
"commonPrefixField088",
"commonPrefixField089",
"commonPrefixField090",
"commonPrefixField091",
"commonPrefixField092",
"commonPrefixField093",
"commonPrefixField094",
"commonPrefixField095",
];
const _WIDE_OBJECT_COMPILES: &KeyMap = &KeyMap::build(WIDE_UNHASHABLE);
fn leaked(names: impl IntoIterator<Item = String>) -> &'static [&'static str] {
let v: Vec<&'static str> = names
.into_iter()
.map(|s| &*Box::leak(s.into_boxed_str()))
.collect();
Vec::leak(v)
}
#[test]
fn a_wide_object_with_distinct_front_bytes_still_gets_a_hash() {
let keys = leaked((0..96).map(|i| format!("f{i:03}")));
assert_eq!(assert_exact(keys), HashKind::FrontHash4);
assert_rejects(keys, &["f096", "f9999", "", "g000"]);
}
#[test]
fn a_wide_object_that_cannot_be_hashed_stays_exact_under_linear() {
let keys: &'static [&'static str] = WIDE_UNHASHABLE;
assert_eq!(assert_exact(keys), HashKind::Linear);
assert_rejects(
keys,
&["commonPrefixField096", "commonPrefixField", "", "other"],
);
}
#[test]
fn the_whole_key_scheme_is_exact() {
let keys: &[&'static str] = &["aaaaaaaaaa", "aaaaaaaaab", "aaaaaaaaba", "aaaaaaaabb"];
assert_eq!(assert_exact(keys), HashKind::FullFlat);
assert_rejects(keys, &["aaaaaaaaaa2", "aaaaaaaa", "", "baaaaaaaaa"]);
}
#[test]
fn wide_objects() {
macro_rules! keys64 {
() => {
&[
"f00", "f01", "f02", "f03", "f04", "f05", "f06", "f07", "f08", "f09", "f10", "f11",
"f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
"f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35",
"f36", "f37", "f38", "f39", "f40", "f41", "f42", "f43", "f44", "f45", "f46", "f47",
"f48", "f49", "f50", "f51", "f52", "f53", "f54", "f55", "f56", "f57", "f58", "f59",
"f60", "f61", "f62", "f63",
]
};
}
let keys: &[&'static str] = keys64!();
assert_exact(keys);
assert_rejects(keys, &["f64", "f99", "g00", "f0", ""]);
}
#[test]
fn generated_key_sets_are_all_exact() {
let mut state: u64 = 0xDEAD_BEEF_CAFE_F00D;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz_0123456789";
for _ in 0..rounds(400) {
let n = 1 + (next() % 40) as usize;
let mut owned: Vec<String> = Vec::with_capacity(n);
while owned.len() < n {
let len = 1 + (next() % 12) as usize;
let mut s = String::with_capacity(len);
for _ in 0..len {
s.push(ALPHABET[(next() % ALPHABET.len() as u64) as usize] as char);
}
if !owned.contains(&s) {
owned.push(s);
}
}
let keys = leaked(owned.iter().cloned());
let map = KeyMap::build(keys);
for (i, k) in keys.iter().enumerate() {
assert_eq!(
lookup(&map, keys, k),
i,
"{:?} mis-resolved {k:?} among {} keys",
map.kind,
keys.len()
);
assert_eq!(
map.lookup_sized(keys, k.as_bytes()),
i,
"{:?} sized-mis-resolved {k:?} among {} keys",
map.kind,
keys.len()
);
}
for stranger in ["", "X", "ZZ", "ABCDEFGHIJKL", &"Q".repeat(40)] {
let i = map.lookup_sized(keys, stranger.as_bytes());
if i < keys.len() {
assert_ne!(keys[i], stranger, "{stranger:?} is not a declared key");
}
}
}
}