use std::{fs, path::Path};
use super::*;
fn stage(root: &Path) {
fs::create_dir_all(root.join("weights")).expect("create weights");
fs::create_dir_all(root.join("analytics")).expect("create analytics");
fs::write(root.join("coremldata.bin"), b"header bytes").expect("write coremldata");
fs::write(root.join("model.mil"), b"program(1.3) {}").expect("write model.mil");
fs::write(root.join("metadata.json"), b"[{}]").expect("write metadata");
fs::write(root.join("weights/weight.bin"), b"0123456789abcdef").expect("write weights");
fs::write(root.join("analytics/coremldata.bin"), b"analytics").expect("write analytics");
}
fn digest_of(root: &Path) -> ArtifactDigest {
digest_artifact(root).expect("a staged bundle hashes")
}
#[test]
fn the_digest_is_a_function_of_the_bytes_and_of_nothing_about_the_load() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path().join("still.mlmodelc");
stage(&root);
let at_load = digest_of(&root);
assert_eq!(
at_load,
digest_artifact(&root).expect("a second walk of the same path"),
"a second walk of an unmodified path must give the digest the load took"
);
let renamed = temp.path().join("renamed.mlmodelc");
fs::rename(&root, &renamed).expect("rename the bundle");
assert_eq!(
at_load,
digest_of(&renamed),
"the artifact root's own name must not be part of the digest"
);
let nested = temp.path().join("elsewhere");
fs::create_dir_all(&nested).expect("create parent");
let moved = nested.join("still.mlmodelc");
fs::rename(&renamed, &moved).expect("move the bundle");
assert_eq!(at_load, digest_of(&moved), "nor may the path it sits at");
fs::write(temp.path().join("unrelated.bin"), b"not part of the bundle").expect("write sibling");
assert_eq!(
at_load,
digest_of(&moved),
"a file outside the artifact root is not part of its identity"
);
fs::write(moved.join("weights/weight.bin"), b"0123456789abcdeF").expect("rewrite one byte");
assert_ne!(
at_load,
digest_of(&moved),
"one byte of the bundle must move the digest, or this gate proves nothing"
);
}
#[test]
fn a_byte_identical_copy_of_a_bundle_has_the_same_digest() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
assert_eq!(
digest_of(&left),
digest_of(&right),
"two byte-identical bundles at different paths must be one artifact"
);
assert_eq!(digest_of(&left), digest_of(&left));
}
#[test]
fn one_changed_weight_byte_changes_the_digest() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
fs::write(right.join("weights/weight.bin"), b"0123456789abcdeF").expect("rewrite weights");
assert_ne!(
digest_of(&left),
digest_of(&right),
"one byte of one weight file is a different artifact"
);
}
#[test]
fn moving_a_files_bytes_to_another_name_changes_the_digest() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
let mil = fs::read(right.join("model.mil")).expect("read");
let metadata = fs::read(right.join("metadata.json")).expect("read");
fs::write(right.join("model.mil"), &metadata).expect("swap");
fs::write(right.join("metadata.json"), &mil).expect("swap");
assert_ne!(
digest_of(&left),
digest_of(&right),
"two files with swapped contents are a different artifact, and only the path in each entry \
can say so"
);
let renamed = temp.path().join("c.mlmodelc");
stage(&renamed);
fs::rename(renamed.join("model.mil"), renamed.join("model.mil.bak")).expect("rename");
assert_ne!(digest_of(&left), digest_of(&renamed));
}
#[test]
fn a_ds_store_beside_the_weights_changes_the_digest() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
let clean = digest_of(&left);
assert_eq!(clean, digest_of(&right), "the two bundles start identical");
fs::write(right.join(".DS_Store"), b"finder junk").expect("write .DS_Store");
assert_ne!(
clean,
digest_of(&right),
"a dot-prefixed file at the root is part of the bytes"
);
fs::remove_file(right.join(".DS_Store")).expect("remove .DS_Store");
assert_eq!(
clean,
digest_of(&right),
"and removing it restores the identity"
);
fs::write(right.join("weights/.DS_Store"), b"more junk").expect("write nested .DS_Store");
assert_ne!(
clean,
digest_of(&right),
"a dot-prefixed file BELOW the root counts too"
);
fs::remove_file(right.join("weights/.DS_Store")).expect("remove nested .DS_Store");
fs::create_dir_all(right.join(".weights")).expect("create dot-directory");
fs::write(right.join(".weights/weight.bin"), b"hidden weights A").expect("write hidden weights");
let hidden_a = digest_of(&right);
assert_ne!(
clean, hidden_a,
"a dot-DIRECTORY's contents are part of the bytes"
);
fs::write(right.join(".weights/weight.bin"), b"hidden weights B").expect("rewrite");
assert_ne!(
hidden_a,
digest_of(&right),
"two bundles whose only difference is a hidden blob are two artifacts"
);
}
#[test]
fn a_symlinked_file_hashes_as_the_bytes_it_resolves_to() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
let elsewhere = temp.path().join("shared-weight.bin");
fs::write(&elsewhere, b"0123456789abcdef").expect("write shared");
fs::remove_file(right.join("weights/weight.bin")).expect("remove");
std::os::unix::fs::symlink(&elsewhere, right.join("weights/weight.bin")).expect("symlink");
assert_eq!(
digest_of(&left),
digest_of(&right),
"a link to identical bytes is the same artifact"
);
fs::remove_file(&elsewhere).expect("break the link");
let error = digest_artifact(&right).expect_err("a dangling link has no bytes");
assert!(
matches!(&error, Error::ArtifactDigest(payload) if payload.path().ends_with("weight.bin")),
"the failure must name the entry that could not be read, got {error:?}"
);
}
#[test]
fn a_directory_symlink_is_refused_rather_than_walked() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path().join("a.mlmodelc");
stage(&root);
let elsewhere = temp.path().join("shared");
fs::create_dir_all(&elsewhere).expect("create shared");
fs::write(elsewhere.join("blob.bin"), b"shared bytes").expect("write shared blob");
let link = root.join("linked-weights");
std::os::unix::fs::symlink(&elsewhere, &link).expect("symlink a directory");
let error = digest_artifact(&root).expect_err("a directory symlink is not a bundle's own tree");
assert!(
matches!(&error, Error::ArtifactDigest(payload) if payload.path() == link),
"the refusal must name the link itself, got {error:?}"
);
let cycle = temp.path().join("b.mlmodelc");
stage(&cycle);
std::os::unix::fs::symlink(&cycle, cycle.join("weights/up")).expect("symlink a parent");
let error = digest_artifact(&cycle).expect_err("a cycle is refused at its first link");
assert!(
matches!(&error, Error::ArtifactDigest(payload) if payload.path().ends_with("up")),
"the cycle must be refused at the link, got {error:?}"
);
}
#[test]
fn the_entry_budget_refuses_rather_than_truncates() {
let temp = tempfile::tempdir().expect("tempdir");
let root = temp.path().join("wide.mlmodelc");
fs::create_dir_all(&root).expect("create root");
for index in 0..MAX_ENTRIES {
fs::write(root.join(format!("f{index:05}")), b"x").expect("write");
}
digest_artifact(&root).expect("exactly the budget is admitted");
fs::write(root.join("one-too-many"), b"x").expect("write");
let error = digest_artifact(&root).expect_err("one past the budget is refused");
assert!(
matches!(&error, Error::ArtifactDigest(payload) if payload.path() == root),
"the refusal must name the directory the walk gave up in, got {error:?}"
);
assert!(
error
.to_string()
.contains("failed to hash the model artifact"),
"and it must read as a digest failure, got {error}"
);
}
#[test]
fn an_empty_directory_is_invisible_and_a_regular_file_root_is_allowed() {
let temp = tempfile::tempdir().expect("tempdir");
let (left, right) = (
temp.path().join("a.mlmodelc"),
temp.path().join("b.mlmodelc"),
);
stage(&left);
stage(&right);
fs::create_dir_all(right.join("empty/also-empty")).expect("create empty dirs");
assert_eq!(
digest_of(&left),
digest_of(&right),
"a directory contributes only through the files under it"
);
let file = temp.path().join("solitary.bin");
fs::write(&file, b"just bytes").expect("write");
let same = temp.path().join("also-solitary.bin");
fs::write(&same, b"just bytes").expect("write");
assert_eq!(digest_of(&file), digest_of(&same));
let different = temp.path().join("other.bin");
fs::write(&different, b"other bytes").expect("write");
assert_ne!(digest_of(&file), digest_of(&different));
}
#[test]
fn a_missing_artifact_is_reported_by_path() {
let temp = tempfile::tempdir().expect("tempdir");
let absent = temp.path().join("not-there.mlmodelc");
let error = digest_artifact(&absent).expect_err("nothing to hash");
assert!(
matches!(&error, Error::ArtifactDigest(payload)
if payload.path() == absent && payload.source().kind() == std::io::ErrorKind::NotFound),
"expected a NotFound naming the artifact, got {error:?}"
);
assert!(
error
.to_string()
.contains("failed to hash the model artifact"),
"the message must say what failed, got {error}"
);
}
fn unprefixed(entries: &[(Vec<u8>, [u8; 32])]) -> Vec<u8> {
entries
.iter()
.flat_map(|(path, hash)| path.iter().chain(hash.iter()).copied())
.collect()
}
#[test]
fn the_length_prefix_is_what_makes_the_encoding_injective() {
let hash_x = [0xABu8; 32];
let hash_y = [0xCDu8; 32];
let left = vec![(b"x".to_vec(), hash_x), (b"y".to_vec(), hash_y)];
let mut absorbed = b"x".to_vec();
absorbed.extend_from_slice(&hash_x);
absorbed.extend_from_slice(b"y");
let right = vec![(absorbed, hash_y)];
assert_eq!(
unprefixed(&left),
unprefixed(&right),
"the two lists must collide without the prefix, or this gate proves nothing"
);
assert_ne!(
fold_entries(left),
fold_entries(right),
"two different artifacts must not share one identity"
);
}
#[test]
fn the_digest_does_not_depend_on_the_order_entries_are_discovered_in() {
let entries = vec![
(b"weights/weight.bin".to_vec(), [1u8; 32]),
(b"coremldata.bin".to_vec(), [2u8; 32]),
(b"analytics/coremldata.bin".to_vec(), [3u8; 32]),
(b"model.mil".to_vec(), [4u8; 32]),
];
let mut shuffled = entries.clone();
shuffled.reverse();
assert_ne!(
entries, shuffled,
"the two orders must differ, or this gate proves nothing"
);
assert_eq!(
fold_entries(entries),
fold_entries(shuffled),
"the digest must be a function of the SET of entries, not of the walk order"
);
}
fn copy_tree(from: &Path, to: &Path) {
fs::create_dir_all(to).expect("mkdir");
for entry in fs::read_dir(from).expect("read_dir") {
let entry = entry.expect("entry");
let (source, destination) = (entry.path(), to.join(entry.file_name()));
if fs::metadata(&source).expect("metadata").is_dir() {
copy_tree(&source, &destination);
} else {
fs::copy(&source, &destination).expect("copy");
}
}
}
#[test]
fn a_real_compiled_bundle_hashes_the_same_at_a_second_path() {
let bundle = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../Models/vadkit/silero-vad-unified-256ms-v6.2.1.mlmodelc");
if !bundle.is_dir() {
return;
}
let temp = tempfile::tempdir().expect("tempdir");
let copy = temp.path().join("elsewhere.mlmodelc");
copy_tree(&bundle, ©);
assert_eq!(
digest_of(&bundle),
digest_of(©),
"a bundle copied to another path, on another filesystem, is one artifact"
);
fs::write(copy.join("weights/weight.bin"), b"not the weights").expect("overwrite");
assert_ne!(digest_of(&bundle), digest_of(©));
}