use crate::error::{BenchError, Result};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[cfg(feature = "raster")]
pub mod raster;
#[cfg(feature = "vector")]
pub mod vector;
pub mod io;
#[cfg(feature = "cloud")]
pub mod cloud;
#[cfg(feature = "ml")]
pub mod ml;
pub trait BenchmarkScenario {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn setup(&mut self) -> Result<()>;
fn execute(&mut self) -> Result<()>;
fn teardown(&mut self) -> Result<()>;
fn validate(&self) -> Result<()> {
Ok(())
}
fn expected_duration_range(&self) -> Option<(Duration, Duration)> {
None
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ScenarioResult {
pub name: String,
#[serde(with = "crate::profiler::duration_serde")]
pub setup_duration: Duration,
#[serde(with = "crate::profiler::duration_serde")]
pub execution_duration: Duration,
#[serde(with = "crate::profiler::duration_serde")]
pub teardown_duration: Duration,
#[serde(with = "crate::profiler::duration_serde")]
pub total_duration: Duration,
pub success: bool,
pub error_message: Option<String>,
pub metrics: HashMap<String, f64>,
}
pub struct ScenarioRunner {
scenarios: Vec<Box<dyn BenchmarkScenario>>,
results: Vec<ScenarioResult>,
}
impl ScenarioRunner {
pub fn new() -> Self {
Self {
scenarios: Vec::new(),
results: Vec::new(),
}
}
pub fn add_scenario<S: BenchmarkScenario + 'static>(&mut self, scenario: S) {
self.scenarios.push(Box::new(scenario));
}
pub fn run_all(&mut self) -> Result<()> {
self.results.clear();
let mut results = Vec::new();
for scenario in &mut self.scenarios {
let result = Self::run_scenario_static(scenario.as_mut());
results.push(result);
}
self.results = results;
Ok(())
}
fn run_scenario_static(scenario: &mut dyn BenchmarkScenario) -> ScenarioResult {
let name = scenario.name().to_string();
let metrics = HashMap::new();
let setup_start = Instant::now();
let setup_result = scenario.setup();
let setup_duration = setup_start.elapsed();
if let Err(e) = setup_result {
return ScenarioResult {
name,
setup_duration,
execution_duration: Duration::ZERO,
teardown_duration: Duration::ZERO,
total_duration: setup_duration,
success: false,
error_message: Some(format!("Setup failed: {e}")),
metrics,
};
}
let execute_start = Instant::now();
let execute_result = scenario.execute();
let execution_duration = execute_start.elapsed();
let success = execute_result.is_ok();
let error_message = execute_result.err().map(|e| e.to_string());
if success && let Err(e) = scenario.validate() {
return ScenarioResult {
name,
setup_duration,
execution_duration,
teardown_duration: Duration::ZERO,
total_duration: setup_duration + execution_duration,
success: false,
error_message: Some(format!("Validation failed: {e}")),
metrics,
};
}
let teardown_start = Instant::now();
let _ = scenario.teardown(); let teardown_duration = teardown_start.elapsed();
let total_duration = setup_duration + execution_duration + teardown_duration;
ScenarioResult {
name,
setup_duration,
execution_duration,
teardown_duration,
total_duration,
success,
error_message,
metrics,
}
}
pub fn results(&self) -> &[ScenarioResult] {
&self.results
}
pub fn successful_results(&self) -> Vec<&ScenarioResult> {
self.results.iter().filter(|r| r.success).collect()
}
pub fn failed_results(&self) -> Vec<&ScenarioResult> {
self.results.iter().filter(|r| !r.success).collect()
}
}
impl Default for ScenarioRunner {
fn default() -> Self {
Self::new()
}
}
pub struct ScenarioBuilder {
name: String,
description: String,
setup_fn: Option<Box<dyn FnMut() -> Result<()>>>,
execute_fn: Option<Box<dyn FnMut() -> Result<()>>>,
teardown_fn: Option<Box<dyn FnMut() -> Result<()>>>,
validate_fn: Option<Box<dyn Fn() -> Result<()>>>,
}
impl ScenarioBuilder {
pub fn new<S: Into<String>>(name: S) -> Self {
let name = name.into();
Self {
name: name.clone(),
description: name,
setup_fn: None,
execute_fn: None,
teardown_fn: None,
validate_fn: None,
}
}
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = description.into();
self
}
pub fn setup<F>(mut self, f: F) -> Self
where
F: FnMut() -> Result<()> + 'static,
{
self.setup_fn = Some(Box::new(f));
self
}
pub fn execute<F>(mut self, f: F) -> Self
where
F: FnMut() -> Result<()> + 'static,
{
self.execute_fn = Some(Box::new(f));
self
}
pub fn teardown<F>(mut self, f: F) -> Self
where
F: FnMut() -> Result<()> + 'static,
{
self.teardown_fn = Some(Box::new(f));
self
}
pub fn validate<F>(mut self, f: F) -> Self
where
F: Fn() -> Result<()> + 'static,
{
self.validate_fn = Some(Box::new(f));
self
}
pub fn build(self) -> CustomScenario {
CustomScenario {
name: self.name,
description: self.description,
setup_fn: self.setup_fn,
execute_fn: self.execute_fn,
teardown_fn: self.teardown_fn,
validate_fn: self.validate_fn,
}
}
}
pub struct CustomScenario {
name: String,
description: String,
setup_fn: Option<Box<dyn FnMut() -> Result<()>>>,
execute_fn: Option<Box<dyn FnMut() -> Result<()>>>,
teardown_fn: Option<Box<dyn FnMut() -> Result<()>>>,
validate_fn: Option<Box<dyn Fn() -> Result<()>>>,
}
impl BenchmarkScenario for CustomScenario {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn setup(&mut self) -> Result<()> {
if let Some(ref mut f) = self.setup_fn {
f()
} else {
Ok(())
}
}
fn execute(&mut self) -> Result<()> {
if let Some(ref mut f) = self.execute_fn {
f()
} else {
Err(BenchError::benchmark_execution(
"No execute function defined",
))
}
}
fn teardown(&mut self) -> Result<()> {
if let Some(ref mut f) = self.teardown_fn {
f()
} else {
Ok(())
}
}
fn validate(&self) -> Result<()> {
if let Some(ref f) = self.validate_fn {
f()
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scenario_builder() {
let scenario = ScenarioBuilder::new("test")
.description("Test scenario")
.execute(|| Ok(()))
.build();
assert_eq!(scenario.name(), "test");
assert_eq!(scenario.description(), "Test scenario");
}
#[test]
fn test_scenario_runner() {
let mut runner = ScenarioRunner::new();
let scenario = ScenarioBuilder::new("test_success")
.execute(|| Ok(()))
.build();
runner.add_scenario(scenario);
let result = runner.run_all();
assert!(result.is_ok());
let results = runner.results();
assert_eq!(results.len(), 1);
assert!(results[0].success);
}
#[test]
fn test_failed_scenario() {
let mut runner = ScenarioRunner::new();
let scenario = ScenarioBuilder::new("test_failure")
.execute(|| Err(BenchError::benchmark_execution("Intentional failure")))
.build();
runner.add_scenario(scenario);
let result = runner.run_all();
assert!(result.is_ok());
let results = runner.results();
assert_eq!(results.len(), 1);
assert!(!results[0].success);
assert!(results[0].error_message.is_some());
}
}