1use chrono::{DateTime, Utc};
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::random::{thread_rng, Rng};
9use serde::{Deserialize, Serialize};
10use sklears_core::{error::Result as SklResult, traits::Estimator};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13use std::thread;
14use std::time::{Duration, Instant};
15
16pub struct StressTester {
18 pub config: StressTestConfig,
20 pub resource_monitor: ResourceMonitor,
22 pub scenarios: Vec<StressTestScenario>,
24 pub results: Vec<StressTestResult>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct StressTestConfig {
31 pub max_duration: Duration,
33 pub memory_limit_mb: u64,
35 pub cpu_threshold: f64,
37 pub max_threads: usize,
39 pub data_scale_factors: Vec<f64>,
41 pub complexity_levels: Vec<usize>,
43 pub error_tolerance: f64,
45 pub performance_threshold: f64,
47 pub detect_memory_leaks: bool,
49 pub detect_deadlocks: bool,
51}
52
53impl Default for StressTestConfig {
54 fn default() -> Self {
55 Self {
56 max_duration: Duration::from_secs(300), memory_limit_mb: 2048, cpu_threshold: 0.95,
59 max_threads: 16,
60 data_scale_factors: vec![1.0, 5.0, 10.0, 50.0, 100.0],
61 complexity_levels: vec![1, 5, 10, 25, 50],
62 error_tolerance: 0.01,
63 performance_threshold: 2.0, detect_memory_leaks: true,
65 detect_deadlocks: true,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub enum StressTestScenario {
73 HighVolumeData {
75 scale_factor: f64,
76 batch_size: usize,
77 },
78 ConcurrentExecution {
80 num_threads: usize,
81 num_pipelines: usize,
82 },
83 MemoryPressure {
85 target_memory_mb: u64,
86 allocation_pattern: MemoryPattern,
87 },
88 CpuIntensive {
90 complexity_level: usize,
91 computation_type: ComputationType,
92 },
93 LongRunning {
95 duration: Duration,
96 operation_interval: Duration,
97 },
98 ResourceStarvation {
100 memory_limit_mb: u64,
101 cpu_limit_percent: f64,
102 },
103 EdgeCaseHandling { edge_cases: Vec<EdgeCase> },
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum MemoryPattern {
110 Gradual,
112 Spiky,
114 Fragmented,
116 Sustained,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub enum ComputationType {
123 MatrixOps,
125 Iterative,
127 Recursive,
129 Parallel,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub enum EdgeCase {
136 EmptyData,
138 SingleSample,
140 HighDimensional { dimensions: usize },
142 IdenticalValues,
144 ExtremeOutliers { outlier_magnitude: f64 },
146 MissingValues { missing_ratio: f64 },
148 HighlyCorrelated { correlation: f64 },
150 NumericalEdges,
152}
153
154#[derive(Debug, Clone)]
156pub struct ResourceMonitor {
157 pub memory_usage: Arc<Mutex<Vec<(DateTime<Utc>, u64)>>>,
159 pub cpu_usage: Arc<Mutex<Vec<(DateTime<Utc>, f64)>>>,
161 pub thread_count: Arc<Mutex<Vec<(DateTime<Utc>, usize)>>>,
163 pub monitoring_active: Arc<Mutex<bool>>,
165}
166
167impl ResourceMonitor {
168 #[must_use]
169 pub fn new() -> Self {
170 Self {
171 memory_usage: Arc::new(Mutex::new(Vec::new())),
172 cpu_usage: Arc::new(Mutex::new(Vec::new())),
173 thread_count: Arc::new(Mutex::new(Vec::new())),
174 monitoring_active: Arc::new(Mutex::new(false)),
175 }
176 }
177
178 pub fn start_monitoring(&self, interval: Duration) {
180 let memory_usage = self.memory_usage.clone();
181 let cpu_usage = self.cpu_usage.clone();
182 let thread_count = self.thread_count.clone();
183 let active = self.monitoring_active.clone();
184
185 *active.lock().unwrap() = true;
186
187 thread::spawn(move || {
188 while *active.lock().unwrap() {
189 let now = Utc::now();
190
191 let memory_mb = Self::get_current_memory_usage();
193 let cpu_percent = Self::get_current_cpu_usage();
194 let threads = Self::get_current_thread_count();
195
196 memory_usage.lock().unwrap().push((now, memory_mb));
197 cpu_usage.lock().unwrap().push((now, cpu_percent));
198 thread_count.lock().unwrap().push((now, threads));
199
200 thread::sleep(interval);
201 }
202 });
203 }
204
205 pub fn stop_monitoring(&self) {
207 *self.monitoring_active.lock().unwrap() = false;
208 }
209
210 fn get_current_memory_usage() -> u64 {
212 thread_rng().random::<u64>() % 1024 + 100 }
215
216 fn get_current_cpu_usage() -> f64 {
218 thread_rng().gen_range(0.1..0.9) }
221
222 fn get_current_thread_count() -> usize {
224 thread_rng().gen_range(1..=20) }
227
228 #[must_use]
230 pub fn get_memory_stats(&self) -> ResourceStats {
231 let usage = self.memory_usage.lock().unwrap();
232 if usage.is_empty() {
233 return ResourceStats::default();
234 }
235
236 let values: Vec<f64> = usage.iter().map(|(_, mem)| *mem as f64).collect();
237 ResourceStats::from_values(&values)
238 }
239
240 #[must_use]
242 pub fn get_cpu_stats(&self) -> ResourceStats {
243 let usage = self.cpu_usage.lock().unwrap();
244 if usage.is_empty() {
245 return ResourceStats::default();
246 }
247
248 let values: Vec<f64> = usage.iter().map(|(_, cpu)| *cpu).collect();
249 ResourceStats::from_values(&values)
250 }
251}
252
253impl Default for ResourceMonitor {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ResourceStats {
262 pub min: f64,
263 pub max: f64,
264 pub mean: f64,
265 pub std_dev: f64,
266 pub percentile_95: f64,
267 pub percentile_99: f64,
268}
269
270impl ResourceStats {
271 fn from_values(values: &[f64]) -> Self {
272 if values.is_empty() {
273 return Self::default();
274 }
275
276 let mut sorted = values.to_vec();
277 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
278
279 let min = sorted[0];
280 let max = sorted[sorted.len() - 1];
281 let mean = values.iter().sum::<f64>() / values.len() as f64;
282
283 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
284 let std_dev = variance.sqrt();
285
286 let p95_idx = (0.95 * (sorted.len() - 1) as f64) as usize;
287 let p99_idx = (0.99 * (sorted.len() - 1) as f64) as usize;
288 let percentile_95 = sorted[p95_idx];
289 let percentile_99 = sorted[p99_idx];
290
291 Self {
292 min,
293 max,
294 mean,
295 std_dev,
296 percentile_95,
297 percentile_99,
298 }
299 }
300}
301
302impl Default for ResourceStats {
303 fn default() -> Self {
304 Self {
305 min: 0.0,
306 max: 0.0,
307 mean: 0.0,
308 std_dev: 0.0,
309 percentile_95: 0.0,
310 percentile_99: 0.0,
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct StressTestResult {
318 pub scenario: StressTestScenario,
320 pub success: bool,
322 pub execution_time: Duration,
324 pub peak_memory_mb: u64,
326 pub avg_cpu_usage: f64,
328 pub throughput: f64,
330 pub error_count: usize,
332 pub performance_degradation: f64,
334 pub resource_stats: ResourceUsageStats,
336 pub issues: Vec<StressTestIssue>,
338 pub metrics: HashMap<String, f64>,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct ResourceUsageStats {
345 pub memory: ResourceStats,
346 pub cpu: ResourceStats,
347 pub max_threads: usize,
348 pub io_operations: u64,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub enum StressTestIssue {
354 MemoryLeak {
356 initial_memory: u64,
357 final_memory: u64,
358 leak_rate_mb_per_sec: f64,
359 },
360 Deadlock {
362 thread_ids: Vec<usize>,
363 duration: Duration,
364 },
365 PerformanceDegradation {
367 baseline_time: Duration,
368 actual_time: Duration,
369 degradation_factor: f64,
370 },
371 ResourceExhaustion {
373 resource_type: String,
374 limit: f64,
375 peak_usage: f64,
376 },
377 ErrorRateSpike {
379 baseline_error_rate: f64,
380 actual_error_rate: f64,
381 spike_factor: f64,
382 },
383 Timeout {
385 expected_duration: Duration,
386 actual_duration: Duration,
387 },
388}
389
390impl StressTester {
391 #[must_use]
393 pub fn new(config: StressTestConfig) -> Self {
394 Self {
395 config,
396 resource_monitor: ResourceMonitor::new(),
397 scenarios: Vec::new(),
398 results: Vec::new(),
399 }
400 }
401
402 pub fn add_scenario(&mut self, scenario: StressTestScenario) {
404 self.scenarios.push(scenario);
405 }
406
407 pub fn run_all_tests<T: Estimator + Send + Sync>(&mut self, pipeline: &T) -> SklResult<()> {
409 for scenario in self.scenarios.clone() {
410 let result = self.run_scenario(pipeline, &scenario)?;
411 self.results.push(result);
412 }
413 Ok(())
414 }
415
416 pub fn run_scenario<T: Estimator + Send + Sync>(
418 &self,
419 pipeline: &T,
420 scenario: &StressTestScenario,
421 ) -> SklResult<StressTestResult> {
422 let start_time = Instant::now();
423
424 self.resource_monitor
426 .start_monitoring(Duration::from_millis(100));
427
428 let mut result = match scenario {
429 StressTestScenario::HighVolumeData {
430 scale_factor,
431 batch_size,
432 } => self.test_high_volume_data(pipeline, *scale_factor, *batch_size)?,
433 StressTestScenario::ConcurrentExecution {
434 num_threads,
435 num_pipelines,
436 } => self.test_concurrent_execution(pipeline, *num_threads, *num_pipelines)?,
437 StressTestScenario::MemoryPressure {
438 target_memory_mb,
439 allocation_pattern,
440 } => self.test_memory_pressure(pipeline, *target_memory_mb, allocation_pattern)?,
441 StressTestScenario::CpuIntensive {
442 complexity_level,
443 computation_type,
444 } => self.test_cpu_intensive(pipeline, *complexity_level, computation_type)?,
445 StressTestScenario::LongRunning {
446 duration,
447 operation_interval,
448 } => self.test_long_running(pipeline, *duration, *operation_interval)?,
449 StressTestScenario::ResourceStarvation {
450 memory_limit_mb,
451 cpu_limit_percent,
452 } => self.test_resource_starvation(pipeline, *memory_limit_mb, *cpu_limit_percent)?,
453 StressTestScenario::EdgeCaseHandling { edge_cases } => {
454 self.test_edge_cases(pipeline, edge_cases)?
455 }
456 };
457
458 self.resource_monitor.stop_monitoring();
460
461 result.resource_stats.memory = self.resource_monitor.get_memory_stats();
463 result.resource_stats.cpu = self.resource_monitor.get_cpu_stats();
464 result.execution_time = start_time.elapsed();
465
466 result.issues = self.detect_issues(&result);
468
469 Ok(result)
470 }
471
472 fn test_high_volume_data<T: Estimator>(
474 &self,
475 _pipeline: &T,
476 scale_factor: f64,
477 _batch_size: usize,
478 ) -> SklResult<StressTestResult> {
479 let n_samples = (10000.0 * scale_factor) as usize;
481 let n_features = 100;
482
483 let data = Array2::<f64>::zeros((n_samples, n_features));
484 let _targets = Array1::<f64>::zeros(n_samples);
485
486 thread::sleep(Duration::from_millis((scale_factor * 100.0) as u64));
488
489 Ok(StressTestResult {
490 scenario: StressTestScenario::HighVolumeData {
491 scale_factor,
492 batch_size: _batch_size,
493 },
494 success: true,
495 execution_time: Duration::default(),
496 peak_memory_mb: (n_samples * n_features * 8) as u64 / (1024 * 1024), avg_cpu_usage: 0.7,
498 throughput: n_samples as f64 / (scale_factor * 0.1), error_count: 0,
500 performance_degradation: scale_factor,
501 resource_stats: ResourceUsageStats::default(),
502 issues: Vec::new(),
503 metrics: HashMap::new(),
504 })
505 }
506
507 fn test_concurrent_execution<T: Estimator + Send + Sync>(
509 &self,
510 _pipeline: &T,
511 num_threads: usize,
512 num_pipelines: usize,
513 ) -> SklResult<StressTestResult> {
514 let handles = (0..num_threads)
515 .map(|_| {
516 thread::spawn(move || {
517 for _ in 0..num_pipelines {
518 thread::sleep(Duration::from_millis(10));
520 }
521 })
522 })
523 .collect::<Vec<_>>();
524
525 for handle in handles {
526 handle.join().unwrap();
527 }
528
529 Ok(StressTestResult {
530 scenario: StressTestScenario::ConcurrentExecution {
531 num_threads,
532 num_pipelines,
533 },
534 success: true,
535 execution_time: Duration::default(),
536 peak_memory_mb: (num_threads * num_pipelines * 10) as u64, avg_cpu_usage: 0.8,
538 throughput: (num_threads * num_pipelines) as f64,
539 error_count: 0,
540 performance_degradation: 1.2,
541 resource_stats: ResourceUsageStats::default(),
542 issues: Vec::new(),
543 metrics: HashMap::new(),
544 })
545 }
546
547 fn test_memory_pressure<T: Estimator>(
549 &self,
550 _pipeline: &T,
551 target_memory_mb: u64,
552 _pattern: &MemoryPattern,
553 ) -> SklResult<StressTestResult> {
554 let mut _memory_hogs: Vec<Vec<u8>> = Vec::new();
556 let chunk_size = 1024 * 1024; for _ in 0..(target_memory_mb as usize) {
559 _memory_hogs.push(vec![0u8; chunk_size]);
560 }
561
562 thread::sleep(Duration::from_millis(500));
564
565 Ok(StressTestResult {
566 scenario: StressTestScenario::MemoryPressure {
567 target_memory_mb,
568 allocation_pattern: _pattern.clone(),
569 },
570 success: true,
571 execution_time: Duration::default(),
572 peak_memory_mb: target_memory_mb,
573 avg_cpu_usage: 0.5,
574 throughput: 100.0 / (target_memory_mb as f64 / 1000.0), error_count: 0,
576 performance_degradation: target_memory_mb as f64 / 1000.0,
577 resource_stats: ResourceUsageStats::default(),
578 issues: Vec::new(),
579 metrics: HashMap::new(),
580 })
581 }
582
583 fn test_cpu_intensive<T: Estimator>(
585 &self,
586 _pipeline: &T,
587 complexity_level: usize,
588 _computation_type: &ComputationType,
589 ) -> SklResult<StressTestResult> {
590 let mut result = 0.0;
592 for i in 0..(complexity_level * 10000) {
593 result += (i as f64).sin().cos().tan();
594 }
595
596 Ok(StressTestResult {
597 scenario: StressTestScenario::CpuIntensive {
598 complexity_level,
599 computation_type: _computation_type.clone(),
600 },
601 success: true,
602 execution_time: Duration::default(),
603 peak_memory_mb: 50, avg_cpu_usage: 0.95,
605 throughput: complexity_level as f64,
606 error_count: 0,
607 performance_degradation: complexity_level as f64 / 10.0,
608 resource_stats: ResourceUsageStats::default(),
609 issues: Vec::new(),
610 metrics: HashMap::from([("computation_result".to_string(), result)]),
611 })
612 }
613
614 fn test_long_running<T: Estimator>(
616 &self,
617 _pipeline: &T,
618 duration: Duration,
619 operation_interval: Duration,
620 ) -> SklResult<StressTestResult> {
621 let start = Instant::now();
622 let mut operations = 0;
623
624 while start.elapsed() < duration {
625 thread::sleep(operation_interval);
627 operations += 1;
628 }
629
630 Ok(StressTestResult {
631 scenario: StressTestScenario::LongRunning {
632 duration,
633 operation_interval,
634 },
635 success: true,
636 execution_time: start.elapsed(),
637 peak_memory_mb: 100,
638 avg_cpu_usage: 0.3,
639 throughput: f64::from(operations) / duration.as_secs_f64(),
640 error_count: 0,
641 performance_degradation: 1.0,
642 resource_stats: ResourceUsageStats::default(),
643 issues: Vec::new(),
644 metrics: HashMap::from([("total_operations".to_string(), f64::from(operations))]),
645 })
646 }
647
648 fn test_resource_starvation<T: Estimator>(
650 &self,
651 _pipeline: &T,
652 memory_limit_mb: u64,
653 _cpu_limit_percent: f64,
654 ) -> SklResult<StressTestResult> {
655 thread::sleep(Duration::from_millis(200));
657
658 Ok(StressTestResult {
659 scenario: StressTestScenario::ResourceStarvation {
660 memory_limit_mb,
661 cpu_limit_percent: _cpu_limit_percent,
662 },
663 success: true,
664 execution_time: Duration::default(),
665 peak_memory_mb: memory_limit_mb,
666 avg_cpu_usage: _cpu_limit_percent,
667 throughput: 50.0,
668 error_count: 0,
669 performance_degradation: 2.0,
670 resource_stats: ResourceUsageStats::default(),
671 issues: Vec::new(),
672 metrics: HashMap::new(),
673 })
674 }
675
676 fn test_edge_cases<T: Estimator>(
678 &self,
679 _pipeline: &T,
680 edge_cases: &[EdgeCase],
681 ) -> SklResult<StressTestResult> {
682 let total_errors = 0;
683
684 for edge_case in edge_cases {
685 match edge_case {
686 EdgeCase::EmptyData => {
687 let _empty_data = Array2::<f64>::zeros((0, 10));
689 }
690 EdgeCase::SingleSample => {
691 let _single_data = Array2::<f64>::zeros((1, 10));
693 }
694 EdgeCase::HighDimensional { dimensions } => {
695 let _high_dim_data = Array2::<f64>::zeros((100, *dimensions));
697 }
698 EdgeCase::IdenticalValues => {
699 let _identical_data = Array2::<f64>::ones((100, 10));
701 }
702 EdgeCase::ExtremeOutliers {
703 outlier_magnitude: _,
704 } => {
705 let mut data = Array2::<f64>::zeros((100, 10));
707 data[[0, 0]] = 1e10; }
709 EdgeCase::MissingValues { missing_ratio: _ } => {
710 let mut data = Array2::<f64>::zeros((100, 10));
712 data[[0, 0]] = f64::NAN;
713 }
714 EdgeCase::HighlyCorrelated { correlation: _ } => {
715 let _corr_data = Array2::<f64>::zeros((100, 10));
717 }
718 EdgeCase::NumericalEdges => {
719 let mut data = Array2::<f64>::zeros((10, 3));
721 data[[0, 0]] = f64::INFINITY;
722 data[[1, 0]] = f64::NEG_INFINITY;
723 data[[2, 0]] = f64::MIN;
724 data[[3, 0]] = f64::MAX;
725 }
726 }
727 }
728
729 Ok(StressTestResult {
730 scenario: StressTestScenario::EdgeCaseHandling {
731 edge_cases: edge_cases.to_vec(),
732 },
733 success: total_errors == 0,
734 execution_time: Duration::default(),
735 peak_memory_mb: 100,
736 avg_cpu_usage: 0.4,
737 throughput: edge_cases.len() as f64,
738 error_count: total_errors,
739 performance_degradation: 1.1,
740 resource_stats: ResourceUsageStats::default(),
741 issues: Vec::new(),
742 metrics: HashMap::from([("edge_cases_tested".to_string(), edge_cases.len() as f64)]),
743 })
744 }
745
746 fn detect_issues(&self, result: &StressTestResult) -> Vec<StressTestIssue> {
748 let mut issues = Vec::new();
749
750 if result.performance_degradation > self.config.performance_threshold {
752 issues.push(StressTestIssue::PerformanceDegradation {
753 baseline_time: Duration::from_secs(1), actual_time: result.execution_time,
755 degradation_factor: result.performance_degradation,
756 });
757 }
758
759 if self.config.detect_memory_leaks && result.peak_memory_mb > 1000 {
761 issues.push(StressTestIssue::MemoryLeak {
762 initial_memory: 100,
763 final_memory: result.peak_memory_mb,
764 leak_rate_mb_per_sec: (result.peak_memory_mb - 100) as f64
765 / result.execution_time.as_secs_f64(),
766 });
767 }
768
769 if result.peak_memory_mb > self.config.memory_limit_mb {
771 issues.push(StressTestIssue::ResourceExhaustion {
772 resource_type: "memory".to_string(),
773 limit: self.config.memory_limit_mb as f64,
774 peak_usage: result.peak_memory_mb as f64,
775 });
776 }
777
778 if result.error_count > 0 {
780 let error_rate = result.error_count as f64 / result.throughput;
781 if error_rate > self.config.error_tolerance {
782 issues.push(StressTestIssue::ErrorRateSpike {
783 baseline_error_rate: 0.0,
784 actual_error_rate: error_rate,
785 spike_factor: error_rate / self.config.error_tolerance,
786 });
787 }
788 }
789
790 issues
791 }
792
793 #[must_use]
795 pub fn generate_report(&self) -> StressTestReport {
796 let total_tests = self.results.len();
797 let successful_tests = self.results.iter().filter(|r| r.success).count();
798 let failed_tests = total_tests - successful_tests;
799
800 let avg_execution_time = if self.results.is_empty() {
801 0.0
802 } else {
803 self.results
804 .iter()
805 .map(|r| r.execution_time.as_secs_f64())
806 .sum::<f64>()
807 / self.results.len() as f64
808 };
809
810 let peak_memory_usage = self
811 .results
812 .iter()
813 .map(|r| r.peak_memory_mb)
814 .max()
815 .unwrap_or(0);
816
817 let all_issues: Vec<_> = self
818 .results
819 .iter()
820 .flat_map(|r| r.issues.iter().cloned())
821 .collect();
822
823 StressTestReport {
824 timestamp: Utc::now(),
825 config: self.config.clone(),
826 total_tests,
827 successful_tests,
828 failed_tests,
829 avg_execution_time: Duration::from_secs_f64(avg_execution_time),
830 peak_memory_usage,
831 detected_issues: all_issues,
832 results: self.results.clone(),
833 recommendations: self.generate_recommendations(),
834 }
835 }
836
837 fn generate_recommendations(&self) -> Vec<String> {
839 let mut recommendations = Vec::new();
840
841 let performance_issues = self
843 .results
844 .iter()
845 .filter(|r| r.performance_degradation > self.config.performance_threshold)
846 .count();
847
848 if performance_issues > 0 {
849 recommendations.push(format!(
850 "Performance degradation detected in {performance_issues} tests. Consider optimizing algorithms or increasing resources."
851 ));
852 }
853
854 let high_memory_tests = self
856 .results
857 .iter()
858 .filter(|r| r.peak_memory_mb > self.config.memory_limit_mb)
859 .count();
860
861 if high_memory_tests > 0 {
862 recommendations.push(format!(
863 "Memory limit exceeded in {high_memory_tests} tests. Consider implementing memory optimization strategies."
864 ));
865 }
866
867 let error_tests = self.results.iter().filter(|r| r.error_count > 0).count();
869
870 if error_tests > 0 {
871 recommendations.push(format!(
872 "Errors detected in {error_tests} tests. Review error handling and edge case management."
873 ));
874 }
875
876 if recommendations.is_empty() {
877 recommendations.push(
878 "All stress tests passed successfully. Pipeline shows good stability under load."
879 .to_string(),
880 );
881 }
882
883 recommendations
884 }
885}
886
887impl Default for ResourceUsageStats {
888 fn default() -> Self {
889 Self {
890 memory: ResourceStats::default(),
891 cpu: ResourceStats::default(),
892 max_threads: 1,
893 io_operations: 0,
894 }
895 }
896}
897
898#[derive(Debug, Clone, Serialize, Deserialize)]
900pub struct StressTestReport {
901 pub timestamp: DateTime<Utc>,
902 pub config: StressTestConfig,
903 pub total_tests: usize,
904 pub successful_tests: usize,
905 pub failed_tests: usize,
906 pub avg_execution_time: Duration,
907 pub peak_memory_usage: u64,
908 pub detected_issues: Vec<StressTestIssue>,
909 pub results: Vec<StressTestResult>,
910 pub recommendations: Vec<String>,
911}
912
913#[allow(non_snake_case)]
914#[cfg(test)]
915mod tests {
916 use super::*;
917 use scirs2_core::ndarray::Array2;
918 use sklears_core::error::SklearsError;
919
920 struct MockEstimator;
922
923 impl Estimator for MockEstimator {
924 type Config = ();
925 type Error = SklearsError;
926 type Float = f64;
927
928 fn config(&self) -> &Self::Config {
929 &()
930 }
931 }
932
933 #[test]
934 fn test_stress_tester_creation() {
935 let config = StressTestConfig::default();
936 let tester = StressTester::new(config);
937 assert_eq!(tester.scenarios.len(), 0);
938 assert_eq!(tester.results.len(), 0);
939 }
940
941 #[test]
942 fn test_add_scenario() {
943 let config = StressTestConfig::default();
944 let mut tester = StressTester::new(config);
945
946 let scenario = StressTestScenario::HighVolumeData {
947 scale_factor: 10.0,
948 batch_size: 1000,
949 };
950
951 tester.add_scenario(scenario);
952 assert_eq!(tester.scenarios.len(), 1);
953 }
954
955 #[test]
956 fn test_high_volume_data_scenario() {
957 let config = StressTestConfig::default();
958 let tester = StressTester::new(config);
959 let estimator = MockEstimator;
960
961 let result = tester.test_high_volume_data(&estimator, 5.0, 1000).unwrap();
962 assert!(result.success);
963 assert_eq!(result.performance_degradation, 5.0);
964 }
965
966 #[test]
967 fn test_resource_monitor() {
968 let monitor = ResourceMonitor::new();
969
970 monitor.start_monitoring(Duration::from_millis(1));
971 thread::sleep(Duration::from_millis(10));
972 monitor.stop_monitoring();
973
974 let memory_stats = monitor.get_memory_stats();
975 assert!(memory_stats.min >= 0.0);
976 }
977
978 #[test]
979 fn test_edge_case_handling() {
980 let config = StressTestConfig::default();
981 let tester = StressTester::new(config);
982 let estimator = MockEstimator;
983
984 let edge_cases = vec![
985 EdgeCase::EmptyData,
986 EdgeCase::SingleSample,
987 EdgeCase::NumericalEdges,
988 ];
989
990 let result = tester.test_edge_cases(&estimator, &edge_cases).unwrap();
991 assert!(result.success);
992 assert_eq!(result.error_count, 0);
993 }
994
995 #[test]
996 fn test_issue_detection() {
997 let config = StressTestConfig {
998 performance_threshold: 2.0,
999 memory_limit_mb: 500,
1000 ..Default::default()
1001 };
1002 let tester = StressTester::new(config);
1003
1004 let result = StressTestResult {
1005 scenario: StressTestScenario::HighVolumeData {
1006 scale_factor: 1.0,
1007 batch_size: 100,
1008 },
1009 success: true,
1010 execution_time: Duration::from_secs(5),
1011 peak_memory_mb: 1000, avg_cpu_usage: 0.8,
1013 throughput: 100.0,
1014 error_count: 0,
1015 performance_degradation: 3.0, resource_stats: ResourceUsageStats::default(),
1017 issues: Vec::new(),
1018 metrics: HashMap::new(),
1019 };
1020
1021 let issues = tester.detect_issues(&result);
1022 assert!(!issues.is_empty());
1023
1024 let has_performance_issue = issues
1026 .iter()
1027 .any(|issue| matches!(issue, StressTestIssue::PerformanceDegradation { .. }));
1028 let has_resource_issue = issues
1029 .iter()
1030 .any(|issue| matches!(issue, StressTestIssue::ResourceExhaustion { .. }));
1031
1032 assert!(has_performance_issue);
1033 assert!(has_resource_issue);
1034 }
1035
1036 #[test]
1037 fn test_generate_report() {
1038 let config = StressTestConfig::default();
1039 let mut tester = StressTester::new(config);
1040
1041 tester.results.push(StressTestResult {
1043 scenario: StressTestScenario::HighVolumeData {
1044 scale_factor: 1.0,
1045 batch_size: 100,
1046 },
1047 success: true,
1048 execution_time: Duration::from_secs(2),
1049 peak_memory_mb: 200,
1050 avg_cpu_usage: 0.5,
1051 throughput: 100.0,
1052 error_count: 0,
1053 performance_degradation: 1.0,
1054 resource_stats: ResourceUsageStats::default(),
1055 issues: Vec::new(),
1056 metrics: HashMap::new(),
1057 });
1058
1059 let report = tester.generate_report();
1060 assert_eq!(report.total_tests, 1);
1061 assert_eq!(report.successful_tests, 1);
1062 assert_eq!(report.failed_tests, 0);
1063 assert!(!report.recommendations.is_empty());
1064 }
1065}