use std::path::PathBuf;
use std::process::Command;
use anyhow::{Context, Result};
use crate::compiler::ClasspathBuilder;
use crate::testing::{TestClass, TestFramework};
#[derive(Debug, Clone)]
pub struct TestResult {
pub class_name: String,
pub success: bool,
pub output: String,
pub error_output: String,
pub tests_run: usize,
pub tests_passed: usize,
pub tests_failed: usize,
}
#[allow(dead_code)]
pub struct TestRunner {
classpath: ClasspathBuilder,
test_classes_dir: PathBuf,
}
impl TestRunner {
pub fn new(test_classes_dir: PathBuf) -> Self {
Self {
classpath: ClasspathBuilder::new(),
test_classes_dir,
}
}
pub fn add_to_classpath(mut self, entry: PathBuf) -> Self {
self.classpath = self.classpath.add_entry(entry);
self
}
pub fn add_jar(mut self, jar: PathBuf) -> Self {
self.classpath = self.classpath.add_jar(jar);
self
}
pub fn run_test(&self, test_class: &TestClass) -> Result<TestResult> {
tracing::info!("Running test class: {}", test_class.class_name);
match test_class.framework {
TestFramework::JUnit4 => self.run_junit4_test(test_class),
TestFramework::JUnit5 => self.run_junit5_test(test_class),
TestFramework::TestNG => self.run_testng_test(test_class),
TestFramework::Unknown => {
self.run_junit4_test(test_class)
}
}
}
pub fn run_tests(&self, test_classes: &[TestClass]) -> Result<Vec<TestResult>> {
let mut results = Vec::new();
for test_class in test_classes {
match self.run_test(test_class) {
Ok(result) => results.push(result),
Err(e) => {
tracing::error!("Failed to run test {}: {}", test_class.class_name, e);
results.push(TestResult {
class_name: test_class.class_name.clone(),
success: false,
output: String::new(),
error_output: format!("Test execution failed: {e}"),
tests_run: 0,
tests_passed: 0,
tests_failed: 1,
});
}
}
}
Ok(results)
}
fn run_junit4_test(&self, test_class: &TestClass) -> Result<TestResult> {
let junit_runner = self.find_junit_runner()?;
let classpath = self.classpath.build();
let output = Command::new("java")
.arg("-cp")
.arg(&classpath)
.arg(&junit_runner)
.arg(&test_class.class_name)
.output()
.context("Failed to execute JUnit test")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let (tests_run, tests_passed, tests_failed) = self.parse_junit_output(&stdout);
Ok(TestResult {
class_name: test_class.class_name.clone(),
success: output.status.success() && tests_failed == 0,
output: stdout,
error_output: stderr,
tests_run,
tests_passed,
tests_failed,
})
}
fn run_junit5_test(&self, test_class: &TestClass) -> Result<TestResult> {
let classpath = self.classpath.build();
let output = Command::new("java")
.arg("-cp")
.arg(&classpath)
.arg("org.junit.platform.console.ConsoleLauncher")
.arg("--select-class")
.arg(&test_class.class_name)
.output()
.context("Failed to execute JUnit 5 test")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let (tests_run, tests_passed, tests_failed) = self.parse_junit5_output(&stdout);
Ok(TestResult {
class_name: test_class.class_name.clone(),
success: output.status.success() && tests_failed == 0,
output: stdout,
error_output: stderr,
tests_run,
tests_passed,
tests_failed,
})
}
fn run_testng_test(&self, test_class: &TestClass) -> Result<TestResult> {
let classpath = self.classpath.build();
let output = Command::new("java")
.arg("-cp")
.arg(&classpath)
.arg("org.testng.TestNG")
.arg("-testclass")
.arg(&test_class.class_name)
.output()
.context("Failed to execute TestNG test")?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let (tests_run, tests_passed, tests_failed) = self.parse_testng_output(&stdout);
Ok(TestResult {
class_name: test_class.class_name.clone(),
success: output.status.success() && tests_failed == 0,
output: stdout,
error_output: stderr,
tests_run,
tests_passed,
tests_failed,
})
}
fn find_junit_runner(&self) -> Result<String> {
Ok("org.junit.runner.JUnitCore".to_string())
}
fn parse_junit_output(&self, output: &str) -> (usize, usize, usize) {
let tests_run = output.matches("Tests run:").count();
let tests_passed = output.matches("OK").count();
let tests_failed = output.matches("FAILURES!!!").count();
(tests_run, tests_passed, tests_failed)
}
fn parse_junit5_output(&self, output: &str) -> (usize, usize, usize) {
let tests_run = output.matches("tests found").count();
let tests_passed = output.matches("tests successful").count();
let tests_failed = output.matches("tests failed").count();
(tests_run, tests_passed, tests_failed)
}
fn parse_testng_output(&self, output: &str) -> (usize, usize, usize) {
let tests_run = output.matches("Total tests run:").count();
let tests_passed = output.matches("Passed:").count();
let tests_failed = output.matches("Failed:").count();
(tests_run, tests_passed, tests_failed)
}
}