use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use thiserror::Error;
use std::time::Duration;
use std::time::SystemTime;
#[derive(Error, Debug)]
pub enum TestRunnerError {
#[error("Test execution failed: {0}")]
ExecutionFailed(String),
#[error("Test timeout: {0}")]
Timeout(String),
#[error("Invalid test configuration: {0}")]
ConfigError(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TestType {
Startup,
Execution,
Memory,
Concurrent,
All,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TestStatus {
Pending,
Running,
Completed,
Failed,
Skipped,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestExecutionResult {
pub test_name: String,
pub test_type: TestType,
pub status: TestStatus,
pub result: Option<BenchmarkResult>,
pub error: Option<String>,
pub execution_time: Duration,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestPlanConfig {
pub test_types: Vec<TestType>,
pub parallel_execution: bool,
pub max_concurrent_tests: usize,
pub timeout_per_test: Option<Duration>,
pub retry_failed_tests: bool,
pub max_retries: usize,
pub save_results: bool,
pub results_directory: String,
}
impl Default for TestPlanConfig {
fn default() -> Self {
Self {
test_types: vec![
TestType::Startup,
TestType::Execution,
TestType::Memory,
TestType::Concurrent,
],
parallel_execution: true,
max_concurrent_tests: 4,
timeout_per_test: Some(Duration::from_secs(300)), retry_failed_tests: true,
max_retries: 3,
save_results: true,
results_directory: "test_results".to_string(),
}
}
}
pub struct AutomatedTestRunner {
config: TestPlanConfig,
framework: BenchmarkFramework,
regression_detector: Arc<Mutex<PerformanceRegressionDetector>>,
execution_results: Arc<Mutex<Vec<TestExecutionResult>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestRunnerStats {
pub total_tests: usize,
pub completed_tests: usize,
pub failed_tests: usize,
pub skipped_tests: usize,
pub total_execution_time: Duration,
pub average_test_time: Duration,
pub parallel_efficiency: f64, }
impl AutomatedTestRunner {
pub fn new(
config: TestPlanConfig,
framework: BenchmarkFramework,
regression_detector: Arc<Mutex<PerformanceRegressionDetector>>,
) -> Self {
Self {
config,
framework,
regression_detector,
execution_results: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn new_default(regression_detector: Arc<Mutex<PerformanceRegressionDetector>>) -> Self {
let config: _ = TestPlanConfig::default();
let framework: _ = BenchmarkFramework::new_default();
Self::new(config, framework, regression_detector)
}
pub async fn run_full_test_suite(&self) -> Result<TestSuiteResults, TestRunnerError> {
println!("🚀 Starting automated performance test suite...");
let start_time: _ = SystemTime::now();
let test_tasks: _ = self.create_test_tasks();
let results: _ = if self.config.parallel_execution {
self.run_tests_parallel(test_tasks).await?
} else {
self.run_tests_sequential(test_tasks).await?
};
let total_time: _ = start_time.elapsed().unwrap_or_default();
let stats: _ = self.generate_stats(&results, total_time);
println!("✅ Test suite completed in {:.2}s", total_time.as_secs_f64());
println!("📊 Tests: {} completed, {} failed, {} skipped",
stats.completed_tests,
stats.failed_tests,
stats.skipped_tests
);
Ok(TestSuiteResults {
results,
stats,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
})
}
fn create_test_tasks(&self) -> Vec<ScheduledTest> {
let mut tasks = Vec::new();
for test_type in &self.config.test_types {
match test_type {
TestType::Startup => {
tasks.extend(self.create_startup_tests());
}
TestType::Execution => {
tasks.extend(self.create_execution_tests());
}
TestType::Memory => {
tasks.extend(self.create_memory_tests());
}
TestType::Concurrent => {
tasks.extend(self.create_concurrent_tests());
}
TestType::All => {
tasks.extend(self.create_startup_tests());
tasks.extend(self.create_execution_tests());
tasks.extend(self.create_memory_tests());
tasks.extend(self.create_concurrent_tests());
}
}
}
tasks
}
fn create_startup_tests(&self) -> Vec<ScheduledTest> {
vec![
ScheduledTest {
name: "cold_start".to_string(),
test_type: TestType::Startup,
config: BenchmarkConfig {
iterations: 100,
warmup_iterations: 5,
timeout: Some(Duration::from_secs(30)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "warm_start".to_string(),
test_type: TestType::Startup,
config: BenchmarkConfig {
iterations: 1000,
warmup_iterations: 10,
timeout: Some(Duration::from_secs(30)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "v8_init".to_string(),
test_type: TestType::Startup,
config: BenchmarkConfig {
iterations: 500,
warmup_iterations: 10,
timeout: Some(Duration::from_secs(60)),
save_raw_data: true,
compare_with_baseline: true,
},
},
]
}
fn create_execution_tests(&self) -> Vec<ScheduledTest> {
vec![
ScheduledTest {
name: "expression_eval".to_string(),
test_type: TestType::Execution,
config: BenchmarkConfig {
iterations: 10000,
warmup_iterations: 100,
timeout: Some(Duration::from_secs(120)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "function_call".to_string(),
test_type: TestType::Execution,
config: BenchmarkConfig {
iterations: 5000,
warmup_iterations: 50,
timeout: Some(Duration::from_secs(120)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "object_creation".to_string(),
test_type: TestType::Execution,
config: BenchmarkConfig {
iterations: 3000,
warmup_iterations: 30,
timeout: Some(Duration::from_secs(120)),
save_raw_data: true,
compare_with_baseline: true,
},
},
]
}
fn create_memory_tests(&self) -> Vec<ScheduledTest> {
vec![
ScheduledTest {
name: "memory_allocation".to_string(),
test_type: TestType::Memory,
config: BenchmarkConfig {
iterations: 1000,
warmup_iterations: 10,
timeout: Some(Duration::from_secs(180)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "memory_pool".to_string(),
test_type: TestType::Memory,
config: BenchmarkConfig {
iterations: 2000,
warmup_iterations: 20,
timeout: Some(Duration::from_secs(180)),
save_raw_data: true,
compare_with_baseline: true,
},
},
]
}
fn create_concurrent_tests(&self) -> Vec<ScheduledTest> {
vec![
ScheduledTest {
name: "concurrent_execution".to_string(),
test_type: TestType::Concurrent,
config: BenchmarkConfig {
iterations: 100,
warmup_iterations: 5,
timeout: Some(Duration::from_secs(240)),
save_raw_data: true,
compare_with_baseline: true,
},
},
ScheduledTest {
name: "async_operations".to_string(),
test_type: TestType::Concurrent,
config: BenchmarkConfig {
iterations: 500,
warmup_iterations: 10,
timeout: Some(Duration::from_secs(240)),
save_raw_data: true,
compare_with_baseline: true,
},
},
]
}
async fn run_tests_parallel(
&self,
tasks: Vec<ScheduledTest>,
) -> Result<Vec<TestExecutionResult>, TestRunnerError> {
let semaphore: _ = Arc::new(Mutex::new(tokio::sync::Semaphore::new(self.config.max_concurrent_tests)));
let mut handles: Vec<JoinHandle<Result<TestExecutionResult, TestRunnerError>>> = Vec::new();
for task in tasks {
let semaphore: _ = semaphore.clone();
let handle: _ = tokio::spawn(async move {
let _permit: _ = semaphore.acquire().await.map_err(|e| {
TestRunnerError::ExecutionFailed(e.to_string())
})?;
Err(TestRunnerError::ExecutionFailed(
"Async test execution needs refactoring".to_string()))
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
match handle.await {
Ok(result) => results.push(result?),
Err(e) => {
return Err(TestRunnerError::ExecutionFailed(e.to_string()));
}
}
}
Ok(results)
}
async fn run_tests_sequential(
&self,
tasks: Vec<ScheduledTest>,
) -> Result<Vec<TestExecutionResult>, TestRunnerError> {
let mut results = Vec::new();
for task in tasks {
let result: _ = self.run_single_test(task).await?;
results.push(result);
}
Ok(results)
}
async fn run_single_test(
&self,
task: ScheduledTest,
) -> Result<TestExecutionResult, TestRunnerError> {
println!("🔄 Running test: {} ({:?})", task.name, task.test_type);
let start_time: _ = SystemTime::now();
let timestamp: _ = start_time
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut status = TestStatus::Running;
let mut result = None;
let mut error = None;
let timeout_duration: _ = self.config.timeout_per_test.unwrap_or(Duration::from_secs(300));
let test_future: _ = self.execute_test(&task);
match tokio::time::timeout(timeout_duration, test_future).await {
Ok(Ok(res)) => {
result = Some(res);
status = TestStatus::Completed;
println!("✅ Test completed: {}", task.name);
}
Ok(Err(e)) => {
error = Some(e.to_string());
status = TestStatus::Failed;
println!("❌ Test failed: {} - {}", task.name, e);
}
Err(_) => {
error = Some("Test timeout".to_string());
status = TestStatus::Failed;
println!("⏱️ Test timeout: {}", task.name);
}
}
let execution_time: _ = start_time.elapsed().unwrap_or_default();
Ok(TestExecutionResult {
test_name: task.name,
test_type: task.test_type,
status,
result,
error,
execution_time,
timestamp,
})
}
async fn execute_test(
&self,
task: &ScheduledTest,
) -> Result<BenchmarkResult, TestRunnerError> {
match task.test_type {
TestType::Startup => {
Ok(self.framework.run_benchmark(
&task.name,
MetricType::StartupTime,
|| {
std::thread::sleep(Duration::from_millis(10));
},
))
}
TestType::Execution => {
Ok(self.framework.run_benchmark(
&task.name,
MetricType::ExecutionTime,
|| {
let mut sum = 0;
for i in 0..1000 {
sum += i;
}
sum
},
))
}
TestType::Memory => {
Ok(self.framework.run_benchmark_with_memory(
&task.name,
MetricType::MemoryUsage,
|| {
let vec: _ = vec![0u64; 1000];
vec
},
))
}
TestType::Concurrent => {
Ok(self.framework.run_benchmark(
&task.name,
MetricType::OperationsPerSecond,
|| {
std::thread::sleep(Duration::from_millis(1));
},
))
}
TestType::All => {
Err(TestRunnerError::ConfigError(
"TestType::All should be expanded before execution".to_string(),
))
}
}
}
fn generate_stats(
&self,
results: &[TestExecutionResult],
total_time: Duration,
) -> TestRunnerStats {
let completed_tests: _ = results.iter().filter(|r| r.status == TestStatus::Completed).count();
let failed_tests: _ = results.iter().filter(|r| r.status == TestStatus::Failed).count();
let skipped_tests: _ = results.iter().filter(|r| r.status == TestStatus::Skipped).count();
let total_execution_time: Duration = results
.iter()
.map(|r| r.execution_time)
.sum();
let average_test_time: _ = if !results.is_empty() {
Duration::from_nanos(total_execution_time.as_nanos() as u64 / results.len() as u64)
} else {
Duration::default()
};
let parallel_efficiency: _ = if total_execution_time.as_secs_f64() > 0.0 {
(total_execution_time.as_secs_f64() / total_time.as_secs_f64()) * 100.0
} else {
0.0
};
TestRunnerStats {
total_tests: results.len(),
completed_tests,
failed_tests,
skipped_tests,
total_execution_time,
average_test_time,
parallel_efficiency,
}
}
pub fn get_results(&self) -> Vec<TestExecutionResult> {
self.execution_results.lock().unwrap().clone()
}
pub async fn run_test_type(&self, test_type: TestType) -> Result<TestSuiteResults, TestRunnerError> {
let config: _ = TestPlanConfig {
test_types: vec![test_type],
parallel_execution: self.config.parallel_execution,
max_concurrent_tests: self.config.max_concurrent_tests,
timeout_per_test: self.config.timeout_per_test,
retry_failed_tests: self.config.retry_failed_tests,
max_retries: self.config.max_retries,
save_results: self.config.save_results,
results_directory: self.config.results_directory.clone(),
};
let runner: _ = AutomatedTestRunner::new(
config,
self.framework.clone(),
self.regression_detector.clone(),
);
runner.run_full_test_suite().await
}
}
#[derive(Debug, Clone)]
pub struct ScheduledTest {
pub name: String,
pub test_type: TestType,
pub config: BenchmarkConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestSuiteResults {
pub results: Vec<TestExecutionResult>,
pub stats: TestRunnerStats,
pub timestamp: u64,
}
impl TestSuiteResults {
pub fn generate_summary(&self) -> String {
let mut report = String::new();
report.push_str("\n=== Automated Test Suite Results ===\n");
report.push_str(&format!("Timestamp: {}\n", self.timestamp));
report.push_str(&format!("Total Tests: {}\n", self.stats.total_tests));
report.push_str(&format!("Completed: {}\n", self.stats.completed_tests));
report.push_str(&format!("Failed: {}\n", self.stats.failed_tests));
report.push_str(&format!("Skipped: {}\n", self.stats.skipped_tests));
report.push_str(&format!("Total Execution Time: {:.2}s\n", self.stats.total_execution_time.as_secs_f64()));
report.push_str(&format!("Average Test Time: {:.2}ms\n", self.stats.average_test_time.as_secs_f64() * 1000.0));
report.push_str(&format!("Parallel Efficiency: {:.1}%\n\n", self.stats.parallel_efficiency));
for result in &self.results {
report.push_str(&format!("Test: {} ({:?})\n", result.test_name, result.test_type));
report.push_str(&format!(" Status: {:?}\n", result.status));
report.push_str(&format!(" Execution Time: {:.2}ms\n", result.execution_time.as_secs_f64() * 1000.0));
if let Some(error) = &result.error {
report.push_str(&format!(" Error: {}\n", error));
}
if let Some(benchmark_result) = &result.result {
report.push_str(&format!(" Avg Duration: {:.2}μs\n", benchmark_result.avg_duration.as_secs_f64() * 1_000_000.0));
report.push_str(&format!(" Ops/sec: {:.0}\n", benchmark_result.operations_per_second));
}
report.push('\n');
}
report
}
}