jbuild 0.1.8

High-performance Java build tool supporting Maven and Gradle
Documentation
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use walkdir::WalkDir;

/// Test framework type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestFramework {
    JUnit4,
    JUnit5,
    TestNG,
    Unknown,
}

/// Test class information
#[derive(Debug, Clone)]
pub struct TestClass {
    pub class_name: String,
    pub file_path: PathBuf,
    pub framework: TestFramework,
    pub test_methods: Vec<String>,
}

/// Test discovery for Java test classes
pub struct TestDiscovery;

impl TestDiscovery {
    /// Discover test classes in a directory
    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);
        }

        // Discover .class files
        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") {
                // Try to determine if it's a test class
                // For now, we'll use simple heuristics (class name ends with Test)
                if let Some(file_name) = path.file_stem() {
                    let file_name_str = file_name.to_string_lossy();
                    
                    // Simple heuristic: class name contains "Test"
                    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(), // Would need bytecode analysis to get methods
                        });
                    }
                }
            }
        }

        Ok(test_classes)
    }

    /// Convert file path to Java class name
    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)
    }

    /// Detect test framework from class file
    fn detect_framework(_class_file: &Path) -> Result<TestFramework> {
        // For now, we'll use a simple approach
        // In a full implementation, we'd analyze the bytecode to check for:
        // - JUnit 4: @Test annotation from org.junit.Test
        // - JUnit 5: @Test annotation from org.junit.jupiter.api.Test
        // - TestNG: @Test annotation from org.testng.annotations.Test
        
        // Default to JUnit4 for now
        Ok(TestFramework::JUnit4)
    }

    /// Discover test classes from multiple directories
    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)
    }
}