use sqlitegraph::backend::native::GraphFile;
use sqlitegraph::backend::native::v2::wal::V2WALConfig;
use sqlitegraph::backend::native::v2::wal::checkpoint::{
CheckpointState, CheckpointStrategy, MultiFileCheckpointConfig, MultiFileRecovery,
V2WALCheckpointManager,
};
use std::time::Duration;
use tempfile::tempdir;
#[test]
fn test_checkpoint_exceeds_1gb() {
let temp_dir = tempdir().unwrap();
let v2_graph_path = temp_dir.path().join("test.v2");
let _graph_file =
GraphFile::create(&v2_graph_path).expect("Failed to create test V2 graph file");
let checkpoint_path = temp_dir.path().join("checkpoint");
let config = V2WALConfig {
wal_path: temp_dir.path().join("test.wal"),
checkpoint_path: temp_dir.path().join("test.checkpoint"),
max_wal_size: 64 * 1024 * 1024,
buffer_size: 1024 * 1024,
checkpoint_interval: 100,
enable_compression: false,
..Default::default()
};
let multi_file_config = MultiFileCheckpointConfig::new(checkpoint_path.clone())
.with_max_segment_size(10 * 1024 * 1024) .with_max_segments(16);
let strategy = CheckpointStrategy::TimeInterval(Duration::from_secs(60));
let manager = V2WALCheckpointManager::with_multi_file(config, strategy, multi_file_config)
.expect("Failed to create multi-file checkpoint manager");
assert!(manager.is_multi_file_enabled());
let retrieved_config = manager.get_multi_file_config();
assert!(retrieved_config.is_some());
let cfg = retrieved_config.unwrap();
assert_eq!(cfg.max_segment_size, 10 * 1024 * 1024);
assert_eq!(cfg.max_total_size(), 160 * 1024 * 1024);
}
#[test]
fn test_checkpoint_segment_rotation() {
use sqlitegraph::backend::native::v2::wal::checkpoint::io::SegmentWriter;
let temp_dir = tempdir().unwrap();
let checkpoint_path = temp_dir.path().join("checkpoint");
let multi_file_config = MultiFileCheckpointConfig::new(checkpoint_path.clone())
.with_max_segment_size(1 * 1024 * 1024) .with_max_segments(10);
let mut writer = SegmentWriter::create(multi_file_config.clone(), 0, 100)
.expect("Failed to create segment writer");
let data = vec![1u8; 900 * 1024]; writer.write_data(&data).expect("Failed to write data");
assert!(!writer.needs_rotation());
writer
.rotate_segment(200)
.expect("Failed to rotate segment");
assert_eq!(writer.current_index(), 1);
assert_eq!(writer.completed_segments().len(), 1);
let data2 = vec![2u8; 500 * 1024]; writer
.write_data(&data2)
.expect("Failed to write data to second segment");
let segment2 = writer
.finalize(300, 50)
.expect("Failed to finalize second segment");
assert_eq!(segment2.segment_index, 1);
assert_eq!(segment2.lsn_range, (200, 300));
assert_eq!(writer.completed_segments().len(), 2);
}
#[test]
fn test_recovery_partial_segments() {
use sqlitegraph::backend::native::v2::wal::checkpoint::io::{
CheckpointManifest, CheckpointSegmentMeta, SegmentWriter,
};
let temp_dir = tempdir().unwrap();
let checkpoint_path = temp_dir.path().join("checkpoint");
let multi_file_config = MultiFileCheckpointConfig::new(checkpoint_path.clone())
.with_max_segment_size(10 * 1024 * 1024)
.with_max_segments(4);
let mut manifest = CheckpointManifest::new();
manifest.add_segment(CheckpointSegmentMeta {
index: 0,
lsn_start: 100,
lsn_end: 200,
block_count: 50,
checksum: 12345,
size: 1024 * 1024,
});
manifest.add_segment(CheckpointSegmentMeta {
index: 1,
lsn_start: 200,
lsn_end: 300,
block_count: 50,
checksum: 67890,
size: 1024 * 1024,
});
MultiFileRecovery::write_manifest(&manifest, &checkpoint_path)
.expect("Failed to write manifest");
let segment0_path = checkpoint_path.with_extension("ckpt.000");
let mut writer = SegmentWriter::create(multi_file_config.clone(), 0, 100)
.expect("Failed to create segment 0");
writer
.write_data(&[1u8; 1024])
.expect("Failed to write data");
writer
.finalize(200, 50)
.expect("Failed to finalize segment 0");
assert!(segment0_path.exists());
assert!(!checkpoint_path.with_extension("ckpt.001").exists());
let result = MultiFileRecovery::recover_checkpoint(manifest, checkpoint_path);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("Segment file"));
}
#[test]
fn test_concurrent_checkpoint_limit() {
let temp_dir = tempdir().unwrap();
let v2_graph_path = temp_dir.path().join("test.v2");
let _graph_file =
GraphFile::create(&v2_graph_path).expect("Failed to create test V2 graph file");
let checkpoint_path = temp_dir.path().join("checkpoint");
let config = V2WALConfig {
wal_path: temp_dir.path().join("test.wal"),
checkpoint_path: temp_dir.path().join("test.checkpoint"),
max_wal_size: 64 * 1024 * 1024,
buffer_size: 1024 * 1024,
checkpoint_interval: 100,
enable_compression: false,
..Default::default()
};
let multi_file_config = MultiFileCheckpointConfig::new(checkpoint_path)
.with_max_segment_size(10 * 1024 * 1024)
.with_max_segments(4);
let strategy = CheckpointStrategy::TimeInterval(Duration::from_secs(1));
let manager = V2WALCheckpointManager::with_multi_file(config, strategy, multi_file_config)
.expect("Failed to create multi-file checkpoint manager");
assert_eq!(manager.get_state(), CheckpointState::Idle);
assert!(!manager.is_checkpoint_in_progress());
let _result = manager.checkpoint();
let state = manager.get_state();
assert!(state == CheckpointState::Idle || state == CheckpointState::Failed);
}
#[test]
fn test_manifest_creation_and_validation() {
use sqlitegraph::backend::native::v2::wal::checkpoint::io::{
CheckpointManifest, CheckpointSegmentMeta,
};
let temp_dir = tempdir().unwrap();
let checkpoint_path = temp_dir.path().join("checkpoint");
let mut manifest = CheckpointManifest::new();
manifest.timestamp = 1234567890;
for i in 0u64..3 {
manifest.add_segment(CheckpointSegmentMeta {
index: i as u32,
lsn_start: i * 1000,
lsn_end: (i + 1) * 1000,
block_count: 100,
checksum: i * 1000,
size: 1024 * 1024,
});
}
MultiFileRecovery::write_manifest(&manifest, &checkpoint_path)
.expect("Failed to write manifest");
let loaded = MultiFileRecovery::load_manifest(&checkpoint_path.with_extension("manifest"))
.expect("Failed to load manifest");
assert_eq!(loaded.segment_count, 3);
assert_eq!(loaded.timestamp, 1234567890);
assert_eq!(loaded.total_lsn_range, (0, 3000));
assert_eq!(loaded.total_block_count, 300);
let validation_result = manifest.validate();
assert!(validation_result.is_ok());
for (i, segment) in loaded.segments.iter().enumerate() {
assert_eq!(segment.index, i as u32);
}
}
#[test]
fn test_lsn_continuity_across_segments() {
use sqlitegraph::backend::native::v2::wal::checkpoint::io::{
CheckpointManifest, CheckpointSegmentMeta,
};
let mut manifest = CheckpointManifest::new();
manifest.add_segment(CheckpointSegmentMeta {
index: 0,
lsn_start: 1000,
lsn_end: 2000,
block_count: 100,
checksum: 1,
size: 1024,
});
manifest.add_segment(CheckpointSegmentMeta {
index: 1,
lsn_start: 2000,
lsn_end: 3000,
block_count: 100,
checksum: 2,
size: 1024,
});
manifest.add_segment(CheckpointSegmentMeta {
index: 2,
lsn_start: 3000,
lsn_end: 4000,
block_count: 100,
checksum: 3,
size: 1024,
});
let result = manifest.validate();
assert!(result.is_ok());
manifest.add_segment(CheckpointSegmentMeta {
index: 3,
lsn_start: 3500, lsn_end: 4500,
block_count: 100,
checksum: 4,
size: 1024,
});
let result = manifest.validate();
assert!(result.is_err());
}