use super::*;
use crate::embedder::HashEmbedder;
use crate::storage::NativeStore;
use crate::{FactStore, GraphStore};
use std::collections::BTreeMap;
pub(super) const NEW_DIM: usize = 8;
pub(super) const SEEDED: u64 = 5;
pub(super) fn root_with_source() -> tempfile::TempDir {
let root = tempfile::tempdir().expect("root");
let store = root.path().join("store");
std::fs::create_dir(&store).expect("mkdir store");
{
let native = NativeStore::open(&store, DIM).expect("open source");
for id in 1..=SEEDED {
native
.store_with_metadata(id, &format!("fact number {id}"), &EMBEDDING, &meta(&[]))
.expect("seed");
}
native.relate(1, 2, "mentions").expect("edge");
}
root
}
pub(super) fn contents(dir: &std::path::Path) -> BTreeMap<u64, serde_json::Value> {
super::preservation::read_out(dir, "_semantic_memory")
.iter()
.map(|fact| {
(
fact.id,
serde_json::from_str(&fact.payload).expect("payload json"),
)
})
.collect()
}
fn run_with(
root: &std::path::Path,
embedder: &dyn crate::Embedder,
) -> Result<super::super::ExecuteOutcome, crate::MemoryError> {
super::super::execute(
&root.join("store"),
root,
&TargetContract::automatic("hash", NEW_DIM),
&root.join("rebuilt"),
embedder,
1024,
)
}
pub(super) fn run(
root: &std::path::Path,
) -> Result<super::super::ExecuteOutcome, crate::MemoryError> {
run_with(root, &HashEmbedder::new(NEW_DIM))
}
struct DriftedEmbedder(HashEmbedder);
impl crate::Embedder for DriftedEmbedder {
fn dimension(&self) -> usize {
self.0.dimension()
}
fn embed(&self, text: &str) -> Result<Vec<f32>, crate::EmbedError> {
let mut vector = self.0.embed(text)?;
vector[0] += 1.0;
Ok(vector)
}
}
#[test]
fn execute_rebuilds_into_the_destination_and_releases_the_lock() {
let root = root_with_source();
let outcome = run(root.path()).expect("execute");
assert_eq!(outcome.rebuild.facts, SEEDED, "every fact must land");
assert_eq!(outcome.rebuild.edges, 1, "the edge must land");
assert_eq!(
contents(&outcome.destination),
contents(&root.path().join("store")),
"the destination must hold the same facts as the source"
);
let journalled = MigrationState::read(&outcome.workspace)
.expect("read journal")
.expect("journal exists");
assert_eq!(
journalled.phase,
Phase::Prepared,
"execute stops at Prepared"
);
assert!(
journalled
.progress
.values()
.all(|p| *p == CollectionProgress::Complete),
"the journal must record completion, got {:?}",
journalled.progress
);
MigrationLock::acquire(&outcome.workspace, "the-next-run")
.expect("a clean success must leave the workspace reacquirable")
.release()
.expect("release probe lock");
}
#[test]
fn a_second_execute_resumes_the_journal_and_replays_nothing() {
let root = root_with_source();
let first = run(root.path()).expect("first execute");
let second = run(root.path()).expect("second execute must resume, not refuse");
assert_eq!(
(
second.rebuild.facts,
second.rebuild.collisions,
second.rebuild.edges
),
(0, 0, 0),
"a journal at Complete has nothing to replay; any nonzero count means \
the resume re-did work the journal already recorded"
);
assert_eq!(
contents(&second.destination),
contents(&first.destination),
"the destination must be untouched by the no-op resume"
);
}
#[test]
fn a_resume_across_an_embedder_updated_in_place_is_refused_by_its_witness() {
let root = root_with_source();
run(root.path()).expect("first execute");
let refusal = run_with(root.path(), &DriftedEmbedder(HashEmbedder::new(NEW_DIM)))
.expect_err("same name, same dimension, different vectors must refuse the resume");
let message = refusal.to_string();
assert!(
message.contains("witness") && message.contains("fresh migration"),
"the refusal must name the witness and the recovery: {message}"
);
run(root.path()).expect("the same embedder must still resume");
}
#[test]
fn a_nonempty_destination_without_a_journal_is_refused() {
let root = root_with_source();
let destination = root.path().join("rebuilt");
std::fs::create_dir(&destination).expect("mkdir destination");
std::fs::write(destination.join("stray.txt"), b"not a rebuild").expect("stray file");
let refusal =
run(root.path()).expect_err("rebuilding into a directory that already holds something");
let message = refusal.to_string();
assert!(
message.contains("rebuilt"),
"the refusal must name the destination: {message}"
);
assert!(
std::fs::read(destination.join("stray.txt")).is_ok(),
"a refusal must not have touched the directory it refused"
);
}
#[test]
fn settling_a_store_is_idempotent_which_the_resume_fingerprint_rests_on() {
let root = root_with_source();
let store = root.path().join("store");
{
let _db = velesdb_core::Database::open(&store).expect("first open settles");
}
let settled = super::super::fingerprint(&store).expect("settled fingerprint");
{
let db = velesdb_core::Database::open(&store).expect("second open");
let _ = super::super::enumerate_by_cursor(&db, "_semantic_memory", 16).expect("walk");
}
assert_eq!(
super::super::fingerprint(&store).expect("fingerprint after reopen"),
settled,
"a second open (with a full cursor walk) must leave the settled tree \
byte-identical; if this ever fails, the journalled fingerprint can no \
longer prove the source unchanged across a resume, and `execute`'s \
settle-then-fingerprint design is void"
);
}
#[test]
fn a_refusing_regime_stops_execute_before_anything_is_created() {
let root = root_with_source();
let embedder = HashEmbedder::new(NEW_DIM);
let refusal = super::super::execute(
&root.path().join("store"),
root.path(),
&TargetContract {
model: "hash".to_owned(),
dimension: NEW_DIM,
strategy: Strategy::Reuse,
},
&root.path().join("rebuilt"),
&embedder,
1024,
)
.expect_err("a refused regime must refuse the execution");
let message = refusal.to_string();
assert!(
message.contains("reuse"),
"the refusal must name the requested regime: {message}"
);
assert!(
!root.path().join("rebuilt").exists(),
"a regime refusal must not leave a half-created destination behind"
);
}