331 lines
10 KiB
Rust
331 lines
10 KiB
Rust
//! Ground Truth Integration Tests
|
|
//!
|
|
//! These tests verify our hash implementations against known-good values
|
|
//! computed using standard third-party tools (sha256sum, sha1sum, md5sum, Python hashlib).
|
|
//!
|
|
//! Reference file: Sintel trailer (480p) - Creative Commons licensed
|
|
//! Source: https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4
|
|
//!
|
|
//! To regenerate ground truth values, run:
|
|
//! cd testdata && ./compute_ground_truth.sh
|
|
|
|
use metamesh_plugin_fast_full_hash::config::HashAlgorithm;
|
|
use metamesh_plugin_fast_full_hash::hasher::hash_file;
|
|
use std::collections::HashSet;
|
|
use std::path::Path;
|
|
|
|
/// Reference file details
|
|
const REFERENCE_FILE: &str = "testdata/sintel_trailer-480p.mp4";
|
|
const REFERENCE_SIZE: u64 = 4372373;
|
|
|
|
/// Ground truth hash values (hex strings)
|
|
/// Computed using standard Unix tools and Python hashlib
|
|
mod expected {
|
|
pub const SHA256: &str = "b670602fa00934ca27c4351bb0efe7ea7a07fae57284e44226025eeed7c51254";
|
|
pub const SHA1: &str = "9b678890fb8ca401c28e7ca09171ec008a154b97";
|
|
pub const MD5: &str = "df6ed4bbc93613c68c8525e21bbddf98";
|
|
pub const CRC32: &str = "61accd5e";
|
|
pub const SHA3_256: &str = "fefc0c74ebd79a9872464ebbb35a0545db97431a6c9a96d36c061244883e9213";
|
|
pub const SHA3_384: &str = "da29f6604c96d09332531a4a2f4881b7bd2401a995053db868d5aea4056ada0b45c0ba69016e931bc468c38788afad94";
|
|
pub const MIDHASH256: &str = "297a6662a49c60da0cf2a75407a0eb0045a36f838d2921ab602483b708157773";
|
|
pub const BT_PIECES_ROOT: &str =
|
|
"424febb4268900457464928a99547dacd07d2075cbbd7ae27fdf5c8d2d982817";
|
|
pub const BT_INFO_HASH: &str =
|
|
"e2f048c0c8bb56c910c05b9efb4f39d2ff6fd0cf01e96e1dacecb56a067f8418";
|
|
}
|
|
|
|
/// Convert hex string to bytes
|
|
fn hex_to_bytes(hex: &str) -> Vec<u8> {
|
|
(0..hex.len())
|
|
.step_by(2)
|
|
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
|
|
.collect()
|
|
}
|
|
|
|
/// Extract raw hash from CID (parse the CID and get the hash bytes)
|
|
fn extract_hash_from_cid(cid_str: &str) -> Vec<u8> {
|
|
use cid::Cid;
|
|
let cid = Cid::try_from(cid_str).expect("Invalid CID");
|
|
cid.hash().digest().to_vec()
|
|
}
|
|
|
|
/// Check if reference file exists, skip test if not
|
|
fn check_reference_file() -> Option<&'static Path> {
|
|
let path = Path::new(REFERENCE_FILE);
|
|
if !path.exists() {
|
|
eprintln!(
|
|
"Skipping ground truth test: reference file not found at {}",
|
|
REFERENCE_FILE
|
|
);
|
|
eprintln!("To download, run: cd testdata && ./compute_ground_truth.sh");
|
|
return None;
|
|
}
|
|
|
|
// Verify file size
|
|
let metadata = std::fs::metadata(path).expect("Failed to read file metadata");
|
|
if metadata.len() != REFERENCE_SIZE {
|
|
eprintln!(
|
|
"WARNING: Reference file size mismatch. Expected {}, got {}",
|
|
REFERENCE_SIZE,
|
|
metadata.len()
|
|
);
|
|
eprintln!("Ground truth values may not match. Re-run compute_ground_truth.sh");
|
|
}
|
|
|
|
Some(path)
|
|
}
|
|
|
|
#[test]
|
|
fn test_sha256_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Sha256].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_sha2-256").expect("SHA-256 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::SHA256);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"SHA-256 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::SHA256,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sha1_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Sha1].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_sha1").expect("SHA-1 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::SHA1);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"SHA-1 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::SHA1,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_md5_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Md5].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_md5").expect("MD5 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::MD5);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"MD5 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::MD5,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_crc32_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Crc32].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_crc32").expect("CRC32 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::CRC32);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"CRC32 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::CRC32,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sha3_256_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Sha3_256].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_sha3-256").expect("SHA3-256 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::SHA3_256);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"SHA3-256 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::SHA3_256,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sha3_384_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Sha3_384].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results.get("cid_sha3-384").expect("SHA3-384 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::SHA3_384);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"SHA3-384 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::SHA3_384,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_midhash256_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::Midhash256].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results
|
|
.get("cid_midhash256")
|
|
.expect("MidHash256 not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::MIDHASH256);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"MidHash256 mismatch!\nExpected: {}\nGot: {}",
|
|
expected::MIDHASH256,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bt_pieces_root_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::BtPiecesRoot].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results
|
|
.get("cid_bt_pieces_root")
|
|
.expect("BT Pieces Root not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::BT_PIECES_ROOT);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"BT Pieces Root mismatch!\nExpected: {}\nGot: {}",
|
|
expected::BT_PIECES_ROOT,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bt_info_hash_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
let enabled: HashSet<_> = vec![HashAlgorithm::BtInfoHash].into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
let cid = results
|
|
.get("cid_bt_info_hash")
|
|
.expect("BT Info Hash not computed");
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected::BT_INFO_HASH);
|
|
|
|
assert_eq!(
|
|
hash_bytes, expected_bytes,
|
|
"BT Info Hash mismatch!\nExpected: {}\nGot: {}",
|
|
expected::BT_INFO_HASH,
|
|
hex::encode(&hash_bytes)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_all_algorithms_ground_truth() {
|
|
let Some(path) = check_reference_file() else {
|
|
return;
|
|
};
|
|
|
|
// Compute all algorithms in single pass
|
|
let enabled: HashSet<_> = HashAlgorithm::all().into_iter().collect();
|
|
let results = hash_file(path, &enabled, 1024 * 1024).expect("Hashing failed");
|
|
|
|
// Verify all 9 algorithms
|
|
let test_cases = vec![
|
|
("cid_sha2-256", expected::SHA256, "SHA-256"),
|
|
("cid_sha1", expected::SHA1, "SHA-1"),
|
|
("cid_md5", expected::MD5, "MD5"),
|
|
("cid_crc32", expected::CRC32, "CRC32"),
|
|
("cid_sha3-256", expected::SHA3_256, "SHA3-256"),
|
|
("cid_sha3-384", expected::SHA3_384, "SHA3-384"),
|
|
("cid_midhash256", expected::MIDHASH256, "MidHash256"),
|
|
(
|
|
"cid_bt_pieces_root",
|
|
expected::BT_PIECES_ROOT,
|
|
"BT Pieces Root",
|
|
),
|
|
("cid_bt_info_hash", expected::BT_INFO_HASH, "BT Info Hash"),
|
|
];
|
|
|
|
let mut failures = Vec::new();
|
|
|
|
for (key, expected_hex, name) in test_cases {
|
|
let cid = match results.get(key) {
|
|
Some(c) => c,
|
|
None => {
|
|
failures.push(format!("{}: NOT COMPUTED", name));
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let hash_bytes = extract_hash_from_cid(cid);
|
|
let expected_bytes = hex_to_bytes(expected_hex);
|
|
|
|
if hash_bytes != expected_bytes {
|
|
failures.push(format!(
|
|
"{}: MISMATCH\n Expected: {}\n Got: {}",
|
|
name,
|
|
expected_hex,
|
|
hex::encode(&hash_bytes)
|
|
));
|
|
}
|
|
}
|
|
|
|
if !failures.is_empty() {
|
|
panic!(
|
|
"Ground truth verification failed:\n{}",
|
|
failures.join("\n\n")
|
|
);
|
|
}
|
|
}
|