use crate::store::Store;
const FINGERPRINT_SEED: u64 = 0x9e37_79b9_7f4a_7c15;
const HASHER_SEEDS: [u64; 4] = [
0x243f_6a88_85a3_08d3,
0x1319_8a2e_0370_7344,
0xa409_3822_299f_31d0,
0x082e_fa98_ec4e_6c89,
];
pub(crate) fn index_fingerprint(store: &Store) -> u64 {
use std::hash::{BuildHasher, Hash, Hasher};
let state = ahash::RandomState::with_seeds(HASHER_SEEDS[0], HASHER_SEEDS[1], HASHER_SEEDS[2], HASHER_SEEDS[3]);
let folded = store
.index
.files
.iter()
.map(|(path, entry)| {
let mut hasher = state.build_hasher();
path.hash(&mut hasher);
entry.hash_hex.hash(&mut hasher);
hasher.finish()
})
.fold(FINGERPRINT_SEED, |acc, entry_hash| acc ^ entry_hash);
let mut hasher = state.build_hasher();
folded.hash(&mut hasher);
store.index.files.len().hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn scan(root: &std::path::Path) -> Store {
let cfg = crate::config::ConfigV1::with_defaults();
let mut store = Store::open(root, crate::store::VIEW_WORKING).unwrap();
crate::scanner::scan(
root,
&mut store,
&cfg,
crate::scanner::ScanSource::WorkingTree,
crate::scanner::EmbedMode::Inline,
)
.unwrap();
store
}
#[test]
fn fingerprint_is_stable_when_no_indexed_file_changed() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::write(root.join("a.rs"), b"pub fn alpha() {}\n").unwrap();
fs::write(root.join("b.rs"), b"pub fn beta() {}\n").unwrap();
let store = scan(root);
let first = index_fingerprint(&store);
drop(store);
let store = scan(root);
let second = index_fingerprint(&store);
assert_eq!(first, second, "a no-op rescan must not change the fingerprint");
assert_ne!(
first, 0,
"a populated index must never fingerprint to the empty-cache sentinel"
);
}
#[test]
fn fingerprint_changes_when_a_file_changes() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::write(root.join("a.rs"), b"pub fn alpha() {}\n").unwrap();
let store = scan(root);
let before = index_fingerprint(&store);
drop(store);
fs::write(root.join("a.rs"), b"pub fn alpha_renamed() {}\n").unwrap();
let store = scan(root);
let after = index_fingerprint(&store);
assert_ne!(before, after, "changed content must change the fingerprint");
}
#[test]
fn fingerprint_changes_when_a_file_is_removed() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::write(root.join("a.rs"), b"pub fn alpha() {}\n").unwrap();
fs::write(root.join("b.rs"), b"pub fn beta() {}\n").unwrap();
let store = scan(root);
let before = index_fingerprint(&store);
drop(store);
fs::remove_file(root.join("b.rs")).unwrap();
let store = scan(root);
let after = index_fingerprint(&store);
assert_ne!(before, after, "a removed file must change the fingerprint");
}
#[test]
fn equal_fingerprint_implies_equal_map_contents() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
fs::write(root.join("a.rs"), b"pub fn alpha() {}\n").unwrap();
fs::write(root.join("b.rs"), b"pub fn beta() {}\n").unwrap();
let store = scan(root);
let map_a = super::super::MapCache::build(&store);
drop(store);
let store = scan(root);
let map_b = super::super::MapCache::build(&store);
assert_eq!(
map_a.fingerprint, map_b.fingerprint,
"no source change => same fingerprint"
);
assert_eq!(
map_a.by_path.keys().collect::<Vec<_>>(),
map_b.by_path.keys().collect::<Vec<_>>(),
"same fingerprint => same indexed path set"
);
for (path, l1) in &map_a.by_path {
let other = map_b.by_path.get(path).expect("path present in both maps");
let names = |m: &crate::extract::FileMapL1| m.symbols.iter().map(|s| s.name.clone()).collect::<Vec<_>>();
assert_eq!(
names(l1),
names(other),
"same fingerprint => identical symbols for {path:?}"
);
}
}
}