malvin 0.2.4

Non-interactive research and coding agent
use super::log_gc_prune::{over_count_cap, prune_run_dirs};
use super::*;
use crate::log_gc_config::LogsGcConfig;

const RUN_OLDEST: &str = "20260101_000000_aaaaaaa1";
const RUN_MID: &str = "20260102_000000_bbbbbbb2";
const RUN_NEWEST: &str = "20260103_000000_ccccccc3";

#[test]
fn over_count_cap_at_limit_does_not_prune() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let logs = crate::workspace_paths::malvin_logs_root(tmp.path());
    for name in [RUN_OLDEST, RUN_MID, RUN_NEWEST] {
        std::fs::create_dir_all(logs.join(name)).expect("mkdir");
    }
    let mut runs = list_run_dirs(&logs);
    runs.sort_by(|a, b| b.file_name().cmp(&a.file_name()));
    let config = LogsGcConfig {
        max_count: 3,
        max_age_days: 0,
        max_bytes: None,
    };
    assert!(!over_count_cap(runs.len(), config.max_count));
    let (removed, _) = prune_run_dirs(&mut runs, &config, None);
    assert_eq!(removed, 0);
    assert_eq!(runs.len(), 3);
}

#[test]
fn prune_removes_oldest_when_over_count_cap() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let logs = crate::workspace_paths::malvin_logs_root(tmp.path());
    for name in [RUN_OLDEST, RUN_MID, RUN_NEWEST] {
        std::fs::create_dir_all(logs.join(name)).expect("mkdir");
    }
    let mut runs = list_run_dirs(&logs);
    runs.sort_by(|a, b| b.file_name().cmp(&a.file_name()));
    let config = LogsGcConfig {
        max_count: 2,
        max_age_days: 0,
        max_bytes: None,
    };
    let (removed, _) = prune_run_dirs(&mut runs, &config, None);
    assert_eq!(removed, 1);
    assert!(!logs.join(RUN_OLDEST).exists());
}

#[test]
fn max_count_zero_means_unlimited_count() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let logs = crate::workspace_paths::malvin_logs_root(tmp.path());
    for name in [RUN_OLDEST, RUN_MID, RUN_NEWEST] {
        std::fs::create_dir_all(logs.join(name)).expect("mkdir");
    }
    let mut runs = list_run_dirs(&logs);
    runs.sort_by(|a, b| b.file_name().cmp(&a.file_name()));
    let config = LogsGcConfig {
        max_count: 0,
        max_age_days: 0,
        max_bytes: None,
    };
    assert!(!over_count_cap(runs.len(), config.max_count));
    let (removed, _) = prune_run_dirs(&mut runs, &config, None);
    assert_eq!(removed, 0);
}

#[test]
fn size_total_matches_direct_dir_size_after_deletes() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let logs = crate::workspace_paths::malvin_logs_root(tmp.path());
    let run = logs.join(RUN_OLDEST);
    std::fs::create_dir_all(&run).expect("mkdir old");
    std::fs::write(run.join("payload"), vec![0u8; 1000]).expect("write");
    let mut runs = list_run_dirs(&logs);
    runs.sort_by(|a, b| b.file_name().cmp(&a.file_name()));
    let config = LogsGcConfig {
        max_count: 0,
        max_age_days: 0,
        max_bytes: Some(500),
    };
    let (removed, _) = prune_run_dirs(&mut runs, &config, None);
    assert_eq!(removed, 1);
    assert!(runs.is_empty());
}

#[test]
fn prune_result_type_is_populated() {
    let result = PruneResult {
        removed: 1,
        freed: 42,
    };
    assert_eq!(result.removed, 1);
    assert_eq!(result.freed, 42);
}