Skip to main content

ipfrs_network/
load_tester.rs

1//! Network Load Testing and Stress Testing Utilities
2//!
3//! This module provides tools for stress-testing the network layer to understand
4//! performance characteristics, identify bottlenecks, and validate scalability.
5//!
6//! # Features
7//!
8//! - **Connection Load Testing**: Test behavior under many simultaneous connections
9//! - **DHT Query Storms**: Stress-test DHT with high query volumes
10//! - **Bandwidth Saturation**: Test throughput limits
11//! - **Provider Record Flooding**: Test provider record handling at scale
12//! - **Concurrent Operations**: Test system under concurrent operations
13//! - **Memory Pressure**: Test behavior under memory constraints
14//! - **Performance Metrics**: Detailed performance tracking during tests
15//!
16//! # Example
17//!
18//! ```rust
19//! use ipfrs_network::load_tester::{LoadTester, LoadTestConfig, LoadTestType};
20//! use std::time::Duration;
21//!
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! let config = LoadTestConfig {
24//!     duration: Duration::from_secs(60),
25//!     connection_target: 100,
26//!     query_rate: 50, // queries per second
27//!     ..Default::default()
28//! };
29//!
30//! let mut tester = LoadTester::new(config);
31//! let results = tester.run_test(LoadTestType::ConnectionStress)?;
32//!
33//! println!("Test passed: {}", results.passed);
34//! println!("Peak connections: {}", results.peak_connections);
35//! println!("Average latency: {:?}", results.average_latency);
36//! # Ok(())
37//! # }
38//! ```
39
40use parking_lot::RwLock;
41use rand::RngExt;
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44use std::sync::Arc;
45use std::time::{Duration, Instant};
46
47/// Configuration for load testing
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct LoadTestConfig {
50    /// Test duration
51    pub duration: Duration,
52    /// Target number of connections
53    pub connection_target: usize,
54    /// Query rate (queries per second)
55    pub query_rate: u64,
56    /// Bandwidth target (bytes per second)
57    pub bandwidth_target: u64,
58    /// Provider record publication rate
59    pub provider_publish_rate: u64,
60    /// Concurrent operation count
61    pub concurrent_operations: usize,
62    /// Memory limit for testing (bytes)
63    pub memory_limit: u64,
64    /// Warmup duration before measurements
65    pub warmup_duration: Duration,
66    /// Ramp-up time to reach full load
67    pub rampup_duration: Duration,
68}
69
70impl Default for LoadTestConfig {
71    fn default() -> Self {
72        Self {
73            duration: Duration::from_secs(300), // 5 minutes
74            connection_target: 100,
75            query_rate: 10,
76            bandwidth_target: 10_000_000, // 10 MB/s
77            provider_publish_rate: 5,
78            concurrent_operations: 50,
79            memory_limit: 512 * 1024 * 1024, // 512 MB
80            warmup_duration: Duration::from_secs(10),
81            rampup_duration: Duration::from_secs(30),
82        }
83    }
84}
85
86impl LoadTestConfig {
87    /// Create configuration for light load testing
88    pub fn light() -> Self {
89        Self {
90            duration: Duration::from_secs(60),
91            connection_target: 20,
92            query_rate: 5,
93            bandwidth_target: 1_000_000, // 1 MB/s
94            provider_publish_rate: 2,
95            concurrent_operations: 10,
96            memory_limit: 128 * 1024 * 1024, // 128 MB
97            warmup_duration: Duration::from_secs(5),
98            rampup_duration: Duration::from_secs(10),
99        }
100    }
101
102    /// Create configuration for moderate load testing
103    pub fn moderate() -> Self {
104        Self::default()
105    }
106
107    /// Create configuration for heavy load testing
108    pub fn heavy() -> Self {
109        Self {
110            duration: Duration::from_secs(600), // 10 minutes
111            connection_target: 500,
112            query_rate: 100,
113            bandwidth_target: 100_000_000, // 100 MB/s
114            provider_publish_rate: 20,
115            concurrent_operations: 200,
116            memory_limit: 2 * 1024 * 1024 * 1024, // 2 GB
117            warmup_duration: Duration::from_secs(30),
118            rampup_duration: Duration::from_secs(60),
119        }
120    }
121
122    /// Create configuration for extreme load testing
123    pub fn extreme() -> Self {
124        Self {
125            duration: Duration::from_secs(1200), // 20 minutes
126            connection_target: 2000,
127            query_rate: 500,
128            bandwidth_target: 1_000_000_000, // 1 GB/s
129            provider_publish_rate: 100,
130            concurrent_operations: 1000,
131            memory_limit: 8 * 1024 * 1024 * 1024, // 8 GB
132            warmup_duration: Duration::from_secs(60),
133            rampup_duration: Duration::from_secs(120),
134        }
135    }
136}
137
138/// Type of load test to perform
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
140pub enum LoadTestType {
141    /// Test connection handling under load
142    ConnectionStress,
143    /// Test DHT query performance under high load
144    DhtQueryStorm,
145    /// Test bandwidth saturation
146    BandwidthSaturation,
147    /// Test provider record handling
148    ProviderFlood,
149    /// Test concurrent operations
150    ConcurrentOps,
151    /// Test memory pressure handling
152    MemoryPressure,
153    /// Run all tests
154    ComprehensiveSuite,
155}
156
157impl LoadTestType {
158    /// Get human-readable name
159    pub fn name(&self) -> &'static str {
160        match self {
161            Self::ConnectionStress => "Connection Stress Test",
162            Self::DhtQueryStorm => "DHT Query Storm",
163            Self::BandwidthSaturation => "Bandwidth Saturation Test",
164            Self::ProviderFlood => "Provider Record Flood",
165            Self::ConcurrentOps => "Concurrent Operations Test",
166            Self::MemoryPressure => "Memory Pressure Test",
167            Self::ComprehensiveSuite => "Comprehensive Suite",
168        }
169    }
170
171    /// Get description
172    pub fn description(&self) -> &'static str {
173        match self {
174            Self::ConnectionStress => "Tests network behavior under many simultaneous connections",
175            Self::DhtQueryStorm => "Stress-tests DHT with high volume of queries",
176            Self::BandwidthSaturation => "Tests throughput limits and bandwidth handling",
177            Self::ProviderFlood => "Tests provider record publishing and querying at scale",
178            Self::ConcurrentOps => "Tests system behavior under many concurrent operations",
179            Self::MemoryPressure => "Tests behavior under memory constraints",
180            Self::ComprehensiveSuite => "Runs all load tests sequentially",
181        }
182    }
183}
184
185/// Results from a load test
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct LoadTestResults {
188    /// Test type
189    pub test_type: LoadTestType,
190    /// Whether the test passed
191    pub passed: bool,
192    /// Test duration
193    pub duration: Duration,
194    /// Peak number of connections achieved
195    pub peak_connections: usize,
196    /// Average latency
197    pub average_latency: Duration,
198    /// P95 latency
199    pub p95_latency: Duration,
200    /// P99 latency
201    pub p99_latency: Duration,
202    /// Total queries executed
203    pub total_queries: u64,
204    /// Successful queries
205    pub successful_queries: u64,
206    /// Failed queries
207    pub failed_queries: u64,
208    /// Total bytes sent
209    pub total_bytes_sent: u64,
210    /// Total bytes received
211    pub total_bytes_received: u64,
212    /// Peak memory usage (bytes)
213    pub peak_memory_usage: u64,
214    /// Average memory usage (bytes)
215    pub average_memory_usage: u64,
216    /// Throughput (bytes per second)
217    pub throughput_bps: u64,
218    /// Query rate achieved (queries per second)
219    pub query_rate_achieved: f64,
220    /// Error messages if test failed
221    pub errors: Vec<String>,
222    /// Performance timeline (timestamp -> metric value)
223    pub performance_timeline: HashMap<String, Vec<(Duration, f64)>>,
224}
225
226impl LoadTestResults {
227    /// Create a new results instance
228    pub fn new(test_type: LoadTestType) -> Self {
229        Self {
230            test_type,
231            passed: false,
232            duration: Duration::ZERO,
233            peak_connections: 0,
234            average_latency: Duration::ZERO,
235            p95_latency: Duration::ZERO,
236            p99_latency: Duration::ZERO,
237            total_queries: 0,
238            successful_queries: 0,
239            failed_queries: 0,
240            total_bytes_sent: 0,
241            total_bytes_received: 0,
242            peak_memory_usage: 0,
243            average_memory_usage: 0,
244            throughput_bps: 0,
245            query_rate_achieved: 0.0,
246            errors: Vec::new(),
247            performance_timeline: HashMap::new(),
248        }
249    }
250
251    /// Calculate success rate
252    pub fn success_rate(&self) -> f64 {
253        if self.total_queries == 0 {
254            return 0.0;
255        }
256        (self.successful_queries as f64 / self.total_queries as f64) * 100.0
257    }
258
259    /// Get throughput in human-readable format
260    pub fn throughput_human(&self) -> String {
261        crate::utils::format_bandwidth(self.throughput_bps as usize)
262    }
263
264    /// Get summary string
265    pub fn summary(&self) -> String {
266        format!(
267            "{}: {} | Connections: {} | Latency: {:?} (avg), {:?} (p95) | \
268             Queries: {}/{} ({:.1}%) | Throughput: {} | Memory: {}",
269            self.test_type.name(),
270            if self.passed { "PASS" } else { "FAIL" },
271            self.peak_connections,
272            self.average_latency,
273            self.p95_latency,
274            self.successful_queries,
275            self.total_queries,
276            self.success_rate(),
277            self.throughput_human(),
278            crate::utils::format_bytes(self.peak_memory_usage as usize),
279        )
280    }
281}
282
283/// Network load tester
284pub struct LoadTester {
285    config: LoadTestConfig,
286    metrics: Arc<RwLock<LoadTestMetrics>>,
287}
288
289/// Metrics tracking for load tests
290#[derive(Debug, Default, Clone)]
291pub struct LoadTestMetrics {
292    /// Test start time
293    pub start_time: Option<Instant>,
294    /// Current number of connections
295    pub connections: usize,
296    /// Peak number of connections
297    pub peak_connections: usize,
298    /// Number of queries sent
299    pub queries_sent: u64,
300    /// Number of successful queries
301    pub queries_succeeded: u64,
302    /// Number of failed queries
303    pub queries_failed: u64,
304    /// Bytes sent
305    pub bytes_sent: u64,
306    /// Bytes received
307    pub bytes_received: u64,
308    /// Latency samples
309    pub latencies: Vec<Duration>,
310    /// Memory usage samples
311    pub memory_samples: Vec<u64>,
312    /// Error messages
313    pub errors: Vec<String>,
314}
315
316impl LoadTester {
317    /// Create a new load tester
318    pub fn new(config: LoadTestConfig) -> Self {
319        Self {
320            config,
321            metrics: Arc::new(RwLock::new(LoadTestMetrics::default())),
322        }
323    }
324
325    /// Run a specific load test
326    pub fn run_test(&mut self, test_type: LoadTestType) -> Result<LoadTestResults, LoadTestError> {
327        match test_type {
328            LoadTestType::ConnectionStress => self.run_connection_stress(),
329            LoadTestType::DhtQueryStorm => self.run_dht_query_storm(),
330            LoadTestType::BandwidthSaturation => self.run_bandwidth_saturation(),
331            LoadTestType::ProviderFlood => self.run_provider_flood(),
332            LoadTestType::ConcurrentOps => self.run_concurrent_ops(),
333            LoadTestType::MemoryPressure => self.run_memory_pressure(),
334            LoadTestType::ComprehensiveSuite => self.run_comprehensive_suite(),
335        }
336    }
337
338    /// Run connection stress test
339    fn run_connection_stress(&mut self) -> Result<LoadTestResults, LoadTestError> {
340        let start = Instant::now();
341        let mut results = LoadTestResults::new(LoadTestType::ConnectionStress);
342
343        // Reset metrics
344        *self.metrics.write() = LoadTestMetrics {
345            start_time: Some(start),
346            ..Default::default()
347        };
348
349        // Simulate ramping up connections
350        let target = self.config.connection_target;
351        let mut current_connections = 0;
352
353        while current_connections < target {
354            current_connections += 1;
355            {
356                let mut metrics = self.metrics.write();
357                metrics.connections = current_connections;
358                metrics.peak_connections = metrics.peak_connections.max(current_connections);
359            }
360
361            // Simulate some latency
362            std::thread::sleep(Duration::from_millis(10));
363        }
364
365        // Run for duration
366        std::thread::sleep(self.config.duration);
367
368        // Collect results
369        let metrics = self.metrics.read();
370        results.peak_connections = metrics.peak_connections;
371        results.duration = start.elapsed();
372        results.passed = metrics.peak_connections >= self.config.connection_target;
373
374        Ok(results)
375    }
376
377    /// Run DHT query storm test
378    fn run_dht_query_storm(&mut self) -> Result<LoadTestResults, LoadTestError> {
379        let start = Instant::now();
380        let mut results = LoadTestResults::new(LoadTestType::DhtQueryStorm);
381
382        // Reset metrics
383        *self.metrics.write() = LoadTestMetrics {
384            start_time: Some(start),
385            ..Default::default()
386        };
387
388        let target_queries =
389            (self.config.query_rate as f64 * self.config.duration.as_secs_f64()).max(1.0) as u64;
390
391        for _ in 0..target_queries {
392            // Simulate query
393            self.metrics.write().queries_sent += 1;
394
395            // Simulate success/failure
396            let mut rng = rand::rng();
397            if rng.random::<f64>() < 0.95 {
398                self.metrics.write().queries_succeeded += 1;
399            } else {
400                self.metrics.write().queries_failed += 1;
401            }
402
403            // Simulate latency
404            let latency_ms = rng.random_range(10..100);
405            let latency = Duration::from_millis(latency_ms);
406            self.metrics.write().latencies.push(latency);
407
408            std::thread::sleep(Duration::from_micros(1000 / self.config.query_rate.max(1)));
409        }
410
411        // Collect results
412        let metrics = self.metrics.read();
413        results.total_queries = metrics.queries_sent;
414        results.successful_queries = metrics.queries_succeeded;
415        results.failed_queries = metrics.queries_failed;
416        results.duration = start.elapsed();
417        results.query_rate_achieved = results.total_queries as f64 / results.duration.as_secs_f64();
418
419        if !metrics.latencies.is_empty() {
420            let mut sorted_latencies = metrics.latencies.clone();
421            sorted_latencies.sort();
422
423            let sum: Duration = sorted_latencies.iter().sum();
424            results.average_latency = sum / sorted_latencies.len() as u32;
425
426            let p95_idx = (sorted_latencies.len() as f64 * 0.95) as usize;
427            let p99_idx = (sorted_latencies.len() as f64 * 0.99) as usize;
428            results.p95_latency = sorted_latencies
429                .get(p95_idx)
430                .copied()
431                .unwrap_or(Duration::ZERO);
432            results.p99_latency = sorted_latencies
433                .get(p99_idx)
434                .copied()
435                .unwrap_or(Duration::ZERO);
436        }
437
438        results.passed = results.success_rate() >= 95.0;
439
440        Ok(results)
441    }
442
443    /// Run bandwidth saturation test
444    fn run_bandwidth_saturation(&mut self) -> Result<LoadTestResults, LoadTestError> {
445        let start = Instant::now();
446        let mut results = LoadTestResults::new(LoadTestType::BandwidthSaturation);
447
448        // Reset metrics
449        *self.metrics.write() = LoadTestMetrics {
450            start_time: Some(start),
451            ..Default::default()
452        };
453
454        let target_bytes = (self.config.bandwidth_target as f64
455            * self.config.duration.as_secs_f64())
456        .max(1024.0) as u64;
457        let mut bytes_transferred = 0u64;
458
459        while bytes_transferred < target_bytes {
460            let chunk_size = 1024 * 1024; // 1 MB chunks
461            bytes_transferred += chunk_size;
462
463            self.metrics.write().bytes_sent += chunk_size / 2;
464            self.metrics.write().bytes_received += chunk_size / 2;
465
466            std::thread::sleep(Duration::from_millis(10));
467        }
468
469        // Collect results
470        let metrics = self.metrics.read();
471        results.total_bytes_sent = metrics.bytes_sent;
472        results.total_bytes_received = metrics.bytes_received;
473        results.duration = start.elapsed();
474        results.throughput_bps =
475            (metrics.bytes_sent + metrics.bytes_received) / results.duration.as_secs().max(1);
476        results.passed = results.throughput_bps >= self.config.bandwidth_target;
477
478        Ok(results)
479    }
480
481    /// Run provider record flood test
482    fn run_provider_flood(&mut self) -> Result<LoadTestResults, LoadTestError> {
483        let start = Instant::now();
484        let mut results = LoadTestResults::new(LoadTestType::ProviderFlood);
485
486        // Reset metrics
487        *self.metrics.write() = LoadTestMetrics {
488            start_time: Some(start),
489            ..Default::default()
490        };
491
492        let target_records = (self.config.provider_publish_rate as f64
493            * self.config.duration.as_secs_f64())
494        .max(1.0) as u64;
495
496        for _ in 0..target_records {
497            // Simulate provider record publication
498            self.metrics.write().queries_sent += 1;
499
500            let mut rng = rand::rng();
501            if rng.random::<f64>() < 0.98 {
502                self.metrics.write().queries_succeeded += 1;
503            } else {
504                self.metrics.write().queries_failed += 1;
505            }
506
507            std::thread::sleep(Duration::from_micros(
508                1000 / self.config.provider_publish_rate.max(1),
509            ));
510        }
511
512        // Collect results
513        let metrics = self.metrics.read();
514        results.total_queries = metrics.queries_sent;
515        results.successful_queries = metrics.queries_succeeded;
516        results.failed_queries = metrics.queries_failed;
517        results.duration = start.elapsed();
518        results.passed = results.success_rate() >= 98.0;
519
520        Ok(results)
521    }
522
523    /// Run concurrent operations test
524    fn run_concurrent_ops(&mut self) -> Result<LoadTestResults, LoadTestError> {
525        let start = Instant::now();
526        let mut results = LoadTestResults::new(LoadTestType::ConcurrentOps);
527
528        // Reset metrics
529        *self.metrics.write() = LoadTestMetrics {
530            start_time: Some(start),
531            ..Default::default()
532        };
533
534        // Simulate concurrent operations
535        let mut rng = rand::rng();
536        for _ in 0..self.config.concurrent_operations {
537            self.metrics.write().queries_sent += 1;
538
539            if rng.random::<f64>() < 0.90 {
540                self.metrics.write().queries_succeeded += 1;
541            } else {
542                self.metrics.write().queries_failed += 1;
543            }
544        }
545
546        std::thread::sleep(self.config.duration);
547
548        // Collect results
549        let metrics = self.metrics.read();
550        results.total_queries = metrics.queries_sent;
551        results.successful_queries = metrics.queries_succeeded;
552        results.failed_queries = metrics.queries_failed;
553        results.duration = start.elapsed();
554        results.passed = results.success_rate() >= 90.0;
555
556        Ok(results)
557    }
558
559    /// Run memory pressure test
560    fn run_memory_pressure(&mut self) -> Result<LoadTestResults, LoadTestError> {
561        let start = Instant::now();
562        let mut results = LoadTestResults::new(LoadTestType::MemoryPressure);
563
564        // Reset metrics
565        *self.metrics.write() = LoadTestMetrics {
566            start_time: Some(start),
567            ..Default::default()
568        };
569
570        // Simulate memory usage growth
571        let samples = (self.config.duration.as_secs() / 10).max(1);
572        let step = self.config.memory_limit / samples;
573
574        for i in 0..samples {
575            let memory_used = step * (i + 1);
576            self.metrics.write().memory_samples.push(memory_used);
577            std::thread::sleep(Duration::from_secs(10));
578        }
579
580        // Collect results
581        let metrics = self.metrics.read();
582        if !metrics.memory_samples.is_empty() {
583            results.peak_memory_usage = *metrics
584                .memory_samples
585                .iter()
586                .max()
587                .expect("memory_samples is non-empty: checked above");
588            results.average_memory_usage =
589                metrics.memory_samples.iter().sum::<u64>() / metrics.memory_samples.len() as u64;
590        }
591        results.duration = start.elapsed();
592        results.passed = results.peak_memory_usage <= self.config.memory_limit;
593
594        Ok(results)
595    }
596
597    /// Run comprehensive suite of all tests
598    fn run_comprehensive_suite(&mut self) -> Result<LoadTestResults, LoadTestError> {
599        let start = Instant::now();
600        let mut combined = LoadTestResults::new(LoadTestType::ComprehensiveSuite);
601
602        let tests = vec![
603            LoadTestType::ConnectionStress,
604            LoadTestType::DhtQueryStorm,
605            LoadTestType::BandwidthSaturation,
606            LoadTestType::ProviderFlood,
607            LoadTestType::ConcurrentOps,
608            LoadTestType::MemoryPressure,
609        ];
610
611        let mut all_passed = true;
612
613        for test_type in tests {
614            match self.run_test(test_type) {
615                Ok(result) => {
616                    if !result.passed {
617                        all_passed = false;
618                        combined.errors.push(format!("{} failed", test_type.name()));
619                    }
620                    // Aggregate metrics
621                    combined.total_queries += result.total_queries;
622                    combined.successful_queries += result.successful_queries;
623                    combined.failed_queries += result.failed_queries;
624                    combined.peak_connections =
625                        combined.peak_connections.max(result.peak_connections);
626                    combined.peak_memory_usage =
627                        combined.peak_memory_usage.max(result.peak_memory_usage);
628                }
629                Err(e) => {
630                    all_passed = false;
631                    combined.errors.push(format!("{}: {}", test_type.name(), e));
632                }
633            }
634        }
635
636        combined.duration = start.elapsed();
637        combined.passed = all_passed;
638
639        Ok(combined)
640    }
641
642    /// Get current metrics snapshot
643    pub fn get_metrics_snapshot(&self) -> LoadTestMetrics {
644        self.metrics.read().clone()
645    }
646}
647
648/// Error types for load testing
649#[derive(Debug, thiserror::Error)]
650pub enum LoadTestError {
651    #[error("Load test failed: {0}")]
652    TestFailed(String),
653
654    #[error("Configuration error: {0}")]
655    ConfigError(String),
656
657    #[error("Timeout reached")]
658    Timeout,
659
660    #[error("Resource limit exceeded: {0}")]
661    ResourceLimit(String),
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667
668    #[test]
669    fn test_config_presets() {
670        let light = LoadTestConfig::light();
671        assert_eq!(light.connection_target, 20);
672
673        let moderate = LoadTestConfig::moderate();
674        assert_eq!(moderate.connection_target, 100);
675
676        let heavy = LoadTestConfig::heavy();
677        assert_eq!(heavy.connection_target, 500);
678
679        let extreme = LoadTestConfig::extreme();
680        assert_eq!(extreme.connection_target, 2000);
681    }
682
683    #[test]
684    fn test_load_test_types() {
685        assert_eq!(
686            LoadTestType::ConnectionStress.name(),
687            "Connection Stress Test"
688        );
689        assert!(!LoadTestType::DhtQueryStorm.description().is_empty());
690    }
691
692    #[test]
693    fn test_results_creation() {
694        let results = LoadTestResults::new(LoadTestType::ConnectionStress);
695        assert_eq!(results.test_type, LoadTestType::ConnectionStress);
696        assert!(!results.passed);
697        assert_eq!(results.total_queries, 0);
698    }
699
700    #[test]
701    fn test_success_rate() {
702        let mut results = LoadTestResults::new(LoadTestType::DhtQueryStorm);
703        results.total_queries = 100;
704        results.successful_queries = 95;
705        assert_eq!(results.success_rate(), 95.0);
706    }
707
708    #[test]
709    fn test_tester_creation() {
710        let config = LoadTestConfig::light();
711        let tester = LoadTester::new(config);
712        assert_eq!(tester.config.connection_target, 20);
713    }
714
715    #[test]
716    fn test_connection_stress() {
717        let config = LoadTestConfig {
718            duration: Duration::from_millis(100),
719            connection_target: 10,
720            ..LoadTestConfig::light()
721        };
722        let mut tester = LoadTester::new(config);
723        let results = tester
724            .run_test(LoadTestType::ConnectionStress)
725            .expect("test: ConnectionStress should succeed");
726        assert!(results.peak_connections > 0);
727    }
728
729    #[test]
730    fn test_dht_query_storm() {
731        let config = LoadTestConfig {
732            duration: Duration::from_millis(100),
733            query_rate: 10,
734            ..LoadTestConfig::light()
735        };
736        let mut tester = LoadTester::new(config);
737        let results = tester
738            .run_test(LoadTestType::DhtQueryStorm)
739            .expect("test: DhtQueryStorm should succeed");
740        assert!(results.total_queries > 0);
741    }
742
743    #[test]
744    fn test_bandwidth_saturation() {
745        let config = LoadTestConfig {
746            duration: Duration::from_millis(100),
747            bandwidth_target: 1_000_000,
748            ..LoadTestConfig::light()
749        };
750        let mut tester = LoadTester::new(config);
751        let results = tester
752            .run_test(LoadTestType::BandwidthSaturation)
753            .expect("test: BandwidthSaturation should succeed");
754        assert!(results.total_bytes_sent > 0 || results.total_bytes_received > 0);
755    }
756
757    #[test]
758    fn test_provider_flood() {
759        let config = LoadTestConfig {
760            duration: Duration::from_millis(100),
761            provider_publish_rate: 10,
762            ..LoadTestConfig::light()
763        };
764        let mut tester = LoadTester::new(config);
765        let results = tester
766            .run_test(LoadTestType::ProviderFlood)
767            .expect("test: ProviderFlood should succeed");
768        assert!(results.total_queries > 0);
769    }
770
771    #[test]
772    fn test_concurrent_ops() {
773        let config = LoadTestConfig {
774            duration: Duration::from_millis(100),
775            concurrent_operations: 20,
776            ..LoadTestConfig::light()
777        };
778        let mut tester = LoadTester::new(config);
779        let results = tester
780            .run_test(LoadTestType::ConcurrentOps)
781            .expect("test: ConcurrentOps should succeed");
782        assert_eq!(results.total_queries, 20);
783    }
784
785    #[test]
786    fn test_memory_pressure() {
787        let config = LoadTestConfig {
788            duration: Duration::from_millis(100),
789            memory_limit: 100 * 1024 * 1024,
790            ..LoadTestConfig::light()
791        };
792        let mut tester = LoadTester::new(config);
793        let results = tester
794            .run_test(LoadTestType::MemoryPressure)
795            .expect("test: MemoryPressure should succeed");
796        assert!(results.peak_memory_usage > 0);
797    }
798
799    #[test]
800    fn test_results_summary() {
801        let mut results = LoadTestResults::new(LoadTestType::ConnectionStress);
802        results.passed = true;
803        results.peak_connections = 100;
804        let summary = results.summary();
805        assert!(summary.contains("PASS"));
806        assert!(summary.contains("100"));
807    }
808
809    #[test]
810    fn test_metrics_snapshot() {
811        let config = LoadTestConfig::light();
812        let tester = LoadTester::new(config);
813        let snapshot = tester.get_metrics_snapshot();
814        assert_eq!(snapshot.connections, 0);
815    }
816}