Skip to content

Commit 393e976

Browse files
committed
lsh: test corrupt snapshot rejection
1 parent c79478d commit 393e976

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

src/lsh/mod.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ pub struct LSHStats {
402402
#[allow(clippy::unwrap_used, clippy::expect_used)]
403403
mod tests {
404404
use super::*;
405+
use serde_json::Value;
406+
use std::path::Path;
405407

406408
/// Helper: create clustered test data.
407409
fn clustered_data(n_clusters: usize, points_per_cluster: usize, dim: usize) -> Vec<f32> {
@@ -436,6 +438,42 @@ mod tests {
436438
dists
437439
}
438440

441+
fn lsh_snapshot_dir() -> tempfile::TempDir {
442+
let dim = 8;
443+
let data = clustered_data(3, 8, dim);
444+
let params = LSHParams {
445+
num_tables: 4,
446+
num_probes: 2,
447+
seed: Some(42),
448+
};
449+
450+
let mut index = CrossPolytopeLSHIndex::new(dim, params).unwrap();
451+
index.add_vectors(&data).unwrap();
452+
index.build().unwrap();
453+
454+
let dir = tempfile::tempdir().unwrap();
455+
index.save_to_dir(dir.path()).unwrap();
456+
dir
457+
}
458+
459+
fn mutate_lsh_manifest(dir: &Path, mutator: impl FnOnce(&mut Value)) {
460+
let path = dir.join("manifest.json");
461+
let mut value: Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
462+
mutator(&mut value);
463+
std::fs::write(&path, serde_json::to_vec_pretty(&value).unwrap()).unwrap();
464+
}
465+
466+
fn assert_lsh_snapshot_load_fails(mutator: impl FnOnce(&Path)) {
467+
let dir = lsh_snapshot_dir();
468+
mutator(dir.path());
469+
let error = CrossPolytopeLSHIndex::load_from_dir(dir.path())
470+
.expect_err("corrupt LSH snapshot loaded successfully");
471+
assert!(
472+
matches!(error, RetrieveError::FormatError(_)),
473+
"expected LSH corrupt snapshot to fail as a format error, got {error:?}"
474+
);
475+
}
476+
439477
#[test]
440478
fn test_build_and_search() {
441479
let dim = 16;
@@ -641,6 +679,37 @@ mod tests {
641679
);
642680
}
643681

682+
#[test]
683+
fn load_rejects_bad_snapshot_magic() {
684+
assert_lsh_snapshot_load_fails(|dir| {
685+
mutate_lsh_manifest(dir, |value| value["magic"][0] = 0u32.into());
686+
});
687+
}
688+
689+
#[test]
690+
fn load_rejects_future_snapshot_version() {
691+
assert_lsh_snapshot_load_fails(|dir| {
692+
mutate_lsh_manifest(dir, |value| value["version"] = 999u32.into());
693+
});
694+
}
695+
696+
#[test]
697+
fn load_rejects_zero_snapshot_dimension() {
698+
assert_lsh_snapshot_load_fails(|dir| {
699+
mutate_lsh_manifest(dir, |value| value["dimension"] = 0u32.into());
700+
});
701+
}
702+
703+
#[test]
704+
fn load_rejects_truncated_vector_payload() {
705+
assert_lsh_snapshot_load_fails(|dir| {
706+
let path = dir.join("vectors.bin");
707+
let mut bytes = std::fs::read(&path).unwrap();
708+
bytes.pop().unwrap();
709+
std::fs::write(path, bytes).unwrap();
710+
});
711+
}
712+
644713
#[test]
645714
fn test_hash_determinism() {
646715
let dim = 8;

0 commit comments

Comments
 (0)