use super::super::*;
use super::*;
use crate::storage::cas::CasStore;
use std::fs;
use std::sync::{Arc, Mutex};
#[allow(dead_code)]
fn make_test_model_identity() -> ModelIdentity {
ModelIdentity {
name: "test-model".to_string(),
digest: "sha256:abcdef".to_string(),
dimensions: 384,
}
}
fn open_writer() -> (tempfile::TempDir, Arc<Mutex<CasStore>>, GenerationWriter) {
let dir = tempfile::tempdir().expect("tempdir");
let storage_root = dir.path().to_path_buf();
let cas_root = storage_root.join("cas");
let store = Arc::new(Mutex::new(CasStore::open(&cas_root).expect("open cas")));
let writer = GenerationWriter::new(storage_root.clone(), store.clone());
(dir, store, writer)
}
fn stage_all_layers(writer: &mut GenerationWriter) {
writer
.stage(LayerKind::Db, b"db layer payload")
.expect("stage db");
writer
.stage(LayerKind::Tfidf, b"tfidf layer payload")
.expect("stage tfidf");
writer
.stage(LayerKind::Neural, b"neural layer payload")
.expect("stage neural");
writer
.stage(LayerKind::Pdg, b"pdg layer payload")
.expect("stage pdg");
writer
.stage(LayerKind::Symbols, b"symbols layer payload")
.expect("stage symbols");
}
#[test]
fn test_writer_stage_dedup() {
let (_dir, store, mut writer) = open_writer();
writer
.stage(LayerKind::Tfidf, b"identical tfidf data")
.expect("stage tfidf first");
let hash_first = *writer
.staged_hash(LayerKind::Tfidf)
.expect("staged hash exists");
writer
.stage(LayerKind::Tfidf, b"identical tfidf data")
.expect("stage tfidf second");
let hash_second = *writer
.staged_hash(LayerKind::Tfidf)
.expect("staged hash exists");
assert_eq!(
hash_first, hash_second,
"identical bytes must produce same hash"
);
{
let s = store.lock().unwrap();
assert!(
s.exists(&hash_first),
"blob must exist in CAS after staging"
);
let blob_count = s.blob_count().expect("blob_count");
assert_eq!(
blob_count, 1,
"dedup: only one blob on disk for identical content"
);
}
}
#[test]
fn test_writer_stage_different_layers_different_hashes() {
let (_dir, _store, mut writer) = open_writer();
writer.stage(LayerKind::Db, b"db data").expect("stage db");
writer
.stage(LayerKind::Neural, b"neural data")
.expect("stage neural");
let db_hash = *writer.staged_hash(LayerKind::Db).expect("db hash");
let neural_hash = *writer.staged_hash(LayerKind::Neural).expect("neural hash");
assert_ne!(
db_hash, neural_hash,
"different content must have different hashes"
);
}
#[test]
fn test_writer_stage_dedup_across_writers() {
let (_dir, store, mut writer_a) = {
let dir = tempfile::tempdir().expect("tempdir");
let storage_root = dir.path().to_path_buf();
let cas_root = storage_root.join("cas");
let store = Arc::new(Mutex::new(CasStore::open(&cas_root).expect("open cas")));
let writer = GenerationWriter::new(storage_root.clone(), store.clone());
(dir, store, writer)
};
let storage_root = writer_a.storage_root().to_path_buf();
let cas_root = storage_root.join("cas");
writer_a
.stage(LayerKind::Db, b"shared db content")
.expect("stage db");
let store_b = Arc::new(Mutex::new(CasStore::open(&cas_root).expect("reopen cas")));
let mut writer_b = GenerationWriter::new(storage_root, store_b.clone());
writer_b
.stage(LayerKind::Symbols, b"shared db content")
.expect("stage symbols with same content");
let hash_a = *writer_a
.staged_hash(LayerKind::Db)
.expect("hash from writer a");
let hash_b = *writer_b
.staged_hash(LayerKind::Symbols)
.expect("hash from writer b");
assert_eq!(
hash_a, hash_b,
"same content across writers must hash identically"
);
{
let s = store.lock().unwrap();
assert_eq!(
s.blob_count().unwrap(),
1,
"cross-writer dedup: one blob on disk"
);
}
}
#[test]
fn test_writer_atomic_publish() {
let (dir, _store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(5).expect("publish");
let storage_root = dir.path().to_path_buf();
let current = fs::read_to_string(storage_root.join("CURRENT")).expect("read CURRENT");
assert_eq!(current.trim(), "5");
let manifest_path = storage_root.join("generations").join("5").join("manifest");
assert!(
manifest_path.exists(),
"manifest file must exist after publish"
);
let partial_path = storage_root
.join("generations")
.join("5")
.join("manifest.partial");
assert!(
!partial_path.exists(),
"manifest.partial must not exist after successful publish"
);
let manifest_bytes = fs::read(&manifest_path).expect("read manifest bytes");
let manifest = Manifest::from_bytes(&manifest_bytes).expect("parse manifest");
assert_eq!(manifest.generation, 5);
assert_eq!(manifest.layers.len(), 5);
}
#[test]
fn test_writer_publish_creates_generation_directory() {
let (dir, _store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(42).expect("publish");
let generation_dir = dir.path().join("generations").join("42");
assert!(
generation_dir.exists(),
"generation directory must be created"
);
assert!(
generation_dir.join("manifest").exists(),
"manifest must exist"
);
}
#[test]
fn test_writer_publish_increments_generation() {
let (dir, _store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(1).expect("publish 1");
assert_eq!(
read_current_generation(dir.path()),
Some(1),
"CURRENT points to gen 1"
);
writer.finish_publish(); stage_all_layers(&mut writer);
writer.publish(2).expect("publish 2");
assert_eq!(
read_current_generation(dir.path()),
Some(2),
"CURRENT updated to gen 2"
);
let m1 = read_generation_manifest(dir.path(), 1).expect("read gen 1 manifest");
let m2 = read_generation_manifest(dir.path(), 2).expect("read gen 2 manifest");
assert_eq!(m1.generation, 1);
assert_eq!(m2.generation, 2);
}
#[test]
fn test_crash_before_rename() {
let (dir, store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(1).expect("publish gen 1");
writer.finish_publish();
stage_all_layers(&mut writer);
writer
.write_manifest_partial_for_test(2)
.expect("write partial");
let partial_path = dir
.path()
.join("generations")
.join("2")
.join("manifest.partial");
assert!(
partial_path.exists(),
"partial file should exist for test setup"
);
let final_path = dir.path().join("generations").join("2").join("manifest");
assert!(
!final_path.exists(),
"manifest must not exist before rename"
);
assert_eq!(
read_current_generation(dir.path()),
Some(1),
"CURRENT must still point to gen 1"
);
{
let cas_dir = dir.path().join("cas");
let store2 = Arc::new(Mutex::new(CasStore::open(&cas_dir).expect("reopen cas")));
let mut writer2 = GenerationWriter::new(dir.path(), store2.clone());
writer2.sweep_partial_manifests().expect("sweep");
}
assert!(!partial_path.exists(), "partial must be swept on recovery");
let current_gen = read_current_generation(dir.path()).expect("current gen");
assert_eq!(current_gen, 1);
let manifest = read_generation_manifest(dir.path(), current_gen).expect("read manifest");
assert_eq!(manifest.generation, 1);
let lease = GenerationLease::acquire(store.clone(), &manifest).expect("acquire lease");
assert_eq!(lease.generation(), 1);
drop(lease);
}
#[test]
fn test_crash_after_rename_before_current() {
let (dir, store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(1).expect("publish gen 1");
writer.finish_publish();
stage_all_layers(&mut writer);
writer
.write_and_rename_manifest_for_test(2)
.expect("rename without current");
let manifest_path = dir.path().join("generations").join("2").join("manifest");
assert!(
manifest_path.exists(),
"gen 2 manifest should exist after rename"
);
assert_eq!(
read_current_generation(dir.path()),
Some(1),
"CURRENT must still be gen 1 after crash before CURRENT update"
);
let current_gen = read_current_generation(dir.path()).expect("current");
assert_eq!(current_gen, 1);
let manifest = read_generation_manifest(dir.path(), current_gen).expect("read gen 1 manifest");
let lease = GenerationLease::acquire(store.clone(), &manifest).expect("lease gen 1");
assert_eq!(lease.generation(), 1);
drop(lease);
{
let cas_dir = dir.path().join("cas");
let store2 = Arc::new(Mutex::new(CasStore::open(&cas_dir).expect("reopen cas")));
let mut writer2 = GenerationWriter::new(dir.path(), store2);
writer2.sweep_partial_manifests().expect("sweep");
}
}
#[test]
fn test_crash_every_publish_phase() {
for kill_point in 0..7u8 {
let (dir, store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(1).expect("publish gen 1");
writer.finish_publish();
stage_all_layers(&mut writer);
let _ = writer.publish_with_simulated_crash(2, kill_point);
let current_gen = read_current_generation(dir.path());
let gen_n = current_gen.expect("CURRENT must always point to a generation");
let expected_gen = if kill_point == 6 { 2 } else { 1 };
assert_eq!(
gen_n, expected_gen,
"CURRENT must point to expected generation {} for kill_point {}",
expected_gen, kill_point
);
let manifest =
read_generation_manifest(dir.path(), gen_n).expect("read CURRENT generation manifest");
assert_eq!(manifest.generation, gen_n);
let lease = GenerationLease::acquire(store.clone(), &manifest).expect("acquire lease");
{
let s = store.lock().unwrap();
for hash in lease.layer_hashes() {
assert!(
s.exists(hash),
"blob {:?} must exist in CAS for kill_point {}",
&hash[..4],
kill_point
);
let _data = s.get(hash).expect("get blob data");
}
}
drop(lease);
{
let cas_dir = dir.path().join("cas");
let store2 = Arc::new(Mutex::new(CasStore::open(&cas_dir).expect("reopen cas")));
let mut writer2 = GenerationWriter::new(dir.path(), store2);
writer2
.sweep_partial_manifests()
.expect("sweep should succeed");
let partial_path = dir
.path()
.join("generations")
.join("2")
.join("manifest.partial");
assert!(
!partial_path.exists(),
"partial manifest must be swept for kill_point {}",
kill_point
);
}
let gen_post = read_current_generation(dir.path()).expect("post-recovery current");
assert_eq!(
gen_post, expected_gen,
"gen_n must still be {} after recovery for kill_point {}",
expected_gen, kill_point
);
let _ = read_generation_manifest(dir.path(), gen_post).expect("post-recovery manifest");
}
}
#[test]
fn test_publish_requires_all_layers() {
let (_dir, _store, mut writer) = open_writer();
writer.stage(LayerKind::Db, b"db").expect("stage");
writer.stage(LayerKind::Tfidf, b"tfidf").expect("stage");
writer.stage(LayerKind::Neural, b"neural").expect("stage");
let result = writer.publish(1);
assert!(
result.is_err(),
"publish must fail when not all layers are staged"
);
}
#[test]
fn test_publish_computes_fingerprints() {
let (_dir, _store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(10).expect("publish");
let manifest = writer.last_published_manifest().expect("manifest");
assert_ne!(
manifest.graph_fingerprint, [0u8; 32],
"graph fingerprint must be computed"
);
assert_ne!(
manifest.search_fingerprint, [0u8; 32],
"search fingerprint must be computed"
);
}
#[test]
fn test_current_file_write_atomic() {
let (dir, _store, mut writer) = open_writer();
stage_all_layers(&mut writer);
writer.publish(7).expect("publish");
let current_path = dir.path().join("CURRENT");
assert!(current_path.exists());
assert!(!dir.path().join("CURRENT.tmp").exists());
assert!(!dir.path().join("CURRENT.partial").exists());
let content = fs::read_to_string(¤t_path).expect("read CURRENT");
assert_eq!(content.trim(), "7");
}