use anyhow::Result;
use std::fs;
use tempfile::TempDir;
use purger_core::{
ProjectCleaner, ProjectScanner,
cleaner::{CleanConfig, CleanStrategy},
scanner::ScanConfig,
};
fn create_test_project(base_path: &std::path::Path, name: &str, with_target: bool) -> Result<()> {
let project_path = base_path.join(name);
fs::create_dir_all(&project_path)?;
let cargo_toml = format!(
r#"
[package]
name = "{name}"
version = "0.1.0"
edition = "2021"
[dependencies]
"#
);
fs::write(project_path.join("Cargo.toml"), cargo_toml)?;
let src_dir = project_path.join("src");
fs::create_dir_all(&src_dir)?;
fs::write(
src_dir.join("main.rs"),
r#"fn main() {
println!("Hello, world!");
}"#,
)?;
if with_target {
let target_dir = project_path.join("target");
fs::create_dir_all(&target_dir)?;
let debug_dir = target_dir.join("debug");
fs::create_dir_all(&debug_dir)?;
fs::write(debug_dir.join(name), "fake executable")?;
fs::write(debug_dir.join("deps.d"), "fake dependency file")?;
let large_content = "x".repeat(1024); fs::write(debug_dir.join("large_file.rlib"), &large_content)?;
}
Ok(())
}
#[test]
fn test_end_to_end_scan_and_clean() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
create_test_project(root, "project1", true)?;
create_test_project(root, "project2", true)?;
create_test_project(root, "project3", false)?;
let scanner = ProjectScanner::default();
let projects = scanner.scan(root)?;
assert_eq!(projects.len(), 3);
let projects_with_target = ProjectScanner::filter_with_target(projects);
assert_eq!(projects_with_target.len(), 2);
let project_names: Vec<&str> = projects_with_target
.iter()
.map(|p| p.name.as_str())
.collect();
assert!(project_names.contains(&"project1"));
assert!(project_names.contains(&"project2"));
let clean_config = CleanConfig {
strategy: CleanStrategy::DirectDelete,
dry_run: true,
..Default::default()
};
let cleaner = ProjectCleaner::new(clean_config);
let result = cleaner.clean_projects(&projects_with_target);
assert_eq!(result.cleaned_projects, 2);
assert!(result.total_size_freed > 0);
assert!(result.failed_projects.is_empty());
Ok(())
}
#[test]
fn test_scan_with_filters() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
create_test_project(root, "small_project", true)?;
create_test_project(root, "large_project", true)?;
let large_target = root.join("large_project").join("target");
let large_content = "x".repeat(10240); fs::write(large_target.join("large_file.rlib"), &large_content)?;
let config = ScanConfig {
keep_size: Some(5000), ..Default::default()
};
let scanner = ProjectScanner::new(config.clone());
let projects = scanner.scan(root)?;
assert!(
!projects.is_empty(),
"Should find at least one project, found: {}",
projects.len()
);
if projects.len() != 2 {
eprintln!(
"Expected 2 projects, found {}: {:?}",
projects.len(),
projects.iter().map(|p| &p.name).collect::<Vec<_>>()
);
}
let filter = purger_core::ProjectFilter::new(config);
let filtered_projects = filter.filter_projects(projects);
assert!(!filtered_projects.is_empty());
let small_project_preserved = filtered_projects.iter().any(|p| p.name == "small_project");
assert!(
small_project_preserved,
"small_project should be preserved by the filter"
);
Ok(())
}
#[test]
fn test_clean_strategies() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
create_test_project(root, "test_project", true)?;
let scanner = ProjectScanner::default();
let projects = scanner.scan(root)?;
let projects_with_target = ProjectScanner::filter_with_target(projects);
let clean_config = CleanConfig {
strategy: CleanStrategy::DirectDelete,
dry_run: false,
..Default::default()
};
let cleaner = ProjectCleaner::new(clean_config);
let result = cleaner.clean_projects(&projects_with_target);
assert_eq!(result.cleaned_projects, 1);
assert!(result.total_size_freed > 0);
let target_path = root.join("test_project").join("target");
assert!(!target_path.exists());
Ok(())
}
#[test]
fn test_parallel_vs_sequential_scanning() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
for i in 0..5 {
create_test_project(root, &format!("project_{i}"), true)?;
}
let parallel_config = ScanConfig {
parallel: true,
..Default::default()
};
let scanner = ProjectScanner::new(parallel_config);
let parallel_projects = scanner.scan(root)?;
let sequential_config = ScanConfig {
parallel: false,
..Default::default()
};
let scanner = ProjectScanner::new(sequential_config);
let sequential_projects = scanner.scan(root)?;
assert_eq!(parallel_projects.len(), sequential_projects.len());
assert_eq!(parallel_projects.len(), 5);
let mut parallel_names: Vec<String> =
parallel_projects.iter().map(|p| p.name.clone()).collect();
let mut sequential_names: Vec<String> =
sequential_projects.iter().map(|p| p.name.clone()).collect();
parallel_names.sort();
sequential_names.sort();
assert_eq!(parallel_names, sequential_names);
Ok(())
}
#[test]
fn test_error_handling() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
create_test_project(root, "test_project", true)?;
let scanner = ProjectScanner::default();
let result = scanner.scan(root.join("nonexistent"));
assert!(result.is_err());
let fake_project = purger_core::RustProject {
path: root.join("nonexistent_project"),
name: "nonexistent".to_string(),
target_size: 0,
last_modified: std::time::SystemTime::now(),
is_workspace: false,
has_target: true,
};
let cleaner = ProjectCleaner::default();
let result = cleaner.clean_project(&fake_project);
assert_eq!(result.unwrap_or(0), 0);
Ok(())
}
#[test]
fn test_workspace_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let root = temp_dir.path();
let workspace_toml = r#"
[workspace]
members = ["member1", "member2"]
[workspace.dependencies]
serde = "1.0"
"#;
fs::write(root.join("Cargo.toml"), workspace_toml)?;
let member1_path = root.join("member1");
fs::create_dir_all(&member1_path)?;
let member1_toml = r#"
[package]
name = "member1"
version = "0.1.0"
edition = "2021"
[dependencies]
serde.workspace = true
"#;
fs::write(member1_path.join("Cargo.toml"), member1_toml)?;
let scanner = ProjectScanner::default();
let projects = scanner.scan(root)?;
assert!(!projects.is_empty());
let member1_project = projects.iter().find(|p| p.name == "member1");
assert!(member1_project.is_some());
Ok(())
}