use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tempfile::TempDir;
use crate::error::HoldError;
use crate::metadata::{clean_metadata, load_metadata, save_metadata};
use crate::state::{AutoCapRun, FileState, METADATA_VERSION, StateMetadata};
#[test]
fn test_save_and_load_metadata() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut metadata = StateMetadata::new();
metadata
.upsert(FileState {
path: PathBuf::from("test.rs"),
size: 1234,
hash: "abcdef".to_string(),
mtime_nanos: SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos(),
})
.unwrap();
save_metadata(&metadata, &metadata_path).unwrap();
assert!(metadata_path.exists());
let loaded_metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(loaded_metadata.len(), 1);
assert!(loaded_metadata.contains(&PathBuf::from("test.rs")).unwrap());
}
#[test]
fn test_load_nonexistent_metadata() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("nonexistent.metadata");
let metadata = load_metadata(&metadata_path).unwrap();
assert!(metadata.is_empty());
}
#[test]
fn test_clean_metadata() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let metadata = StateMetadata::new();
save_metadata(&metadata, &metadata_path).unwrap();
assert!(metadata_path.exists());
clean_metadata(&metadata_path).unwrap();
assert!(!metadata_path.exists());
clean_metadata(&metadata_path).unwrap();
}
#[test]
fn test_atomic_save() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let metadata = StateMetadata::new();
save_metadata(&metadata, &metadata_path).unwrap();
let temp_path = metadata_path.with_extension("tmp");
assert!(!temp_path.exists());
assert!(metadata_path.exists());
}
#[test]
fn test_metadata_version() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut metadata = StateMetadata::new();
metadata
.upsert(FileState {
path: PathBuf::from("test.rs"),
size: 100,
hash: "hash".to_string(),
mtime_nanos: 123456789,
})
.unwrap();
save_metadata(&metadata, &metadata_path).unwrap();
let loaded_metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(loaded_metadata.version, METADATA_VERSION);
assert_eq!(loaded_metadata.len(), 1);
}
#[test]
fn test_fresh_auto_cap_metrics_round_trip() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut metadata = StateMetadata::new();
metadata.gc_metrics.runs = 1;
metadata.gc_metrics.last_suggested_cap = Some(4096);
metadata.gc_metrics.recent_auto_cap_runs.push(AutoCapRun {
cap: 4096,
initial_size: 8192,
final_size: 6144,
bytes_freed: 2048,
protected_artifact_bytes: 5120,
eligible_artifact_bytes: 2048,
retained_artifact_bytes: 5120,
preserved_binary_bytes: 512,
unrecognized_bytes: 512,
});
save_metadata(&metadata, &metadata_path).unwrap();
let loaded = load_metadata(&metadata_path).unwrap();
assert_eq!(loaded.version, METADATA_VERSION);
assert_eq!(loaded.gc_metrics, metadata.gc_metrics);
}
#[test]
fn test_older_metadata_version_is_discarded_without_migration() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut metadata = StateMetadata::new();
metadata.version = METADATA_VERSION - 1;
metadata.gc_metrics.last_suggested_cap = Some(123);
save_metadata(&metadata, &metadata_path).unwrap();
let loaded = load_metadata(&metadata_path).unwrap();
assert_eq!(loaded.version, METADATA_VERSION);
assert_eq!(loaded.gc_metrics, Default::default());
assert!(!metadata_path.exists());
}
#[test]
fn test_last_gc_mtime_nanos_preservation() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut metadata = StateMetadata::new();
metadata
.upsert(FileState {
path: PathBuf::from("file1.rs"),
size: 100,
hash: "hash1".to_string(),
mtime_nanos: 1000000000,
})
.unwrap();
metadata
.upsert(FileState {
path: PathBuf::from("file2.rs"),
size: 200,
hash: "hash2".to_string(),
mtime_nanos: 2000000000,
})
.unwrap();
assert_eq!(metadata.max_mtime_nanos(), Some(2000000000));
save_metadata(&metadata, &metadata_path).unwrap();
let loaded = load_metadata(&metadata_path).unwrap();
let mut new_metadata = StateMetadata::new();
new_metadata.last_gc_mtime_nanos = loaded.max_mtime_nanos();
new_metadata
.upsert(FileState {
path: PathBuf::from("file3.rs"),
size: 300,
hash: "hash3".to_string(),
mtime_nanos: 3000000000,
})
.unwrap();
save_metadata(&new_metadata, &metadata_path).unwrap();
let final_metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(final_metadata.last_gc_mtime_nanos, Some(2000000000));
assert_eq!(final_metadata.max_mtime_nanos(), Some(3000000000));
}
#[test]
fn test_format_incompatibility_recovery() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let invalid_data = b"this is not valid rkyv data and should cause deserialization to fail";
fs::write(&metadata_path, invalid_data).unwrap();
assert!(metadata_path.exists());
assert_eq!(fs::read(&metadata_path).unwrap(), invalid_data);
let result = load_metadata(&metadata_path);
assert!(result.is_ok());
let metadata = result.unwrap();
assert_eq!(metadata.version, METADATA_VERSION);
assert_eq!(metadata.len(), 0);
assert!(metadata.last_gc_mtime_nanos.is_none());
assert!(!metadata_path.exists());
}
#[test]
fn test_format_incompatibility_with_subsequent_save() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let invalid_data = b"invalid rkyv data representing old format";
fs::write(&metadata_path, invalid_data).unwrap();
let mut metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(metadata.version, METADATA_VERSION);
assert_eq!(metadata.len(), 0);
metadata
.upsert(FileState {
path: PathBuf::from("test.rs"),
size: 100,
hash: "testhash".to_string(),
mtime_nanos: 1234567890,
})
.unwrap();
save_metadata(&metadata, &metadata_path).unwrap();
let reloaded = load_metadata(&metadata_path).unwrap();
assert_eq!(reloaded.version, METADATA_VERSION);
assert_eq!(reloaded.len(), 1);
assert!(reloaded.get(Path::new("test.rs")).unwrap().is_some());
}
#[test]
fn test_future_version_handling() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let mut future_metadata = StateMetadata::new();
future_metadata.version = METADATA_VERSION + 1;
save_metadata(&future_metadata, &metadata_path).unwrap();
let result = load_metadata(&metadata_path);
assert!(result.is_err());
match result.unwrap_err() {
HoldError::ConfigError(message) => {
assert!(message.contains("newer than supported"));
assert!(message.contains(&(METADATA_VERSION + 1).to_string()));
}
other => panic!("Expected ConfigError, got: {other:?}"),
}
}
#[test]
fn test_real_world_incompatible_format_scenario() {
let temp_dir = TempDir::new().unwrap();
let metadata_path = temp_dir.path().join("test.metadata");
let problematic_data = [
0x72, 0x6b, 0x79, 0x76, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
fs::write(&metadata_path, problematic_data).unwrap();
let metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(metadata.version, METADATA_VERSION);
assert_eq!(metadata.len(), 0);
assert!(metadata.last_gc_mtime_nanos.is_none());
assert!(!metadata_path.exists());
let mut recovered = metadata;
recovered
.upsert(FileState {
path: PathBuf::from("recovered.rs"),
size: 42,
hash: "recovered".to_string(),
mtime_nanos: 12345,
})
.unwrap();
save_metadata(&recovered, &metadata_path).unwrap();
let final_metadata = load_metadata(&metadata_path).unwrap();
assert_eq!(final_metadata.len(), 1);
assert!(
final_metadata
.get(Path::new("recovered.rs"))
.unwrap()
.is_some()
);
}