use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use std::{fs, thread};
use rand::prelude::*;
use tempfile::TempDir;
fn run_test_executable(log_dir: &Path, num_lines: usize, env_vars: &[(&str, &str)]) {
let executable_path = PathBuf::from(env!("CARGO_BIN_EXE_log_test_executable"));
let mut command = Command::new(&executable_path);
command.arg(log_dir.to_string_lossy().as_ref()).arg(num_lines.to_string());
for (key, value) in env_vars {
command.env(key, value);
}
let mut child = command.spawn().expect("Failed to execute test executable");
let status = child.wait().expect("Failed to wait for test executable");
if !status.success() {
panic!("Test executable failed with status: {}", status);
}
}
fn run_test_executable_parallel(log_dir: &Path, num_lines: usize, env_vars: &[(&str, &str)]) -> thread::JoinHandle<()> {
let executable_path = PathBuf::from(env!("CARGO_BIN_EXE_log_test_executable"));
let log_dir = log_dir.to_path_buf();
let env_vars: Vec<(String, String)> = env_vars.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
thread::spawn(move || {
let mut command = Command::new(&executable_path);
command.arg(log_dir.to_string_lossy().as_ref()).arg(num_lines.to_string());
for (key, value) in &env_vars {
command.env(key, value);
}
let mut child = command.spawn().expect("Failed to execute test executable");
let status = child.wait().expect("Failed to wait for test executable");
if !status.success() {
panic!("Test executable failed with status: {}", status);
}
})
}
fn get_directory_size(dir: &Path) -> u64 {
let mut total_size = 0;
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Ok(metadata) = entry.metadata()
&& metadata.is_file()
{
total_size += metadata.len();
}
}
}
total_size
}
fn count_log_files(dir: &Path) -> usize {
if let Ok(entries) = fs::read_dir(dir) {
entries
.flatten()
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "log"))
.count()
} else {
0
}
}
#[test]
fn test_maximum_age_cleanup() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
let env_vars = [
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "500ms"),
("HF_XET_LOG_DIR_MIN_DELETION_AGE", "100ms"),
("HF_XET_LOG_DIR_MAX_SIZE", "1gb"), ("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
for _ in 0..5 {
run_test_executable(log_dir, 100, &env_vars);
}
run_test_executable(log_dir, 10, &env_vars);
std::thread::sleep(Duration::from_millis(1000));
run_test_executable(log_dir, 5, &env_vars);
let log_files = count_log_files(log_dir);
assert!(log_files <= 1, "Expected at most 1 log file after age-based cleanup, found {}", log_files);
}
#[test]
fn test_maximum_size_cleanup() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
let env_vars = [
("HF_XET_LOG_DIR_MAX_SIZE", "10kb"),
("HF_XET_LOG_DIR_MIN_DELETION_AGE", "1ms"), ("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1h"), ("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
for _ in 0..20 {
run_test_executable(log_dir, 1000, &env_vars); }
std::thread::sleep(Duration::from_millis(100));
run_test_executable(log_dir, 1, &env_vars);
std::thread::sleep(Duration::from_millis(100));
let total_size = get_directory_size(log_dir);
assert!(total_size <= 10 * 1024, "Directory size {} exceeds 10kb limit", total_size);
}
#[test]
fn test_active_window_protection() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
let env_vars = [
("HF_XET_LOG_DIR_MAX_SIZE", "1kb"),
("HF_XET_LOG_DIR_MIN_DELETION_AGE", "30s"),
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1h"),
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
for _ in 0..3 {
run_test_executable(log_dir, 100, &env_vars);
}
std::thread::sleep(Duration::from_millis(200));
let log_files = count_log_files(log_dir);
assert!(log_files <= 5);
let log_dir_size = get_directory_size(log_dir);
assert!(log_dir_size > 1024);
let cleanup_env_vars = [
("HF_XET_LOG_DIR_MAX_SIZE", "1kb"),
("HF_XET_LOG_DIR_MIN_DELETION_AGE", "2s"),
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1h"),
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
std::thread::sleep(Duration::from_secs(3));
for _ in 0..2 {
run_test_executable(log_dir, 100, &cleanup_env_vars);
}
std::thread::sleep(Duration::from_millis(200));
let mut log_files = count_log_files(log_dir);
for _ in 0..15 {
if log_files == 2 {
break;
}
thread::sleep(Duration::from_millis(200));
log_files = count_log_files(log_dir);
}
assert_eq!(log_files, 2);
}
#[test]
fn test_cleanup_disabled() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
let env_vars = [
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "true"),
("HF_XET_LOG_DIR_MAX_SIZE", "1kb"),
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1s"),
];
for _ in 0..3 {
run_test_executable(log_dir, 20, &env_vars);
std::thread::sleep(Duration::from_millis(50));
}
std::thread::sleep(Duration::from_millis(100));
let log_files = count_log_files(log_dir);
assert_eq!(log_files, 3);
}
#[test]
fn test_maximum_age_cleanup_parallel() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
std::fs::create_dir_all(log_dir).expect("Failed to create log directory");
let env_vars = [
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "500ms"),
("HF_XET_LOG_DIR_MIN_DELETION_AGE", "100ms"),
("HF_XET_LOG_DIR_MAX_SIZE", "1gb"),
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
let mut handles = Vec::new();
for _ in 0..5 {
let handle = run_test_executable_parallel(log_dir, 100, &env_vars);
handles.push(handle);
}
run_test_executable(log_dir, 10, &env_vars);
for handle in handles {
handle.join().expect("Background task failed");
}
std::thread::sleep(Duration::from_millis(1000));
run_test_executable(log_dir, 5, &env_vars);
std::thread::sleep(Duration::from_millis(500)); let log_files = count_log_files(log_dir);
assert!(log_files <= 1, "Expected at most 1 log file after age-based cleanup, found {}", log_files);
}
#[test]
fn test_maximum_size_cleanup_parallel() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
std::fs::create_dir_all(log_dir).expect("Failed to create log directory");
let env_vars = [
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1h"), ("HF_XET_LOG_DIR_MIN_DELETION_AGE", "1ms"), ("HF_XET_LOG_DIR_MAX_SIZE", "10kb"),
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
let mut handles = Vec::new();
for _ in 0..20 {
let handle = run_test_executable_parallel(log_dir, 1000, &env_vars);
handles.push(handle);
}
for handle in handles {
handle.join().expect("Background task failed");
}
std::thread::sleep(Duration::from_millis(100));
run_test_executable(log_dir, 1, &env_vars);
std::thread::sleep(Duration::from_millis(100));
let total_size = get_directory_size(log_dir);
assert!(total_size <= 10 * 1024, "Directory size {} exceeds 10kb limit", total_size);
}
#[test]
fn test_active_window_protection_parallel() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
std::fs::create_dir_all(log_dir).expect("Failed to create log directory");
let env_vars = [
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1h"), ("HF_XET_LOG_DIR_MIN_DELETION_AGE", "1ms"), ("HF_XET_LOG_DIR_MAX_SIZE", "10kb"),
("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
let mut handles = Vec::new();
for _ in 0..5 {
let handle = run_test_executable_parallel(log_dir, 1000, &env_vars);
handles.push(handle);
}
for handle in handles {
handle.join().expect("Background task failed");
}
std::thread::sleep(Duration::from_millis(100));
let total_size = get_directory_size(log_dir);
assert!(
total_size > 10 * 1024,
"Expected directory size to exceed 10kb due to active window protection, found {}",
total_size
);
}
#[test]
fn test_cleanup_stress_test() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let log_dir = temp_dir.path();
let env_vars = [
("HF_XET_LOG_DIR_MAX_RETENTION_AGE", "1s"), ("HF_XET_LOG_DIR_MIN_DELETION_AGE", "1ms"), ("HF_XET_LOG_DIR_MAX_SIZE", "10kb"), ("HF_XET_LOG_DIR_DISABLE_CLEANUP", "false"),
];
let mut handles = Vec::new();
let mut rng = StdRng::seed_from_u64(42);
for i in 0..100 {
let log_size = rng.random_range(1..=250);
let handle = run_test_executable_parallel(log_dir, log_size, &env_vars);
handles.push(handle);
if i % 10 == 0 {
std::thread::sleep(Duration::from_millis(20));
}
}
for handle in handles {
handle.join().expect("Background task failed");
}
std::thread::sleep(Duration::from_millis(100));
let mut handles = Vec::new();
for _ in 0..5 {
let handle = run_test_executable_parallel(log_dir, 5, &env_vars);
handles.push(handle);
}
for handle in handles {
handle.join().expect("Background task failed");
}
std::thread::sleep(Duration::from_millis(2000));
run_test_executable(log_dir, 1, &env_vars);
let total_size = get_directory_size(log_dir);
assert!(
total_size <= 10 * 1024,
"Expected directory size to be under 50kb after stress test cleanup, found {}",
total_size
);
}