mirror of
https://github.com/PR0M3TH3AN/Marlin.git
synced 2025-09-08 07:08:44 +00:00
273 lines
9.2 KiB
Rust
273 lines
9.2 KiB
Rust
//! Tests for the file system watcher functionality
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// Updated import for BackupManager from the new backup module
|
|
use crate::backup::BackupManager;
|
|
// These are still from the watcher module
|
|
use crate::db::open as open_marlin_db;
|
|
use crate::watcher::{FileWatcher, WatcherConfig, WatcherState}; // Use your project's DB open function
|
|
use crate::Marlin;
|
|
|
|
use std::fs::{self, File};
|
|
use std::io::Write;
|
|
// No longer need: use std::path::PathBuf;
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
use tempfile::tempdir;
|
|
|
|
/// Polls the DB until `query` returns `expected` or the timeout elapses.
|
|
fn wait_for_row_count(
|
|
marlin: &Marlin,
|
|
path: &std::path::Path,
|
|
expected: i64,
|
|
timeout: Duration,
|
|
) {
|
|
let start = Instant::now();
|
|
loop {
|
|
let count: i64 = marlin
|
|
.conn()
|
|
.query_row(
|
|
"SELECT COUNT(*) FROM files WHERE path = ?1",
|
|
[path.to_string_lossy()],
|
|
|r| r.get(0),
|
|
)
|
|
.unwrap();
|
|
if count == expected {
|
|
break;
|
|
}
|
|
if start.elapsed() > timeout {
|
|
panic!(
|
|
"Timed out waiting for {} rows for {}",
|
|
expected,
|
|
path.display()
|
|
);
|
|
}
|
|
thread::sleep(Duration::from_millis(50));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_watcher_lifecycle() {
|
|
// Create a temp directory for testing
|
|
let temp_dir = tempdir().expect("Failed to create temp directory");
|
|
let temp_path = temp_dir.path();
|
|
|
|
// Create a test file
|
|
let test_file_path = temp_path.join("test.txt");
|
|
let mut file = File::create(&test_file_path).expect("Failed to create test file");
|
|
writeln!(file, "Test content").expect("Failed to write to test file");
|
|
drop(file);
|
|
|
|
// Configure and start the watcher
|
|
let config = WatcherConfig {
|
|
debounce_ms: 100,
|
|
batch_size: 10,
|
|
max_queue_size: 100,
|
|
drain_timeout_ms: 1000,
|
|
};
|
|
|
|
let mut watcher = FileWatcher::new(vec![temp_path.to_path_buf()], config)
|
|
.expect("Failed to create watcher");
|
|
|
|
watcher.start().expect("Failed to start watcher");
|
|
assert_eq!(watcher.status().unwrap().state, WatcherState::Watching);
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
let new_file_path = temp_path.join("new_file.txt");
|
|
let mut new_file_handle = File::create(&new_file_path).expect("Failed to create new file");
|
|
writeln!(new_file_handle, "New file content").expect("Failed to write to new file");
|
|
drop(new_file_handle);
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
let mut existing_file_handle = fs::OpenOptions::new()
|
|
.write(true)
|
|
.append(true)
|
|
.open(&test_file_path)
|
|
.expect("Failed to open test file for modification");
|
|
writeln!(existing_file_handle, "Additional content")
|
|
.expect("Failed to append to test file");
|
|
drop(existing_file_handle);
|
|
|
|
thread::sleep(Duration::from_millis(200));
|
|
fs::remove_file(&new_file_path).expect("Failed to remove file");
|
|
|
|
thread::sleep(Duration::from_millis(1500));
|
|
watcher.stop().expect("Failed to stop watcher");
|
|
|
|
assert_eq!(watcher.status().unwrap().state, WatcherState::Stopped);
|
|
assert!(
|
|
watcher.status().unwrap().events_processed > 0,
|
|
"Expected some file events to be processed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_backup_manager_related_functionality() {
|
|
let live_db_tmp_dir = tempdir().expect("Failed to create temp directory for live DB");
|
|
let backups_storage_tmp_dir =
|
|
tempdir().expect("Failed to create temp directory for backups storage");
|
|
|
|
let live_db_path = live_db_tmp_dir.path().join("test_live_watcher.db"); // Unique name
|
|
let backups_actual_dir = backups_storage_tmp_dir.path().join("my_backups_watcher"); // Unique name
|
|
|
|
// Initialize a proper SQLite DB for the "live" database
|
|
let _conn = open_marlin_db(&live_db_path)
|
|
.expect("Failed to open test_live_watcher.db for backup test");
|
|
|
|
let backup_manager = BackupManager::new(&live_db_path, &backups_actual_dir)
|
|
.expect("Failed to create BackupManager instance");
|
|
|
|
let backup_info = backup_manager
|
|
.create_backup()
|
|
.expect("Failed to create first backup");
|
|
|
|
assert!(
|
|
backups_actual_dir.join(&backup_info.id).exists(),
|
|
"Backup file should exist"
|
|
);
|
|
assert!(
|
|
backup_info.size_bytes > 0,
|
|
"Backup size should be greater than 0"
|
|
);
|
|
|
|
for i in 0..3 {
|
|
std::thread::sleep(std::time::Duration::from_millis(30)); // Ensure timestamp difference
|
|
backup_manager
|
|
.create_backup()
|
|
.unwrap_or_else(|e| panic!("Failed to create additional backup {}: {:?}", i, e));
|
|
}
|
|
|
|
let backups = backup_manager
|
|
.list_backups()
|
|
.expect("Failed to list backups");
|
|
assert_eq!(backups.len(), 4, "Should have 4 backups listed");
|
|
|
|
let prune_result = backup_manager.prune(2).expect("Failed to prune backups");
|
|
|
|
assert_eq!(prune_result.kept.len(), 2, "Should have kept 2 backups");
|
|
assert_eq!(
|
|
prune_result.removed.len(),
|
|
2,
|
|
"Should have removed 2 backups (4 initial - 2 kept)"
|
|
);
|
|
|
|
let remaining_backups = backup_manager
|
|
.list_backups()
|
|
.expect("Failed to list backups after prune");
|
|
assert_eq!(
|
|
remaining_backups.len(),
|
|
2,
|
|
"Should have 2 backups remaining after prune"
|
|
);
|
|
|
|
for removed_info in prune_result.removed {
|
|
assert!(
|
|
!backups_actual_dir.join(&removed_info.id).exists(),
|
|
"Removed backup file {} should not exist",
|
|
removed_info.id
|
|
);
|
|
}
|
|
for kept_info in prune_result.kept {
|
|
assert!(
|
|
backups_actual_dir.join(&kept_info.id).exists(),
|
|
"Kept backup file {} should exist",
|
|
kept_info.id
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn rename_file_updates_db() {
|
|
let tmp = tempdir().unwrap();
|
|
let dir = tmp.path();
|
|
let file = dir.join("a.txt");
|
|
fs::write(&file, b"hi").unwrap();
|
|
let db_path = dir.join("test.db");
|
|
let mut marlin = Marlin::open_at(&db_path).unwrap();
|
|
marlin.scan(&[dir]).unwrap();
|
|
|
|
let mut watcher = marlin
|
|
.watch(
|
|
dir,
|
|
Some(WatcherConfig {
|
|
debounce_ms: 50,
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.unwrap();
|
|
|
|
thread::sleep(Duration::from_millis(100));
|
|
let new_file = dir.join("b.txt");
|
|
fs::rename(&file, &new_file).unwrap();
|
|
wait_for_row_count(&marlin, &new_file, 1, Duration::from_secs(10));
|
|
watcher.stop().unwrap();
|
|
assert!(
|
|
watcher.status().unwrap().events_processed > 0,
|
|
"rename event should be processed"
|
|
);
|
|
|
|
let count: i64 = marlin
|
|
.conn()
|
|
.query_row(
|
|
"SELECT COUNT(*) FROM files WHERE path = ?1",
|
|
[new_file.to_string_lossy()],
|
|
|r| r.get(0),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(count, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn rename_directory_updates_children() {
|
|
let tmp = tempdir().unwrap();
|
|
let dir = tmp.path();
|
|
let sub = dir.join("old");
|
|
fs::create_dir(&sub).unwrap();
|
|
let f1 = sub.join("one.txt");
|
|
fs::write(&f1, b"1").unwrap();
|
|
let f2 = sub.join("two.txt");
|
|
fs::write(&f2, b"2").unwrap();
|
|
|
|
let db_path = dir.join("test2.db");
|
|
let mut marlin = Marlin::open_at(&db_path).unwrap();
|
|
marlin.scan(&[dir]).unwrap();
|
|
|
|
let mut watcher = marlin
|
|
.watch(
|
|
dir,
|
|
Some(WatcherConfig {
|
|
debounce_ms: 50,
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.unwrap();
|
|
|
|
thread::sleep(Duration::from_millis(100));
|
|
let new = dir.join("newdir");
|
|
fs::rename(&sub, &new).unwrap();
|
|
for fname in ["one.txt", "two.txt"] {
|
|
let p = new.join(fname);
|
|
wait_for_row_count(&marlin, &p, 1, Duration::from_secs(10));
|
|
}
|
|
watcher.stop().unwrap();
|
|
assert!(
|
|
watcher.status().unwrap().events_processed > 0,
|
|
"rename event should be processed"
|
|
);
|
|
|
|
for fname in ["one.txt", "two.txt"] {
|
|
let p = new.join(fname);
|
|
let cnt: i64 = marlin
|
|
.conn()
|
|
.query_row(
|
|
"SELECT COUNT(*) FROM files WHERE path = ?1",
|
|
[p.to_string_lossy()],
|
|
|r| r.get(0),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(cnt, 1, "{} missing", p.display());
|
|
}
|
|
}
|
|
}
|