#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::float_cmp,
clippy::approx_constant
)]
use super::*;
use serde_json::json;
use tempfile::tempdir;
#[test]
fn test_storage_new_creates_files() {
let dir = tempdir().unwrap();
let storage = MmapStorage::new(dir.path(), 3).unwrap();
assert!(dir.path().join("vectors.dat").exists());
assert!(dir.path().join("vectors.wal").exists());
assert_eq!(storage.len(), 0);
}
#[test]
fn test_storage_store_and_retrieve() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
let vector = vec![1.0, 2.0, 3.0];
storage.store(1, &vector).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(vector));
assert_eq!(storage.len(), 1);
}
#[test]
fn test_storage_persistence() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let vector = vec![1.0, 2.0, 3.0];
{
let mut storage = MmapStorage::new(&path, 3).unwrap();
storage.store(1, &vector).unwrap();
storage.flush().unwrap();
}
let storage = MmapStorage::new(&path, 3).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(vector));
assert_eq!(storage.len(), 1);
}
#[test]
fn test_drop_reopen_after_flush_preserves_vectors() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let dim = 4;
{
let mut storage = MmapStorage::new(&path, dim).unwrap();
for i in 0u64..16 {
let vector = vec![i as f32, i as f32 + 1.0, i as f32 + 2.0, i as f32 + 3.0];
storage.store(i, &vector).unwrap();
}
storage.flush().unwrap();
}
let storage = MmapStorage::new(&path, dim).unwrap();
for i in 0u64..16 {
let expected = vec![i as f32, i as f32 + 1.0, i as f32 + 2.0, i as f32 + 3.0];
assert_eq!(storage.retrieve(i).unwrap(), Some(expected));
}
}
#[test]
fn test_drop_best_effort_mmap_lock_contention_non_blocking() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
let _mmap_guard = storage.mmap.read();
let start = std::time::Instant::now();
storage.flush_on_shutdown_best_effort();
assert!(
start.elapsed() < std::time::Duration::from_secs(1),
"best-effort shutdown flush should not block under mmap lock contention"
);
}
#[test]
fn test_drop_best_effort_wal_lock_contention_non_blocking() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
let _wal_guard = storage.wal.write();
let start = std::time::Instant::now();
storage.flush_on_shutdown_best_effort();
assert!(
start.elapsed() < std::time::Duration::from_secs(1),
"best-effort shutdown flush should not block under WAL lock contention"
);
}
#[test]
fn test_storage_delete() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
let vector = vec![1.0, 2.0, 3.0];
storage.store(1, &vector).unwrap();
storage.delete(1).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, None);
assert_eq!(storage.len(), 0);
}
#[test]
fn test_storage_wal_recovery() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let vector = vec![1.0, 2.0, 3.0];
{
let mut storage = MmapStorage::new(&path, 3).unwrap();
storage.store(1, &vector).unwrap();
storage.flush().unwrap();
}
let storage = MmapStorage::new(&path, 3).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(vector));
}
#[test]
fn test_payload_storage_new() {
let dir = tempdir().unwrap();
let _storage = LogPayloadStorage::new(dir.path()).unwrap();
assert!(dir.path().join("payloads.log").exists());
}
#[test]
fn test_payload_storage_ops() {
let dir = tempdir().unwrap();
let mut storage = LogPayloadStorage::new(dir.path()).unwrap();
let payload = json!({"key": "value", "num": 42});
storage.store(1, &payload).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(payload.clone()));
storage.delete(1).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, None);
}
#[test]
fn test_payload_persistence() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let payload = json!({"foo": "bar"});
{
let mut storage = LogPayloadStorage::new(&path).unwrap();
storage.store(1, &payload).unwrap();
storage.flush().unwrap();
}
let storage = LogPayloadStorage::new(&path).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(payload));
}
#[test]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn test_mmap_storage_multiple_vectors() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let dim: usize = 4;
let mut storage = MmapStorage::new(&path, dim).unwrap();
for i in 0u64..10 {
let vector: Vec<f32> = (0..dim).map(|j| (i as usize * dim + j) as f32).collect();
storage.store(i, &vector).unwrap();
}
for i in 0u64..10 {
let expected: Vec<f32> = (0..dim).map(|j| (i as usize * dim + j) as f32).collect();
let retrieved = storage.retrieve(i).unwrap();
assert_eq!(retrieved, Some(expected));
}
}
#[test]
fn test_mmap_storage_update_vector() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let mut storage = MmapStorage::new(&path, 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
storage.store(1, &[4.0, 5.0, 6.0]).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(vec![4.0, 5.0, 6.0]));
}
#[test]
fn test_mmap_storage_retrieve_nonexistent() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let storage = MmapStorage::new(&path, 3).unwrap();
let retrieved = storage.retrieve(999).unwrap();
assert_eq!(retrieved, None);
}
#[test]
fn test_payload_storage_multiple_payloads() {
let dir = tempdir().unwrap();
let mut storage = LogPayloadStorage::new(dir.path()).unwrap();
for i in 0u64..5 {
let payload = json!({"id": i, "data": format!("payload_{}", i)});
storage.store(i, &payload).unwrap();
}
for i in 0u64..5 {
let retrieved = storage.retrieve(i).unwrap();
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap()["id"], i);
}
}
#[test]
fn test_payload_storage_retrieve_nonexistent() {
let dir = tempdir().unwrap();
let storage = LogPayloadStorage::new(dir.path()).unwrap();
let retrieved = storage.retrieve(999).unwrap();
assert_eq!(retrieved, None);
}
#[test]
fn test_payload_storage_complex_json() {
let dir = tempdir().unwrap();
let mut storage = LogPayloadStorage::new(dir.path()).unwrap();
let payload = json!({
"string": "hello",
"number": 42,
"float": 3.15,
"bool": true,
"null": null,
"array": [1, 2, 3],
"nested": {"key": "value"}
});
storage.store(1, &payload).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert_eq!(retrieved, Some(payload));
}
#[test]
fn test_retrieve_ref_returns_slice_without_allocation() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
let vector = vec![1.0, 2.0, 3.0];
storage.store(1, &vector).unwrap();
let guard = storage.retrieve_ref(1).unwrap();
assert!(guard.is_some());
let slice = guard.unwrap();
assert_eq!(slice.as_ref(), &[1.0, 2.0, 3.0]);
}
#[test]
fn test_retrieve_ref_nonexistent_returns_none() {
let dir = tempdir().unwrap();
let storage = MmapStorage::new(dir.path(), 3).unwrap();
let guard = storage.retrieve_ref(999).unwrap();
assert!(guard.is_none());
}
#[test]
fn test_retrieve_ref_multiple_concurrent_reads() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
storage.store(2, &[4.0, 5.0, 6.0]).unwrap();
let guard1 = storage.retrieve_ref(1).unwrap().unwrap();
let guard2 = storage.retrieve_ref(2).unwrap().unwrap();
assert_eq!(guard1.as_ref(), &[1.0, 2.0, 3.0]);
assert_eq!(guard2.as_ref(), &[4.0, 5.0, 6.0]);
}
#[test]
fn test_retrieve_ref_data_integrity_after_update() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
storage.store(1, &[7.0, 8.0, 9.0]).unwrap();
let guard = storage.retrieve_ref(1).unwrap().unwrap();
assert_eq!(guard.as_ref(), &[7.0, 8.0, 9.0]);
}
#[test]
#[allow(clippy::cast_precision_loss, clippy::float_cmp)]
fn test_retrieve_ref_large_dimension() {
let dir = tempdir().unwrap();
let dim = 768;
let mut storage = MmapStorage::new(dir.path(), dim).unwrap();
let vector: Vec<f32> = (0..dim).map(|i| i as f32).collect();
storage.store(1, &vector).unwrap();
let guard = storage.retrieve_ref(1).unwrap().unwrap();
assert_eq!(guard.as_ref().len(), dim);
assert_eq!(guard.as_ref()[0], 0.0);
assert_eq!(guard.as_ref()[767], 767.0);
}
#[test]
fn test_retrieve_ref_returns_invalid_data_on_misaligned_offset() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
storage.index.insert(42, 1);
let result = storage.retrieve_ref(42);
match result {
Err(err) => assert_eq!(err.kind(), std::io::ErrorKind::InvalidData),
Ok(_) => panic!("misaligned offset must not succeed"),
}
}
#[test]
#[allow(clippy::cast_precision_loss)]
fn test_compaction_reclaims_space() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let dim = 4;
let vector_size = dim * std::mem::size_of::<f32>();
let mut storage = MmapStorage::new(&path, dim).unwrap();
for i in 0u64..10 {
let vector: Vec<f32> = vec![i as f32; dim];
storage.store(i, &vector).unwrap();
}
for i in 0u64..5 {
storage.delete(i).unwrap();
}
let frag_before = storage.fragmentation_ratio();
assert!(frag_before > 0.4, "Should have ~50% fragmentation");
let reclaimed = storage.compact().unwrap();
assert_eq!(reclaimed, 5 * vector_size, "Should reclaim 5 vectors worth");
let frag_after = storage.fragmentation_ratio();
assert!(
frag_after < 0.01,
"Should have no fragmentation after compact"
);
for i in 5u64..10 {
let retrieved = storage.retrieve(i).unwrap();
assert!(retrieved.is_some(), "Vector {i} should exist");
#[allow(clippy::cast_precision_loss)]
let expected = vec![i as f32; dim];
assert_eq!(retrieved.unwrap(), expected);
}
for i in 0u64..5 {
let retrieved = storage.retrieve(i).unwrap();
assert!(retrieved.is_none(), "Vector {i} should be deleted");
}
}
#[test]
fn test_compaction_empty_storage() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
let reclaimed = storage.compact().unwrap();
assert_eq!(reclaimed, 0);
}
#[test]
fn test_compaction_no_fragmentation() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 3).unwrap();
storage.store(1, &[1.0, 2.0, 3.0]).unwrap();
storage.store(2, &[4.0, 5.0, 6.0]).unwrap();
let reclaimed = storage.compact().unwrap();
assert_eq!(reclaimed, 0);
}
#[test]
fn test_fragmentation_ratio() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 4).unwrap();
assert!(storage.fragmentation_ratio() < 0.01);
#[allow(clippy::cast_precision_loss)]
for i in 0u64..4 {
storage.store(i, &[i as f32; 4]).unwrap();
}
assert!(storage.fragmentation_ratio() < 0.01);
storage.delete(0).unwrap();
storage.delete(1).unwrap();
let frag = storage.fragmentation_ratio();
assert!(
frag > 0.4 && frag < 0.6,
"Expected ~50% fragmentation, got {frag}"
);
}
#[test]
fn test_reserve_capacity_preallocates() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 768).unwrap();
storage.reserve_capacity(10_000).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..1000 {
let v: Vec<f32> = (0..768).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
assert_eq!(storage.len(), 1000);
}
#[test]
fn test_aggressive_growth_reduces_resizes() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 128).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..5000 {
let v: Vec<f32> = (0..128).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
assert_eq!(storage.len(), 5000);
let v0 = storage.retrieve(0).unwrap().unwrap();
assert_eq!(v0.len(), 128);
let v4999 = storage.retrieve(4999).unwrap().unwrap();
assert_eq!(v4999.len(), 128);
}
#[test]
fn test_metrics_tracking_ensure_capacity() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 768).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..100 {
let v: Vec<f32> = (0..768).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
let stats = storage.metrics().ensure_capacity_latency_stats();
assert!(
stats.count > 0,
"Should have recorded ensure_capacity calls"
);
}
#[test]
fn test_metrics_resize_count() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 768).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..6000 {
let v: Vec<f32> = (0..768).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
assert!(
storage.metrics().resize_count() >= 1,
"Should have triggered at least one resize, got {}",
storage.metrics().resize_count()
);
}
#[test]
fn test_metrics_bytes_resized() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 768).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..6000 {
let v: Vec<f32> = (0..768).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
assert!(
storage.metrics().total_bytes_resized() > 0,
"Should have recorded bytes resized, got {}",
storage.metrics().total_bytes_resized()
);
}
#[test]
fn test_metrics_latency_percentiles() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 64).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..1000 {
let v: Vec<f32> = (0..64).map(|j| (i + j) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
let stats = storage.metrics().ensure_capacity_latency_stats();
assert!(stats.p50_us <= stats.p95_us, "P50 should be <= P95");
assert!(stats.p95_us <= stats.p99_us, "P95 should be <= P99");
assert!(stats.p99_us <= stats.max_us, "P99 should be <= max");
}
#[test]
fn test_compaction_then_resize_data_integrity() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
let dim = 4;
let mut storage = MmapStorage::new(&path, dim).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..20 {
let v: Vec<f32> = (0..dim).map(|j| (i * 10 + j as u64) as f32).collect();
storage.store(i, &v).unwrap();
}
for i in 0u64..10 {
storage.delete(i).unwrap();
}
let reclaimed = storage.compact().unwrap();
assert!(reclaimed > 0, "Should have reclaimed space");
#[allow(clippy::cast_precision_loss)]
for i in 100u64..200 {
let v: Vec<f32> = (0..dim).map(|j| (i * 10 + j as u64) as f32).collect();
storage.store(i, &v).unwrap();
}
#[allow(clippy::cast_precision_loss)]
for i in 10u64..20 {
let expected: Vec<f32> = (0..dim).map(|j| (i * 10 + j as u64) as f32).collect();
let retrieved = storage.retrieve(i).unwrap();
assert_eq!(
retrieved,
Some(expected),
"Vector {i} should be intact after compaction + resize"
);
}
#[allow(clippy::cast_precision_loss)]
for i in 100u64..200 {
let expected: Vec<f32> = (0..dim).map(|j| (i * 10 + j as u64) as f32).collect();
let retrieved = storage.retrieve(i).unwrap();
assert_eq!(
retrieved,
Some(expected),
"Vector {i} should be correctly stored after compaction"
);
}
storage.flush().unwrap();
drop(storage);
let storage2 = MmapStorage::new(&path, dim).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 10u64..20 {
let expected: Vec<f32> = (0..dim).map(|j| (i * 10 + j as u64) as f32).collect();
let retrieved = storage2.retrieve(i).unwrap();
assert_eq!(
retrieved,
Some(expected),
"Vector {i} should persist correctly after compaction + resize"
);
}
}
#[test]
fn test_guard_invalidation_after_resize() {
let dir = tempdir().unwrap();
let dim = 4;
let mut storage = MmapStorage::new(dir.path(), dim).unwrap();
storage.store(1, &[1.0, 2.0, 3.0, 4.0]).unwrap();
let guard_before = storage.retrieve_ref(1).unwrap().unwrap();
assert_eq!(guard_before.as_ref(), &[1.0, 2.0, 3.0, 4.0]);
drop(guard_before);
let guard_after = storage.retrieve_ref(1).unwrap().unwrap();
assert_eq!(
guard_after.as_ref(),
&[1.0, 2.0, 3.0, 4.0],
"Post-resize guard should return correct data"
);
}
#[test]
#[allow(clippy::cast_precision_loss)]
fn test_resize_epoch_increments_and_data_consistency() {
let dir = tempdir().unwrap();
let dim = 768;
let mut storage = MmapStorage::new(dir.path(), dim).unwrap();
for i in 0u64..6000 {
let v: Vec<f32> = (0..dim).map(|j| (i + j as u64) as f32 * 0.001).collect();
storage.store(i, &v).unwrap();
}
let resize_count = storage.metrics().resize_count();
assert!(
resize_count >= 1,
"Should have triggered at least one resize, got {resize_count}"
);
for check_id in [0u64, 100, 2000, 5999] {
let guard = storage.retrieve_ref(check_id).unwrap().unwrap();
let slice = guard.as_ref();
assert_eq!(slice.len(), dim, "Vector dimension must match");
let expected_first = (check_id) as f32 * 0.001;
assert!(
(slice[0] - expected_first).abs() < 1e-5,
"First element of vector {check_id} should be {expected_first}, got {}",
slice[0]
);
}
for check_id in [0u64, 5999] {
let copied = storage.retrieve(check_id).unwrap().unwrap();
let guard = storage.retrieve_ref(check_id).unwrap().unwrap();
assert_eq!(
copied.as_slice(),
guard.as_ref(),
"Copy and zero-copy retrieval must match for ID {check_id}"
);
}
}
#[test]
fn test_concurrent_snapshot_reads_consistency() {
let dir = tempdir().unwrap();
let dim = 32;
let mut storage = MmapStorage::new(dir.path(), dim).unwrap();
#[allow(clippy::cast_precision_loss)]
for i in 0u64..100 {
let v: Vec<f32> = (0..dim).map(|j| (i * 100 + j as u64) as f32).collect();
storage.store(i, &v).unwrap();
}
let guards: Vec<_> = (0u64..10)
.map(|i| storage.retrieve_ref(i).unwrap().unwrap())
.collect();
#[allow(clippy::cast_precision_loss)]
for (idx, guard) in guards.iter().enumerate() {
let expected_first = (idx as u64 * 100) as f32;
assert_eq!(
guard.as_ref()[0],
expected_first,
"Guard {idx} first element should be {expected_first}"
);
assert_eq!(guard.as_ref().len(), dim);
}
drop(guards);
#[allow(clippy::cast_precision_loss)]
for i in 0u64..100 {
let expected: Vec<f32> = (0..dim).map(|j| (i * 100 + j as u64) as f32).collect();
let retrieved = storage.retrieve(i).unwrap();
assert_eq!(
retrieved,
Some(expected),
"Vector {i} must be consistent after concurrent reads"
);
}
}
#[test]
fn test_epoch_mismatch_detection() {
use std::sync::atomic::{AtomicU64, Ordering};
let epoch = AtomicU64::new(0);
let guard_epoch = epoch.load(Ordering::Acquire);
assert_eq!(guard_epoch, 0);
epoch.fetch_add(1, Ordering::Release);
let current = epoch.load(Ordering::Acquire);
assert_ne!(
guard_epoch, current,
"After resize, guard epoch should differ from current"
);
epoch.fetch_add(1, Ordering::Release);
epoch.fetch_add(1, Ordering::Release);
let current = epoch.load(Ordering::Acquire);
assert_eq!(current, 3, "Epoch should track number of resizes");
assert_ne!(guard_epoch, current, "Stale guard still invalid");
}
#[test]
#[allow(clippy::cast_precision_loss)]
fn test_interleaved_store_and_snapshot_reads() {
let dir = tempdir().unwrap();
let dim = 8;
let mut storage = MmapStorage::new(dir.path(), dim).unwrap();
for round in 0u64..50 {
let v: Vec<f32> = (0..dim).map(|j| (round * 10 + j as u64) as f32).collect();
storage.store(round, &v).unwrap();
let guard = storage.retrieve_ref(round).unwrap().unwrap();
assert_eq!(
guard.as_ref(),
v.as_slice(),
"Guard for vector {round} must match stored data immediately"
);
if round > 0 && round % 10 == 0 {
for prev in 0..round {
let expected: Vec<f32> = (0..dim).map(|j| (prev * 10 + j as u64) as f32).collect();
let prev_guard = storage.retrieve_ref(prev).unwrap().unwrap();
assert_eq!(
prev_guard.as_ref(),
expected.as_slice(),
"Previously stored vector {prev} must remain intact at round {round}"
);
}
}
}
}
#[test]
fn test_hole_punch_on_delete() {
use super::compaction::punch_hole;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_hole_punch.dat");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&file_path)
.unwrap();
file.set_len(64 * 1024).unwrap();
let mut file_clone = file.try_clone().unwrap();
file_clone.seek(SeekFrom::Start(4096)).unwrap();
file_clone.write_all(&[0xAB; 4096]).unwrap();
file_clone.flush().unwrap();
let result = punch_hole(&file, 4096, 4096);
assert!(result.is_ok(), "punch_hole should succeed");
let _reclaimed = result.unwrap();
let mut file_read = file.try_clone().unwrap();
file_read.seek(SeekFrom::Start(4096)).unwrap();
let mut buf = [0u8; 4096];
file_read.read_exact(&mut buf).unwrap();
assert!(
buf.iter().all(|&b| b == 0),
"Hole-punched region should be zeroed"
);
}
#[test]
fn test_delete_triggers_hole_punch() {
let dir = tempdir().unwrap();
let mut storage = MmapStorage::new(dir.path(), 4).unwrap();
storage.store(1, &[1.0, 2.0, 3.0, 4.0]).unwrap();
storage.flush().unwrap();
storage.delete(1).unwrap();
let retrieved = storage.retrieve(1).unwrap();
assert!(retrieved.is_none(), "Vector should be deleted");
assert_eq!(storage.len(), 0);
}
#[test]
fn test_hole_punch_fallback_zeros_data() {
use super::compaction::punch_hole;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_fallback.dat");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&file_path)
.unwrap();
let mut f = file.try_clone().unwrap();
f.write_all(&[0xFF; 1024]).unwrap();
f.flush().unwrap();
let result = punch_hole(&file, 0, 512);
assert!(result.is_ok());
let mut f = file.try_clone().unwrap();
f.seek(SeekFrom::Start(0)).unwrap();
let mut buf = [0u8; 512];
f.read_exact(&mut buf).unwrap();
assert!(
buf.iter().all(|&b| b == 0),
"First 512 bytes should be zeroed"
);
let mut buf2 = [0u8; 512];
f.read_exact(&mut buf2).unwrap();
assert!(
buf2.iter().all(|&b| b == 0xFF),
"Remaining bytes should be unchanged"
);
}