use std::io;
use std::path::Path;
pub use blake3::Hash;
pub fn blake3_file(path: &Path) -> io::Result<Hash> {
let mut hasher = blake3::Hasher::new();
hasher.update_mmap_rayon(path)?;
Ok(hasher.finalize())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn write_temp(name: &str, bytes: &[u8]) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!(
"rp-content-hash-{}-{}-{}",
std::process::id(),
name,
bytes.len()
));
let mut f = std::fs::File::create(&path).expect("create temp file");
f.write_all(bytes).expect("write temp file");
f.flush().expect("flush temp file");
path
}
#[test]
fn hashes_the_bytes_not_the_path() {
let bytes = b"the quick brown fox jumps over the lazy dog";
let path = write_temp("bytes", bytes);
let got = blake3_file(&path).expect("hash temp file");
std::fs::remove_file(&path).ok();
assert_eq!(got, blake3::hash(bytes));
}
#[test]
fn same_contents_at_different_paths_hash_equal() {
let bytes = b"identical contents";
let a = write_temp("dup-a", bytes);
let b = write_temp("dup-b", bytes);
let ha = blake3_file(&a).expect("hash a");
let hb = blake3_file(&b).expect("hash b");
std::fs::remove_file(&a).ok();
std::fs::remove_file(&b).ok();
assert_eq!(ha, hb, "content-based hash must ignore the path");
}
#[test]
fn different_contents_hash_differently() {
let a = write_temp("diff-a", b"content one");
let b = write_temp("diff-b", b"content two");
let ha = blake3_file(&a).expect("hash a");
let hb = blake3_file(&b).expect("hash b");
std::fs::remove_file(&a).ok();
std::fs::remove_file(&b).ok();
assert_ne!(ha, hb);
}
#[test]
fn empty_file_hashes_like_empty_input() {
let path = write_temp("empty", b"");
let got = blake3_file(&path).expect("hash empty file");
std::fs::remove_file(&path).ok();
assert_eq!(got, blake3::hash(b""));
}
#[test]
fn first_16_hex_is_a_stable_stamp() {
let bytes = b"stamp me";
let path = write_temp("stamp", bytes);
let hash = blake3_file(&path).expect("hash temp file");
std::fs::remove_file(&path).ok();
let hex = hash.to_hex();
let stamp16 = &hex[..16];
assert_eq!(stamp16.len(), 16);
assert!(stamp16.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(stamp16, &blake3::hash(bytes).to_hex()[..16]);
}
#[test]
fn missing_file_is_an_io_error() {
let mut path = std::env::temp_dir();
path.push(format!(
"rp-content-hash-does-not-exist-{}",
std::process::id()
));
let err = blake3_file(&path).expect_err("missing file must error");
assert_eq!(err.kind(), io::ErrorKind::NotFound);
}
}