1use crate::benchmarking::{BenchmarkConfig, BenchmarkResults, BenchmarkSuite};
55use crate::error::{Result, SklearsError};
56use chrono::{DateTime, Utc};
57use serde::{Deserialize, Serialize};
58use std::collections::{BTreeMap, HashMap};
59use std::path::{Path, PathBuf};
60use std::time::Duration;
61
62#[derive(Debug)]
64pub struct PerformanceReporter {
65 config: ReportConfig,
66 database: PerformanceDatabase,
67 analyzers: Vec<Box<dyn PerformanceAnalyzer>>,
68}
69
70impl PerformanceReporter {
71 pub fn new(config: ReportConfig) -> Self {
73 let database = PerformanceDatabase::new(&config.database_path);
74 let analyzers: Vec<Box<dyn PerformanceAnalyzer>> = vec![
75 Box::new(RegressionAnalyzer::new(&config)),
76 Box::new(TrendAnalyzer::new(&config)),
77 Box::new(ResourceAnalyzer::new(&config)),
78 Box::new(ScalabilityAnalyzer::new(&config)),
79 ];
80
81 Self {
82 config,
83 database,
84 analyzers,
85 }
86 }
87
88 pub fn run_ci_analysis(&mut self) -> Result<PerformanceReport> {
90 println!("Starting automated performance analysis...");
91
92 let benchmark_results = self.run_benchmarks()?;
94
95 self.database.store_results(&benchmark_results)?;
97
98 let mut analysis_results = Vec::new();
100 for analyzer in &self.analyzers {
101 let result = analyzer.analyze(&benchmark_results, &self.database)?;
102 analysis_results.push(result);
103 }
104
105 let report = self.generate_report(benchmark_results, analysis_results)?;
107
108 if report.has_regressions() && self.config.alert_config.enabled {
110 self.send_alerts(&report)?;
111 }
112
113 self.save_report(&report)?;
115
116 Ok(report)
117 }
118
119 fn run_benchmarks(&self) -> Result<BenchmarkResults> {
121 let benchmark_config = BenchmarkConfig::new()
122 .with_dataset_sizes(self.config.benchmark_sizes.clone())
123 .with_iterations(self.config.benchmark_iterations)
124 .with_accuracy_tolerance(self.config.accuracy_tolerance)
125 .with_memory_profiling(true);
126
127 let mut suite = BenchmarkSuite::new(benchmark_config);
128
129 for algorithm in &self.config.algorithms {
131 match algorithm.as_str() {
132 "linear_regression" => {
133 suite.add_benchmark(
134 "linear_regression",
135 crate::benchmarking::AlgorithmBenchmark::linear_regression(),
136 );
137 }
138 "random_forest" => {
139 suite.add_benchmark(
140 "random_forest",
141 crate::benchmarking::AlgorithmBenchmark::random_forest(),
142 );
143 }
144 "k_means" => {
145 suite.add_benchmark(
146 "k_means",
147 crate::benchmarking::AlgorithmBenchmark::k_means(),
148 );
149 }
150 _ => {
151 println!("Warning: Unknown algorithm '{algorithm}'");
152 }
153 }
154 }
155
156 suite.run()
157 }
158
159 fn generate_report(
161 &self,
162 results: BenchmarkResults,
163 analyses: Vec<AnalysisResult>,
164 ) -> Result<PerformanceReport> {
165 let timestamp = Utc::now();
166 let mut report = PerformanceReport {
167 timestamp,
168 config: self.config.clone(),
169 benchmark_results: results,
170 analysis_results: analyses,
171 summary: ReportSummary::default(),
172 output_path: PathBuf::new(),
173 };
174
175 report.summary = self.generate_summary(&report)?;
177
178 Ok(report)
179 }
180
181 fn generate_summary(&self, report: &PerformanceReport) -> Result<ReportSummary> {
183 let mut summary = ReportSummary::default();
184
185 for analysis in &report.analysis_results {
187 match &analysis.analysis_type {
188 AnalysisType::Regression(regression) => {
189 summary.total_regressions += regression.flagged_algorithms.len();
190 summary.total_improvements += regression.improved_algorithms.len();
191 }
192 AnalysisType::Trend(trend) => {
193 summary.performance_trend = trend.overall_trend.clone();
194 }
195 AnalysisType::Resource(resource) => {
196 summary.memory_efficiency = resource.memory_efficiency_score;
197 summary.cpu_efficiency = resource.cpu_efficiency_score;
198 }
199 AnalysisType::Scalability(scalability) => {
200 summary.scalability_score = scalability.overall_score;
201 }
202 }
203 }
204
205 summary.overall_health = if summary.total_regressions > 0 {
207 HealthStatus::Poor
208 } else if summary.total_improvements > 0 {
209 HealthStatus::Good
210 } else {
211 HealthStatus::Stable
212 };
213
214 Ok(summary)
215 }
216
217 fn save_report(&self, report: &PerformanceReport) -> Result<()> {
219 let base_path = &self.config.output_directory;
220 std::fs::create_dir_all(base_path).map_err(|e| {
221 SklearsError::InvalidInput(format!("Cannot create output directory: {e}"))
222 })?;
223
224 let timestamp_str = report.timestamp.format("%Y%m%d_%H%M%S").to_string();
225
226 if self.config.output_formats.contains(&OutputFormat::Json) {
228 let json_path = base_path.join(format!("performance_report_{timestamp_str}.json"));
229 let json_data = serde_json::to_string_pretty(report)
230 .map_err(|e| SklearsError::InvalidInput(format!("Cannot serialize report: {e}")))?;
231 std::fs::write(&json_path, json_data).map_err(|e| {
232 SklearsError::InvalidInput(format!("Cannot write JSON report: {e}"))
233 })?;
234 }
235
236 if self.config.output_formats.contains(&OutputFormat::Html) {
238 let html_path = base_path.join(format!("performance_report_{timestamp_str}.html"));
239 let html_content = self.generate_html_report(report)?;
240 std::fs::write(&html_path, html_content).map_err(|e| {
241 SklearsError::InvalidInput(format!("Cannot write HTML report: {e}"))
242 })?;
243 }
244
245 if self.config.output_formats.contains(&OutputFormat::Csv) {
247 let csv_path = base_path.join(format!("performance_summary_{timestamp_str}.csv"));
248 let csv_content = self.generate_csv_summary(report)?;
249 std::fs::write(&csv_path, csv_content)
250 .map_err(|e| SklearsError::InvalidInput(format!("Cannot write CSV report: {e}")))?;
251 }
252
253 Ok(())
254 }
255
256 fn generate_html_report(&self, report: &PerformanceReport) -> Result<String> {
258 let mut html = String::new();
259
260 html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
261 html.push_str("<title>Sklears Performance Report</title>\n");
262 html.push_str("<style>\n");
263 html.push_str("body { font-family: Arial, sans-serif; margin: 40px; }\n");
264 html.push_str("table { border-collapse: collapse; width: 100%; }\n");
265 html.push_str("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n");
266 html.push_str("th { background-color: #f2f2f2; }\n");
267 html.push_str(".regression { background-color: #ffebee; }\n");
268 html.push_str(".improvement { background-color: #e8f5e8; }\n");
269 html.push_str(".stable { background-color: #f0f0f0; }\n");
270 html.push_str("</style>\n</head>\n<body>\n");
271
272 html.push_str("<h1>Sklears Performance Report</h1>\n");
274 html.push_str(&format!(
275 "<p>Generated: {}</p>\n",
276 report.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
277 ));
278
279 html.push_str("<h2>Executive Summary</h2>\n");
281 html.push_str("<table>\n");
282 html.push_str("<tr><th>Metric</th><th>Value</th><th>Status</th></tr>\n");
283 html.push_str(&format!(
284 "<tr><td>Overall Health</td><td>{:?}</td><td class=\"{}\">{:?}</td></tr>\n",
285 report.summary.overall_health,
286 self.health_status_class(&report.summary.overall_health),
287 report.summary.overall_health
288 ));
289 html.push_str(&format!("<tr><td>Performance Regressions</td><td>{}</td><td class=\"regression\">{}</td></tr>\n",
290 report.summary.total_regressions,
291 if report.summary.total_regressions > 0 { "ALERT" } else { "OK" }));
292 html.push_str(&format!("<tr><td>Performance Improvements</td><td>{}</td><td class=\"improvement\">{}</td></tr>\n",
293 report.summary.total_improvements,
294 if report.summary.total_improvements > 0 { "GOOD" } else { "NONE" }));
295 html.push_str(&format!(
296 "<tr><td>Memory Efficiency</td><td>{:.2}</td><td>{}</td></tr>\n",
297 report.summary.memory_efficiency,
298 if report.summary.memory_efficiency > 0.8 {
299 "GOOD"
300 } else {
301 "NEEDS IMPROVEMENT"
302 }
303 ));
304 html.push_str("</table>\n");
305
306 html.push_str("<h2>Detailed Results</h2>\n");
308 for analysis in &report.analysis_results {
309 html.push_str(&format!("<h3>{}</h3>\n", analysis.analyzer_name));
310 html.push_str(&format!("<p>{}</p>\n", analysis.description));
311
312 if let AnalysisType::Regression(regression) = &analysis.analysis_type {
314 if !regression.flagged_algorithms.is_empty() {
315 html.push_str("<h4>Performance Regressions Detected</h4>\n");
316 html.push_str("<ul>\n");
317 for algorithm in ®ression.flagged_algorithms {
318 html.push_str(&format!(
319 "<li class=\"regression\">{}: {:.2}% slower</li>\n",
320 algorithm.name, algorithm.performance_change
321 ));
322 }
323 html.push_str("</ul>\n");
324 }
325 }
326 }
327
328 html.push_str("</body>\n</html>");
329 Ok(html)
330 }
331
332 fn generate_csv_summary(&self, report: &PerformanceReport) -> Result<String> {
334 let mut csv = String::new();
335 csv.push_str("Metric,Value,Status\n");
336 csv.push_str(&format!(
337 "Overall Health,{:?},{}\n",
338 report.summary.overall_health,
339 if matches!(report.summary.overall_health, HealthStatus::Good) {
340 "GOOD"
341 } else {
342 "ALERT"
343 }
344 ));
345 csv.push_str(&format!(
346 "Total Regressions,{},{}\n",
347 report.summary.total_regressions,
348 if report.summary.total_regressions > 0 {
349 "ALERT"
350 } else {
351 "OK"
352 }
353 ));
354 csv.push_str(&format!(
355 "Total Improvements,{},{}\n",
356 report.summary.total_improvements,
357 if report.summary.total_improvements > 0 {
358 "GOOD"
359 } else {
360 "NONE"
361 }
362 ));
363 csv.push_str(&format!(
364 "Memory Efficiency,{:.2},{}\n",
365 report.summary.memory_efficiency,
366 if report.summary.memory_efficiency > 0.8 {
367 "GOOD"
368 } else {
369 "NEEDS_IMPROVEMENT"
370 }
371 ));
372 Ok(csv)
373 }
374
375 fn health_status_class(&self, status: &HealthStatus) -> &'static str {
376 match status {
377 HealthStatus::Good => "improvement",
378 HealthStatus::Stable => "stable",
379 HealthStatus::Poor => "regression",
380 }
381 }
382
383 fn send_alerts(&self, report: &PerformanceReport) -> Result<()> {
385 if !self.config.alert_config.enabled {
386 return Ok(());
387 }
388
389 let alert_message = format!(
390 "Performance Alert: {} regressions detected in sklears performance analysis.\nReport timestamp: {}",
391 report.summary.total_regressions,
392 report.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
393 );
394
395 if self.config.alert_config.email_notifications {
397 self.send_email_alert(&alert_message)?;
398 }
399
400 if let Some(ref webhook) = self.config.alert_config.slack_webhook {
402 self.send_slack_alert(webhook, &alert_message)?;
403 }
404
405 Ok(())
406 }
407
408 fn send_email_alert(&self, message: &str) -> Result<()> {
409 println!("EMAIL ALERT: {message}");
411 Ok(())
412 }
413
414 fn send_slack_alert(&self, webhook: &str, message: &str) -> Result<()> {
415 println!("SLACK ALERT to {webhook}: {message}");
417 Ok(())
418 }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct ReportConfig {
424 pub database_path: PathBuf,
425 pub output_directory: PathBuf,
426 pub output_formats: Vec<OutputFormat>,
427 pub algorithms: Vec<String>,
428 pub benchmark_sizes: Vec<usize>,
429 pub benchmark_iterations: usize,
430 pub accuracy_tolerance: f64,
431 pub baseline_branch: Option<String>,
432 pub regression_threshold: RegressionThreshold,
433 pub alert_config: AlertConfig,
434}
435
436impl ReportConfig {
437 pub fn new() -> Self {
439 Self {
440 database_path: PathBuf::from("performance_history.db"),
441 output_directory: PathBuf::from("performance_reports"),
442 output_formats: vec![OutputFormat::Html, OutputFormat::Json],
443 algorithms: vec![
444 "linear_regression".to_string(),
445 "random_forest".to_string(),
446 "k_means".to_string(),
447 ],
448 benchmark_sizes: vec![1000, 5000, 10000],
449 benchmark_iterations: 5,
450 accuracy_tolerance: 1e-6,
451 baseline_branch: None,
452 regression_threshold: RegressionThreshold::Percentage(5.0),
453 alert_config: AlertConfig::default(),
454 }
455 }
456
457 pub fn with_baseline_branch(mut self, branch: &str) -> Self {
459 self.baseline_branch = Some(branch.to_string());
460 self
461 }
462
463 pub fn with_regression_threshold(mut self, threshold: RegressionThreshold) -> Self {
465 self.regression_threshold = threshold;
466 self
467 }
468
469 pub fn with_alert_config(mut self, config: AlertConfig) -> Self {
471 self.alert_config = config;
472 self
473 }
474}
475
476impl Default for ReportConfig {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484pub enum OutputFormat {
485 Html,
486 Json,
487 Csv,
488 Pdf,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub enum RegressionThreshold {
494 Absolute(f64),
496 Percentage(f64),
498 Statistical { confidence_level: f64 },
500}
501
502#[derive(Debug, Clone, Serialize, Deserialize, Default)]
504pub struct AlertConfig {
505 pub enabled: bool,
506 pub email_notifications: bool,
507 pub email_recipients: Vec<String>,
508 pub slack_webhook: Option<String>,
509 pub custom_webhooks: Vec<String>,
510}
511
512impl AlertConfig {
513 pub fn new() -> Self {
514 Self::default()
515 }
516
517 pub fn with_email_notifications(mut self, enabled: bool) -> Self {
518 self.email_notifications = enabled;
519 self
520 }
521
522 pub fn with_slack_webhook(mut self, webhook: &str) -> Self {
523 self.slack_webhook = Some(webhook.to_string());
524 self
525 }
526}
527
528#[derive(Debug, Serialize, Deserialize)]
530pub struct PerformanceReport {
531 pub timestamp: DateTime<Utc>,
532 pub config: ReportConfig,
533 pub benchmark_results: BenchmarkResults,
534 pub analysis_results: Vec<AnalysisResult>,
535 pub summary: ReportSummary,
536 pub output_path: PathBuf,
537}
538
539impl PerformanceReport {
540 pub fn has_regressions(&self) -> bool {
542 self.summary.total_regressions > 0
543 }
544
545 pub fn output_path(&self) -> &Path {
547 &self.output_path
548 }
549}
550
551#[derive(Debug, Default, Serialize, Deserialize)]
553pub struct ReportSummary {
554 pub overall_health: HealthStatus,
555 pub total_regressions: usize,
556 pub total_improvements: usize,
557 pub memory_efficiency: f64,
558 pub cpu_efficiency: f64,
559 pub scalability_score: f64,
560 pub performance_trend: TrendDirection,
561}
562
563#[derive(Debug, Default, Serialize, Deserialize)]
565pub enum HealthStatus {
566 Good,
567 #[default]
568 Stable,
569 Poor,
570}
571
572#[derive(Debug, Clone, Default, Serialize, Deserialize)]
574pub enum TrendDirection {
575 Improving,
576 #[default]
577 Stable,
578 Declining,
579}
580
581#[derive(Debug)]
583#[allow(dead_code)]
584pub struct PerformanceDatabase {
585 path: PathBuf,
586 data: BTreeMap<DateTime<Utc>, BenchmarkResults>,
587}
588
589impl PerformanceDatabase {
590 pub fn new(path: &Path) -> Self {
591 Self {
592 path: path.to_path_buf(),
593 data: BTreeMap::new(),
594 }
595 }
596
597 pub fn store_results(&mut self, results: &BenchmarkResults) -> Result<()> {
598 let timestamp = Utc::now();
599 self.data.insert(timestamp, results.clone());
600
601 Ok(())
603 }
604
605 pub fn get_historical_data(&self, time_range: TimeRange) -> Vec<&BenchmarkResults> {
606 let cutoff = match time_range {
607 TimeRange::Days(days) => Utc::now() - chrono::Duration::days(days as i64),
608 TimeRange::Weeks(weeks) => Utc::now() - chrono::Duration::weeks(weeks as i64),
609 TimeRange::Months(months) => Utc::now() - chrono::Duration::days(months as i64 * 30),
610 };
611
612 self.data
613 .range(cutoff..)
614 .map(|(_, results)| results)
615 .collect()
616 }
617}
618
619#[derive(Debug, Clone, Copy)]
621pub enum TimeRange {
622 Days(u32),
623 Weeks(u32),
624 Months(u32),
625}
626
627pub trait PerformanceAnalyzer: std::fmt::Debug {
629 fn analyze(
630 &self,
631 results: &BenchmarkResults,
632 database: &PerformanceDatabase,
633 ) -> Result<AnalysisResult>;
634}
635
636#[derive(Debug, Serialize, Deserialize)]
638pub struct AnalysisResult {
639 pub analyzer_name: String,
640 pub analysis_type: AnalysisType,
641 pub description: String,
642 pub timestamp: DateTime<Utc>,
643}
644
645#[derive(Debug, Serialize, Deserialize)]
647pub enum AnalysisType {
648 Regression(RegressionAnalysis),
649 Trend(TrendAnalysis),
650 Resource(ResourceAnalysis),
651 Scalability(ScalabilityAnalysis),
652}
653
654#[derive(Debug, Serialize, Deserialize)]
656pub struct RegressionAnalysis {
657 pub flagged_algorithms: Vec<AlgorithmRegression>,
658 pub improved_algorithms: Vec<AlgorithmRegression>,
659 pub stable_algorithms: Vec<String>,
660}
661
662#[derive(Debug, Serialize, Deserialize)]
664pub struct AlgorithmRegression {
665 pub name: String,
666 pub performance_change: f64, pub confidence_level: f64,
668 pub baseline_timing: Duration,
669 pub current_timing: Duration,
670}
671
672#[derive(Debug, Serialize, Deserialize)]
674pub struct TrendAnalysis {
675 pub overall_trend: TrendDirection,
676 pub algorithm_trends: HashMap<String, TrendDirection>,
677 pub trend_strength: f64, }
679
680#[derive(Debug, Serialize, Deserialize)]
682pub struct ResourceAnalysis {
683 pub memory_efficiency_score: f64,
684 pub cpu_efficiency_score: f64,
685 pub memory_peak_usage: usize,
686 pub memory_leak_indicators: Vec<String>,
687}
688
689#[derive(Debug, Serialize, Deserialize)]
691pub struct ScalabilityAnalysis {
692 pub overall_score: f64,
693 pub scaling_coefficients: HashMap<String, f64>,
694 pub bottleneck_analysis: Vec<String>,
695}
696
697#[derive(Debug)]
699#[allow(dead_code)]
700pub struct RegressionAnalyzer {
701 config: ReportConfig,
702}
703
704impl RegressionAnalyzer {
705 pub fn new(config: &ReportConfig) -> Self {
706 Self {
707 config: config.clone(),
708 }
709 }
710}
711
712impl PerformanceAnalyzer for RegressionAnalyzer {
713 fn analyze(
714 &self,
715 _results: &BenchmarkResults,
716 database: &PerformanceDatabase,
717 ) -> Result<AnalysisResult> {
718 let _historical_data = database.get_historical_data(TimeRange::Days(30));
719
720 let flagged_algorithms = Vec::new();
721 let improved_algorithms = Vec::new();
722 let stable_algorithms = vec![
724 "linear_regression".to_string(),
725 "random_forest".to_string(),
726 "k_means".to_string(),
727 ];
728
729 let analysis = RegressionAnalysis {
730 flagged_algorithms,
731 improved_algorithms,
732 stable_algorithms,
733 };
734
735 Ok(AnalysisResult {
736 analyzer_name: "Regression Analyzer".to_string(),
737 analysis_type: AnalysisType::Regression(analysis),
738 description:
739 "Statistical analysis of performance regressions compared to historical baselines"
740 .to_string(),
741 timestamp: Utc::now(),
742 })
743 }
744}
745
746#[derive(Debug)]
748#[allow(dead_code)]
749pub struct TrendAnalyzer {
750 config: ReportConfig,
751}
752
753impl TrendAnalyzer {
754 pub fn new(config: &ReportConfig) -> Self {
755 Self {
756 config: config.clone(),
757 }
758 }
759}
760
761impl PerformanceAnalyzer for TrendAnalyzer {
762 fn analyze(
763 &self,
764 _results: &BenchmarkResults,
765 _database: &PerformanceDatabase,
766 ) -> Result<AnalysisResult> {
767 let analysis = TrendAnalysis {
768 overall_trend: TrendDirection::Stable,
769 algorithm_trends: HashMap::new(),
770 trend_strength: 0.8,
771 };
772
773 Ok(AnalysisResult {
774 analyzer_name: "Trend Analyzer".to_string(),
775 analysis_type: AnalysisType::Trend(analysis),
776 description: "Analysis of performance trends over time".to_string(),
777 timestamp: Utc::now(),
778 })
779 }
780}
781
782#[derive(Debug)]
784#[allow(dead_code)]
785pub struct ResourceAnalyzer {
786 config: ReportConfig,
787}
788
789impl ResourceAnalyzer {
790 pub fn new(config: &ReportConfig) -> Self {
791 Self {
792 config: config.clone(),
793 }
794 }
795}
796
797impl PerformanceAnalyzer for ResourceAnalyzer {
798 fn analyze(
799 &self,
800 _results: &BenchmarkResults,
801 _database: &PerformanceDatabase,
802 ) -> Result<AnalysisResult> {
803 let analysis = ResourceAnalysis {
804 memory_efficiency_score: 0.85,
805 cpu_efficiency_score: 0.92,
806 memory_peak_usage: 1024 * 1024 * 128, memory_leak_indicators: Vec::new(),
808 };
809
810 Ok(AnalysisResult {
811 analyzer_name: "Resource Analyzer".to_string(),
812 analysis_type: AnalysisType::Resource(analysis),
813 description: "Analysis of memory and CPU resource utilization".to_string(),
814 timestamp: Utc::now(),
815 })
816 }
817}
818
819#[derive(Debug)]
821#[allow(dead_code)]
822pub struct ScalabilityAnalyzer {
823 config: ReportConfig,
824}
825
826impl ScalabilityAnalyzer {
827 pub fn new(config: &ReportConfig) -> Self {
828 Self {
829 config: config.clone(),
830 }
831 }
832}
833
834impl PerformanceAnalyzer for ScalabilityAnalyzer {
835 fn analyze(
836 &self,
837 _results: &BenchmarkResults,
838 _database: &PerformanceDatabase,
839 ) -> Result<AnalysisResult> {
840 let analysis = ScalabilityAnalysis {
841 overall_score: 0.88,
842 scaling_coefficients: HashMap::new(),
843 bottleneck_analysis: Vec::new(),
844 };
845
846 Ok(AnalysisResult {
847 analyzer_name: "Scalability Analyzer".to_string(),
848 analysis_type: AnalysisType::Scalability(analysis),
849 description: "Analysis of algorithm scalability characteristics".to_string(),
850 timestamp: Utc::now(),
851 })
852 }
853}
854
855#[allow(non_snake_case)]
856#[cfg(test)]
857mod tests {
858 use super::*;
859 use tempfile::tempdir;
860
861 #[test]
862 fn test_report_config_creation() {
863 let config = ReportConfig::new()
864 .with_baseline_branch("main")
865 .with_regression_threshold(RegressionThreshold::Percentage(10.0));
866
867 assert_eq!(config.baseline_branch, Some("main".to_string()));
868 assert!(matches!(
869 config.regression_threshold,
870 RegressionThreshold::Percentage(10.0)
871 ));
872 }
873
874 #[test]
875 fn test_alert_config() {
876 let config = AlertConfig::new()
877 .with_email_notifications(true)
878 .with_slack_webhook("https://hooks.slack.com/test");
879
880 assert!(config.email_notifications);
881 assert_eq!(
882 config.slack_webhook,
883 Some("https://hooks.slack.com/test".to_string())
884 );
885 }
886
887 #[test]
888 fn test_performance_database() {
889 let dir = tempdir().unwrap();
890 let db_path = dir.path().join("test.db");
891 let mut database = PerformanceDatabase::new(&db_path);
892
893 let config = BenchmarkConfig::new();
895 let results = BenchmarkResults::new(config);
896
897 assert!(database.store_results(&results).is_ok());
898
899 let historical = database.get_historical_data(TimeRange::Days(1));
900 assert_eq!(historical.len(), 1);
901 }
902
903 #[test]
904 fn test_regression_analyzer() {
905 let config = ReportConfig::new();
906 let analyzer = RegressionAnalyzer::new(&config);
907 let database = PerformanceDatabase::new(&PathBuf::from("test.db"));
908
909 let benchmark_config = BenchmarkConfig::new();
910 let results = BenchmarkResults::new(benchmark_config);
911
912 let analysis = analyzer.analyze(&results, &database);
913 assert!(analysis.is_ok());
914
915 let analysis = analysis.unwrap();
916 assert_eq!(analysis.analyzer_name, "Regression Analyzer");
917 assert!(matches!(
918 analysis.analysis_type,
919 AnalysisType::Regression(_)
920 ));
921 }
922
923 #[test]
924 fn test_performance_reporter_creation() {
925 let config = ReportConfig::new();
926 let reporter = PerformanceReporter::new(config);
927
928 assert_eq!(reporter.analyzers.len(), 4); }
930
931 #[test]
932 fn test_regression_threshold_types() {
933 let absolute = RegressionThreshold::Absolute(100.0);
934 let percentage = RegressionThreshold::Percentage(5.0);
935 let statistical = RegressionThreshold::Statistical {
936 confidence_level: 0.95,
937 };
938
939 assert!(matches!(absolute, RegressionThreshold::Absolute(100.0)));
940 assert!(matches!(percentage, RegressionThreshold::Percentage(5.0)));
941 assert!(matches!(
942 statistical,
943 RegressionThreshold::Statistical {
944 confidence_level: 0.95
945 }
946 ));
947 }
948
949 #[test]
950 fn test_health_status() {
951 let good = HealthStatus::Good;
952 let stable = HealthStatus::Stable;
953 let poor = HealthStatus::Poor;
954
955 assert!(matches!(good, HealthStatus::Good));
956 assert!(matches!(stable, HealthStatus::Stable));
957 assert!(matches!(poor, HealthStatus::Poor));
958 }
959
960 #[test]
961 fn test_output_formats() {
962 let formats = vec![
963 OutputFormat::Html,
964 OutputFormat::Json,
965 OutputFormat::Csv,
966 OutputFormat::Pdf,
967 ];
968
969 assert_eq!(formats.len(), 4);
970 assert!(formats.contains(&OutputFormat::Html));
971 assert!(formats.contains(&OutputFormat::Json));
972 }
973}