use crate::hash::fnv32;
pub fn line_hash(line: &str) -> String {
let h = fnv32(line.as_bytes());
let folded = (h ^ (h >> 12) ^ (h >> 24)) & 0xfff;
format!("{folded:03x}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_is_three_lowercase_hex_chars() {
for s in ["", "x", "fn main() {}", " let y = 1;", "🦀 unicode"] {
let h = line_hash(s);
assert_eq!(h.len(), 3, "hash {h:?} for {s:?} not 3 chars");
assert!(
h.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()),
"hash {h:?} not lowercase hex"
);
}
}
#[test]
fn hash_output_is_locked() {
for (input, expected) in [
("", "c8d"),
("x", "0bf"),
("fn main() {}", "8a1"),
(" let y = 1;", "b4d"),
("🦀 unicode", "64b"),
("let total = a + b;", "e3b"),
] {
assert_eq!(line_hash(input), expected, "drift for {input:?}");
}
}
#[test]
fn hash_is_deterministic() {
assert_eq!(
line_hash("let total = a + b;"),
line_hash("let total = a + b;")
);
}
#[test]
fn distinct_lines_usually_differ() {
let a = line_hash(" return Ok(());");
let b = line_hash(" return Err(e);");
assert_ne!(a, b);
}
#[test]
fn whitespace_is_significant() {
assert_ne!(line_hash("x = 1"), line_hash(" x = 1"));
}
}