#![cfg(feature = "persistent-artrie")]
use libdictenstein::persistent_artrie::{CompactionConfig, CompactionProgress, PersistentARTrie};
use libdictenstein::{Dictionary, MappedDictionary};
use std::fs;
use std::sync::atomic::{AtomicU64, Ordering};
use tempfile::tempdir;
#[test]
fn test_compact_empty_trie() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("empty_compact.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
let mut progress_calls = 0;
let stats = trie
.compact(CompactionConfig::default(), |_| {
progress_calls += 1;
})
.expect("Failed to compact");
assert_eq!(stats.terms_copied, 0);
assert_eq!(trie.len(), Some(0));
assert!(path.exists());
}
#[test]
fn test_compact_preserves_all_data() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("preserve_data.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
let test_terms = vec![
("apple", 1u64),
("application", 2),
("banana", 3),
("bandana", 4),
("cat", 5),
("caterpillar", 6),
("dog", 7),
("elephant", 8),
("zebra", 9),
];
for (term, value) in &test_terms {
trie.insert_with_value(term, *value);
}
trie.checkpoint().expect("Failed to checkpoint");
let original_count = trie.len();
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
assert_eq!(stats.terms_copied, test_terms.len() as u64);
assert_eq!(trie.len(), original_count);
for (term, value) in &test_terms {
assert!(
trie.contains(term),
"Term '{}' not found after compaction",
term
);
assert_eq!(
trie.get_value(term),
Some(*value),
"Value for '{}' incorrect after compaction",
term
);
}
}
#[test]
fn test_compact_after_modifications() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("modifications_compact.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..100 {
trie.insert_with_value(&format!("term_{:03}", i), i as u64);
}
trie.checkpoint().expect("Failed to first checkpoint");
let size_after_first = fs::metadata(&path).expect("metadata").len();
for i in 0..50 {
trie.insert_with_value(&format!("term_{:03}", i), (i * 100) as u64);
}
trie.checkpoint().expect("Failed to second checkpoint");
let size_before_compact = fs::metadata(&path).expect("metadata").len();
assert!(
size_before_compact >= size_after_first,
"Size should grow or stay same after modifications"
);
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
let size_after_compact = fs::metadata(&path).expect("metadata").len();
assert!(
size_after_compact <= size_before_compact,
"Compacted size ({}) should be <= pre-compact size ({})",
size_after_compact,
size_before_compact
);
assert_eq!(stats.terms_copied, 100);
for i in 0..100 {
assert!(
trie.contains(&format!("term_{:03}", i)),
"term_{:03} should exist after compaction",
i
);
}
}
#[test]
fn test_compact_multiple_checkpoints() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("multi_checkpoint.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..50 {
trie.insert_with_value(&format!("batch1_{:02}", i), i as u64);
}
trie.checkpoint().expect("Failed to checkpoint 1");
let size1 = fs::metadata(&path).expect("metadata").len();
for i in 0..50 {
trie.insert_with_value(&format!("batch2_{:02}", i), (i + 100) as u64);
}
trie.checkpoint().expect("Failed to checkpoint 2");
let size2 = fs::metadata(&path).expect("metadata").len();
for i in 0..50 {
trie.insert_with_value(&format!("batch3_{:02}", i), (i + 200) as u64);
}
trie.checkpoint().expect("Failed to checkpoint 3");
let size3 = fs::metadata(&path).expect("metadata").len();
assert!(size2 >= size1);
assert!(size3 >= size2);
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
let _size_after = fs::metadata(&path).expect("metadata").len();
assert_eq!(stats.terms_copied, 150);
for i in 0..50 {
assert!(trie.contains(&format!("batch1_{:02}", i)));
assert!(trie.contains(&format!("batch2_{:02}", i)));
assert!(trie.contains(&format!("batch3_{:02}", i)));
}
}
#[test]
fn test_compact_to_new_file() {
let dir = tempdir().expect("Failed to create temp dir");
let original_path = dir.path().join("original.artrie");
let compacted_path = dir.path().join("compacted.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&original_path).expect("Failed to create trie");
for i in 0..100 {
trie.insert_with_value(&format!("term_{:03}", i), i as u64);
}
trie.checkpoint().expect("Failed to checkpoint");
let _original_size = fs::metadata(&original_path).expect("metadata").len();
let config = CompactionConfig {
output_path: Some(compacted_path.clone()),
progress_interval: 10,
verify_after_compact: true,
};
let _stats = trie.compact(config, |_| {}).expect("Failed to compact");
assert!(original_path.exists(), "Original file should still exist");
assert!(compacted_path.exists(), "Compacted file should exist");
assert_eq!(trie.len(), Some(100));
let compacted_trie: PersistentARTrie<u64> =
PersistentARTrie::open(&compacted_path).expect("Failed to open compacted trie");
assert_eq!(compacted_trie.len(), Some(100));
for i in 0..100 {
assert_eq!(
compacted_trie.get_value(&format!("term_{:03}", i)),
Some(i as u64)
);
}
}
#[test]
fn test_compact_progress_callback() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("progress_test.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..50 {
trie.insert_with_value(&format!("term_{:03}", i), i as u64);
}
trie.checkpoint().expect("Failed to checkpoint");
let mut phases_seen: Vec<String> = Vec::new();
let progress_count = AtomicU64::new(0);
let config = CompactionConfig {
output_path: None,
progress_interval: 10, verify_after_compact: true,
};
let stats = trie
.compact(config, |progress: CompactionProgress| {
progress_count.fetch_add(1, Ordering::SeqCst);
phases_seen.push(progress.phase.to_string());
})
.expect("Failed to compact");
let count = progress_count.load(Ordering::SeqCst);
assert!(
count >= 4,
"Expected at least 4 progress callbacks, got {}",
count
);
assert!(
phases_seen.contains(&"copying".to_string()),
"Should see 'copying' phase"
);
assert!(
phases_seen.contains(&"checkpointing".to_string()),
"Should see 'checkpointing' phase"
);
assert!(
phases_seen.contains(&"verifying".to_string()),
"Should see 'verifying' phase"
);
assert!(
phases_seen.contains(&"finalizing".to_string()),
"Should see 'finalizing' phase"
);
assert_eq!(stats.terms_copied, 50);
}
#[test]
fn test_compact_without_verification() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("no_verify.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..20 {
trie.insert_with_value(&format!("term_{:02}", i), i as u64);
}
trie.checkpoint().expect("Failed to checkpoint");
let config = CompactionConfig {
output_path: None,
progress_interval: 0, verify_after_compact: false, };
let mut phases_seen: Vec<String> = Vec::new();
let _stats = trie
.compact(config, |progress| {
phases_seen.push(progress.phase.to_string());
})
.expect("Failed to compact");
assert!(
!phases_seen.contains(&"verifying".to_string()),
"Should not see 'verifying' phase when verification is disabled"
);
assert_eq!(trie.len(), Some(20));
for i in 0..20 {
assert!(trie.contains(&format!("term_{:02}", i)));
}
}
#[test]
fn test_compact_single_term() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("single_term.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
trie.insert_with_value("single", 42);
trie.checkpoint().expect("Failed to checkpoint");
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
assert_eq!(stats.terms_copied, 1);
assert_eq!(trie.len(), Some(1));
assert_eq!(trie.get_value("single"), Some(42));
}
#[test]
fn test_compact_large_values() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("large_values.artrie");
let mut trie: PersistentARTrie<String> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..20 {
let large_value = "x".repeat(100 + i); trie.insert_with_value(&format!("k{:02}", i), large_value);
}
trie.checkpoint().expect("Failed to checkpoint");
let config = CompactionConfig {
verify_after_compact: false,
..Default::default()
};
let stats = trie.compact(config, |_| {}).expect("Failed to compact");
assert!(stats.terms_copied > 0, "Should have copied some terms");
for i in 0..20 {
let val = trie.get_value(&format!("k{:02}", i));
assert!(
val.is_some(),
"Value for k{:02} should exist after compaction",
i
);
}
}
#[test]
fn test_compact_stats_accuracy() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("stats_test.artrie");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
for i in 0..100 {
trie.insert_with_value(&format!("term_{:03}", i), i as u64);
}
trie.checkpoint().expect("Failed to checkpoint");
let original_size = fs::metadata(&path).expect("metadata").len();
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
let final_size = fs::metadata(&path).expect("metadata").len();
assert_eq!(stats.terms_copied, 100);
assert_eq!(stats.original_bytes, original_size);
assert_eq!(stats.compacted_bytes, final_size);
let expected_savings = (1.0 - (final_size as f64 / original_size as f64)) * 100.0;
assert!(
(stats.space_savings_percent - expected_savings).abs() < 0.01,
"Space savings calculation incorrect: expected {}, got {}",
expected_savings,
stats.space_savings_percent
);
assert!(stats.duration_ms > 0 || stats.terms_copied == 0);
}
#[test]
fn test_compact_cleans_up_stale_temp_file() {
let dir = tempdir().expect("Failed to create temp dir");
let path = dir.path().join("stale_cleanup.artrie");
let temp_path = path.with_extension("compacting");
fs::write(&temp_path, b"stale data").expect("Failed to create stale file");
let mut trie: PersistentARTrie<u64> =
PersistentARTrie::create(&path).expect("Failed to create trie");
trie.insert_with_value("test", 1);
trie.checkpoint().expect("Failed to checkpoint");
let stats = trie
.compact(CompactionConfig::default(), |_| {})
.expect("Failed to compact");
assert_eq!(stats.terms_copied, 1);
assert!(!temp_path.exists(), "Stale temp file should be cleaned up");
}