use crate::atp::benchmark::{
AtpProfile, BaselineAdapter, BenchmarkConfig, BenchmarkError, BenchmarkReport, BenchmarkResult,
};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use tokio::fs;
#[derive(Debug)]
pub struct BenchmarkSuite {
pub name: String,
baseline_adapters: Vec<Box<dyn BaselineAdapter>>,
atp_profiles: Vec<AtpProfile>,
work_dir: Option<TempDir>,
}
impl BenchmarkSuite {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
baseline_adapters: Vec::new(),
atp_profiles: Vec::new(),
work_dir: None,
}
}
pub fn add_baseline(&mut self, adapter: Box<dyn BaselineAdapter>) {
self.baseline_adapters.push(adapter);
}
pub fn add_atp_profile(&mut self, profile: AtpProfile) {
self.atp_profiles.push(profile);
}
pub async fn run_benchmark(
&mut self,
config: &BenchmarkConfig,
) -> Result<BenchmarkReport, BenchmarkError> {
let work_dir = TempDir::new().map_err(|e| BenchmarkError::Io(e))?;
let source_path = work_dir.path().join("test_source");
let dest_base = work_dir.path().join("dest");
fs::create_dir_all(&dest_base).await?;
let mut baseline_results = BTreeMap::new();
for adapter in &self.baseline_adapters {
match self
.run_baseline_benchmark(adapter.as_ref(), config, &source_path, &dest_base)
.await
{
Ok(result) => {
baseline_results.insert(adapter.tool_name().to_string(), result);
}
Err(e) => {
eprintln!("Baseline {} failed: {}", adapter.tool_name(), e);
}
}
}
let mut atp_results = BTreeMap::new();
for profile in &self.atp_profiles {
match self
.run_atp_benchmark(profile, config, &source_path, &dest_base)
.await
{
Ok(result) => {
atp_results.insert(format!("atp-{}", profile.kind.label()), result);
}
Err(e) => {
eprintln!("ATP profile {} failed: {}", profile.kind.label(), e);
}
}
}
self.work_dir = Some(work_dir);
let report = BenchmarkReport::new(
baseline_results,
atp_results,
config.data_size,
config.iterations,
);
Ok(report)
}
async fn run_baseline_benchmark(
&self,
adapter: &dyn BaselineAdapter,
config: &BenchmarkConfig,
source_path: &Path,
dest_base: &Path,
) -> Result<BenchmarkResult, BenchmarkError> {
let availability = adapter.check_availability().await;
if !availability.is_usable() {
return Err(BenchmarkError::ToolUnavailable {
tool: adapter.tool_name().to_string(),
reason: format!("Tool availability: {:?}", availability),
});
}
let dest_path = dest_base.join(format!("{}_dest", adapter.tool_name()));
adapter.run_benchmark(config, source_path, &dest_path).await
}
async fn run_atp_benchmark(
&self,
profile: &AtpProfile,
config: &BenchmarkConfig,
source_path: &Path,
dest_base: &Path,
) -> Result<BenchmarkResult, BenchmarkError> {
let dest_path = dest_base.join(format!("atp_{}_dest", profile.kind.label()));
profile.run_benchmark(config, source_path, &dest_path).await
}
#[must_use]
pub fn smoke_test_suite() -> Self {
let mut suite = Self::new("smoke-test");
suite.add_baseline(Box::new(crate::atp::benchmark::ScpAdapter::new()));
suite.add_atp_profile(AtpProfile::clean_lan());
suite.add_atp_profile(AtpProfile::stream());
suite
}
#[must_use]
pub fn comprehensive_suite() -> Self {
let mut suite = Self::new("comprehensive");
suite.add_baseline(Box::new(crate::atp::benchmark::ScpAdapter::new()));
suite.add_baseline(Box::new(crate::atp::benchmark::RsyncAdapter::new()));
suite.add_baseline(Box::new(crate::atp::benchmark::RcloneAdapter::new()));
suite.add_baseline(Box::new(crate::atp::benchmark::CurlAdapter::new()));
suite.add_atp_profile(AtpProfile::clean_lan());
suite.add_atp_profile(AtpProfile::lossy_wifi());
suite.add_atp_profile(AtpProfile::wan());
suite.add_atp_profile(AtpProfile::stream());
suite
}
#[must_use]
pub fn work_dir_path(&self) -> Option<&Path> {
self.work_dir.as_ref().map(|d| d.path())
}
pub fn preserve_work_dir(&mut self) -> Option<PathBuf> {
self.work_dir.take().map(|dir| {
let path = dir.path().to_owned();
std::mem::forget(dir);
path
})
}
}
#[derive(Debug, Default)]
pub struct BenchmarkSuiteBuilder {
name: String,
include_scp: bool,
include_rsync: bool,
include_rclone: bool,
include_curl: bool,
atp_profiles: Vec<AtpProfile>,
}
impl BenchmarkSuiteBuilder {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
include_scp: false,
include_rsync: false,
include_rclone: false,
include_curl: false,
atp_profiles: Vec::new(),
}
}
#[must_use]
pub fn with_scp(mut self) -> Self {
self.include_scp = true;
self
}
#[must_use]
pub fn with_rsync(mut self) -> Self {
self.include_rsync = true;
self
}
#[must_use]
pub fn with_rclone(mut self) -> Self {
self.include_rclone = true;
self
}
#[must_use]
pub fn with_curl(mut self) -> Self {
self.include_curl = true;
self
}
#[must_use]
pub fn with_atp_profile(mut self, profile: AtpProfile) -> Self {
self.atp_profiles.push(profile);
self
}
#[must_use]
pub fn with_all_atp_profiles(mut self) -> Self {
self.atp_profiles.extend([
AtpProfile::clean_lan(),
AtpProfile::lossy_wifi(),
AtpProfile::wan(),
AtpProfile::stream(),
]);
self
}
#[must_use]
pub fn build(self) -> BenchmarkSuite {
let mut suite = BenchmarkSuite::new(self.name);
if self.include_scp {
suite.add_baseline(Box::new(crate::atp::benchmark::ScpAdapter::new()));
}
if self.include_rsync {
suite.add_baseline(Box::new(crate::atp::benchmark::RsyncAdapter::new()));
}
if self.include_rclone {
suite.add_baseline(Box::new(crate::atp::benchmark::RcloneAdapter::new()));
}
if self.include_curl {
suite.add_baseline(Box::new(crate::atp::benchmark::CurlAdapter::new()));
}
for profile in self.atp_profiles {
suite.add_atp_profile(profile);
}
suite
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn benchmark_suite_creation() {
let suite = BenchmarkSuite::new("test-suite");
assert_eq!(suite.name, "test-suite");
assert!(suite.baseline_adapters.is_empty());
assert!(suite.atp_profiles.is_empty());
}
#[test]
fn smoke_test_suite_has_basics() {
let suite = BenchmarkSuite::smoke_test_suite();
assert_eq!(suite.baseline_adapters.len(), 1);
assert_eq!(suite.atp_profiles.len(), 2);
}
#[test]
fn comprehensive_suite_has_more_coverage() {
let suite = BenchmarkSuite::comprehensive_suite();
assert!(!suite.baseline_adapters.is_empty());
assert!(!suite.atp_profiles.is_empty());
assert!(suite.atp_profiles.len() >= 4); }
#[test]
fn suite_builder_works() {
let suite = BenchmarkSuiteBuilder::new("builder-test")
.with_scp()
.with_atp_profile(AtpProfile::clean_lan())
.build();
assert_eq!(suite.name, "builder-test");
assert_eq!(suite.baseline_adapters.len(), 1);
assert_eq!(suite.atp_profiles.len(), 1);
}
#[test]
fn suite_builder_all_atp_profiles() {
let suite = BenchmarkSuiteBuilder::new("all-atp")
.with_all_atp_profiles()
.build();
assert!(suite.atp_profiles.len() >= 4);
}
}