Skip to main content

shadow_load_testing/
report.rs

1//! Load test reporting
2
3use serde::{Serialize, Deserialize};
4use std::time::Duration;
5
6/// Complete load test report
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LoadTestReport {
9    pub peer_count: usize,
10    pub results: Vec<LoadTestResult>,
11    pub timestamp: String,
12}
13
14/// Individual test result
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct LoadTestResult {
17    pub name: String,
18    pub duration_ms: u64,
19    pub passed: bool,
20}
21
22impl LoadTestReport {
23    pub fn new(peer_count: usize) -> Self {
24        LoadTestReport {
25            peer_count,
26            results: Vec::new(),
27            timestamp: chrono::Utc::now().to_rfc3339(),
28        }
29    }
30
31    pub fn record(&mut self, name: &str, duration: Duration, passed: bool) {
32        self.results.push(LoadTestResult {
33            name: name.to_string(),
34            duration_ms: duration.as_millis() as u64,
35            passed,
36        });
37    }
38
39    pub fn all_passed(&self) -> bool {
40        self.results.iter().all(|r| r.passed)
41    }
42
43    pub fn total_duration_ms(&self) -> u64 {
44        self.results.iter().map(|r| r.duration_ms).sum()
45    }
46
47    pub fn passed_count(&self) -> usize {
48        self.results.iter().filter(|r| r.passed).count()
49    }
50
51    pub fn failed_count(&self) -> usize {
52        self.results.iter().filter(|r| !r.passed).count()
53    }
54
55    pub fn summary(&self) -> String {
56        format!(
57            "Load Test Report ({})\n\
58             Peers: {}\n\
59             Tests: {} passed, {} failed, {} total\n\
60             Total Duration: {}ms\n\
61             Results:\n{}",
62            self.timestamp,
63            self.peer_count,
64            self.passed_count(),
65            self.failed_count(),
66            self.results.len(),
67            self.total_duration_ms(),
68            self.results
69                .iter()
70                .map(|r| {
71                    format!(
72                        "  {} - {} ({}ms)",
73                        if r.passed { "pass" } else { "FAIL" },
74                        r.name,
75                        r.duration_ms
76                    )
77                })
78                .collect::<Vec<_>>()
79                .join("\n")
80        )
81    }
82
83    pub fn to_json(&self) -> String {
84        serde_json::to_string_pretty(self).unwrap_or_default()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_report_creation() {
94        let mut report = LoadTestReport::new(10_000);
95        report.record("test_1", Duration::from_millis(100), true);
96        report.record("test_2", Duration::from_millis(200), true);
97        report.record("test_3", Duration::from_millis(50), false);
98        
99        assert_eq!(report.peer_count, 10_000);
100        assert_eq!(report.passed_count(), 2);
101        assert_eq!(report.failed_count(), 1);
102        assert_eq!(report.total_duration_ms(), 350);
103        assert!(!report.all_passed());
104    }
105
106    #[test]
107    fn test_report_all_passed() {
108        let mut report = LoadTestReport::new(100);
109        report.record("a", Duration::from_millis(10), true);
110        report.record("b", Duration::from_millis(20), true);
111        assert!(report.all_passed());
112    }
113
114    #[test]
115    fn test_report_summary() {
116        let mut report = LoadTestReport::new(5000);
117        report.record("dht_stress", Duration::from_millis(500), true);
118        let summary = report.summary();
119        assert!(summary.contains("5000"));
120        assert!(summary.contains("dht_stress"));
121    }
122
123    #[test]
124    fn test_report_json() {
125        let mut report = LoadTestReport::new(1000);
126        report.record("test", Duration::from_millis(100), true);
127        let json = report.to_json();
128        assert!(json.contains("peer_count"));
129        assert!(json.contains("1000"));
130    }
131}