use vecstore::wal::{LogEntry, WriteAheadLog};
#[test]
fn test_wal_create_and_open() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path);
assert!(wal.is_ok(), "Should be able to create new WAL");
drop(wal);
let mut wal = WriteAheadLog::open(&wal_path);
assert!(wal.is_ok(), "Should be able to reopen existing WAL");
}
#[test]
fn test_wal_append_insert() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entry = LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0, 2.0, 3.0],
};
let result = wal.append(entry);
assert!(result.is_ok(), "Should be able to append insert entry");
}
#[test]
fn test_wal_append_update() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entry = LogEntry::Update {
id: "doc1".to_string(),
vector: vec![4.0, 5.0, 6.0],
};
let result = wal.append(entry);
assert!(result.is_ok(), "Should be able to append update entry");
}
#[test]
fn test_wal_append_delete() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entry = LogEntry::Delete {
id: "doc1".to_string(),
};
let result = wal.append(entry);
assert!(result.is_ok(), "Should be able to append delete entry");
}
#[test]
fn test_wal_replay_empty() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 0, "Empty WAL should have no entries");
}
#[test]
fn test_wal_replay_single_entry() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entry = LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0, 2.0, 3.0],
};
wal.append(entry).unwrap();
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 1, "Should have one entry");
match &entries[0] {
LogEntry::Insert { id, vector } => {
assert_eq!(id, "doc1");
assert_eq!(vector, &vec![1.0, 2.0, 3.0]);
}
_ => panic!("Expected Insert entry"),
}
}
#[test]
fn test_wal_replay_multiple_entries() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0, 2.0, 3.0],
})
.unwrap();
wal.append(LogEntry::Update {
id: "doc1".to_string(),
vector: vec![4.0, 5.0, 6.0],
})
.unwrap();
wal.append(LogEntry::Delete {
id: "doc2".to_string(),
})
.unwrap();
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 3, "Should have three entries");
}
#[test]
fn test_wal_checkpoint() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
for i in 0..10 {
wal.append(LogEntry::Insert {
id: format!("doc{}", i),
vector: vec![i as f32, 0.0, 0.0],
})
.unwrap();
}
let result = wal.checkpoint();
assert!(result.is_ok(), "Checkpoint should succeed");
}
#[test]
fn test_wal_checkpoint_truncates_log() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
for i in 0..5 {
wal.append(LogEntry::Insert {
id: format!("doc{}", i),
vector: vec![i as f32],
})
.unwrap();
}
}
let size_before = std::fs::metadata(&wal_path).unwrap().len();
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.checkpoint().unwrap();
}
let size_after = std::fs::metadata(&wal_path).unwrap().len();
assert!(size_after >= 0, "WAL file should exist after checkpoint");
}
#[test]
fn test_wal_transaction_begin_commit() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::BeginTx { tx_id: 1 }).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0],
})
.unwrap();
wal.append(LogEntry::CommitTx { tx_id: 1 }).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 3);
}
#[test]
fn test_wal_transaction_abort() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::BeginTx { tx_id: 1 }).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0],
})
.unwrap();
wal.append(LogEntry::AbortTx { tx_id: 1 }).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 3);
}
#[test]
fn test_wal_flush() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0, 2.0, 3.0],
})
.unwrap();
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 1);
}
#[test]
fn test_wal_durability_after_crash() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
for i in 0..10 {
wal.append(LogEntry::Insert {
id: format!("doc{}", i),
vector: vec![i as f32, (i * 2) as f32],
})
.unwrap();
}
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 10, "All entries should be recovered");
for (i, entry) in entries.iter().enumerate() {
match entry {
LogEntry::Insert { id, vector } => {
assert_eq!(id, &format!("doc{}", i));
assert_eq!(vector, &vec![i as f32, (i * 2) as f32]);
}
_ => panic!("Expected Insert entry"),
}
}
}
#[test]
fn test_wal_large_vectors() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let large_vector: Vec<f32> = (0..1536).map(|i| i as f32 * 0.01).collect();
wal.append(LogEntry::Insert {
id: "large_doc".to_string(),
vector: large_vector.clone(),
})
.unwrap();
let entries = wal.replay().unwrap();
match &entries[0] {
LogEntry::Insert { vector, .. } => {
assert_eq!(vector.len(), 1536);
assert_eq!(vector, &large_vector);
}
_ => panic!("Expected Insert entry"),
}
}
#[test]
fn test_wal_sequence_ordering() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
for i in 0..20 {
wal.append(LogEntry::Insert {
id: format!("doc{:03}", i),
vector: vec![i as f32],
})
.unwrap();
}
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
for (i, entry) in entries.iter().enumerate() {
match entry {
LogEntry::Insert { id, vector } => {
assert_eq!(id, &format!("doc{:03}", i));
assert_eq!(vector, &vec![i as f32]);
}
_ => panic!("Expected Insert entry"),
}
}
}
#[test]
fn test_wal_empty_vectors() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let result = wal.append(LogEntry::Insert {
id: "empty".to_string(),
vector: vec![],
});
assert!(result.is_ok(), "Should handle empty vectors");
}
#[test]
fn test_wal_special_characters_in_id() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let special_ids = vec![
"doc with spaces",
"doc/with/slashes",
"doc:with:colons",
"doc@with@at",
"unicode-文档-🚀",
];
for id in special_ids {
wal.append(LogEntry::Insert {
id: id.to_string(),
vector: vec![1.0],
})
.unwrap();
}
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 5);
}
#[test]
fn test_wal_mixed_operations() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0, 2.0],
})
.unwrap();
wal.append(LogEntry::Update {
id: "doc1".to_string(),
vector: vec![3.0, 4.0],
})
.unwrap();
wal.append(LogEntry::Insert {
id: "doc2".to_string(),
vector: vec![5.0, 6.0],
})
.unwrap();
wal.append(LogEntry::Delete {
id: "doc1".to_string(),
})
.unwrap();
wal.append(LogEntry::Insert {
id: "doc3".to_string(),
vector: vec![7.0, 8.0],
})
.unwrap();
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 5);
}
#[test]
fn test_wal_reopen_preserves_data() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Insert {
id: "doc1".to_string(),
vector: vec![1.0],
})
.unwrap();
}
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Insert {
id: "doc2".to_string(),
vector: vec![2.0],
})
.unwrap();
}
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert_eq!(entries.len(), 2);
}
#[test]
fn test_wal_checkpoint_marker() {
let temp_dir = tempfile::tempdir().unwrap();
let wal_path = temp_dir.path().join("test.wal");
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
wal.append(LogEntry::Checkpoint { sequence: 100 }).unwrap();
}
{
let mut wal = WriteAheadLog::open(&wal_path).unwrap();
let entries = wal.replay().unwrap();
assert!(entries.len() >= 0, "Replay should succeed");
if entries.len() > 0 {
match &entries[0] {
LogEntry::Checkpoint { sequence } => {
assert_eq!(*sequence, 100);
}
_ => {} }
}
}
}