use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use walkdir::WalkDir;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestFramework {
JUnit4,
JUnit5,
TestNG,
Unknown,
}
#[derive(Debug, Clone)]
pub struct TestClass {
pub class_name: String,
pub file_path: PathBuf,
pub framework: TestFramework,
pub test_methods: Vec<String>,
}
pub struct TestDiscovery;
impl TestDiscovery {
pub fn discover_tests(test_classes_dir: &Path) -> Result<Vec<TestClass>> {
let mut test_classes = Vec::new();
if !test_classes_dir.exists() {
return Ok(test_classes);
}
for entry in WalkDir::new(test_classes_dir)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "class") {
if let Some(file_name) = path.file_stem() {
let file_name_str = file_name.to_string_lossy();
if file_name_str.contains("Test") || file_name_str.ends_with("Tests") {
let class_name = Self::path_to_class_name(path, test_classes_dir)?;
let framework = Self::detect_framework(path)?;
test_classes.push(TestClass {
class_name,
file_path: path.to_path_buf(),
framework,
test_methods: Vec::new(), });
}
}
}
}
Ok(test_classes)
}
fn path_to_class_name(class_file: &Path, base_dir: &Path) -> Result<String> {
let relative = class_file.strip_prefix(base_dir)
.context("Failed to get relative path")?;
let class_name = relative
.to_string_lossy()
.replace(['/', '\\'], ".")
.strip_suffix(".class")
.ok_or_else(|| anyhow::anyhow!("Not a .class file"))?
.to_string();
Ok(class_name)
}
fn detect_framework(_class_file: &Path) -> Result<TestFramework> {
Ok(TestFramework::JUnit4)
}
pub fn discover_from_directories(dirs: &[PathBuf]) -> Result<Vec<TestClass>> {
let mut all_tests = Vec::new();
for dir in dirs {
let tests = Self::discover_tests(dir)?;
all_tests.extend(tests);
}
Ok(all_tests)
}
}