1#![allow(clippy::must_use_candidate)]
15#![allow(clippy::missing_panics_doc)]
16#![allow(clippy::missing_errors_doc)]
17#![allow(clippy::module_name_repetitions)]
18#![allow(clippy::missing_const_for_fn)]
19#![allow(clippy::return_self_not_must_use)]
20#![allow(clippy::uninlined_format_args)]
21#![allow(clippy::format_push_string)]
22#![allow(clippy::doc_markdown)]
23#![allow(clippy::cast_precision_loss)]
24#![allow(clippy::items_after_statements)]
25#![allow(clippy::manual_saturating_arithmetic)]
26#![allow(clippy::use_self)]
27
28use serde::{Deserialize, Serialize};
29use std::time::{Duration, Instant};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
37pub enum StressMode {
38 #[default]
40 Atomics,
41 WorkerMsg,
43 Render,
45 Trace,
47 Full,
49}
50
51impl std::fmt::Display for StressMode {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Atomics => write!(f, "atomics"),
55 Self::WorkerMsg => write!(f, "worker-msg"),
56 Self::Render => write!(f, "render"),
57 Self::Trace => write!(f, "trace"),
58 Self::Full => write!(f, "full"),
59 }
60 }
61}
62
63impl std::str::FromStr for StressMode {
64 type Err = String;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 match s.to_lowercase().as_str() {
68 "atomics" => Ok(Self::Atomics),
69 "worker-msg" | "workermsg" | "worker_msg" => Ok(Self::WorkerMsg),
70 "render" => Ok(Self::Render),
71 "trace" => Ok(Self::Trace),
72 "full" => Ok(Self::Full),
73 _ => Err(format!("Unknown stress mode: {}", s)),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct StressConfig {
81 pub mode: StressMode,
83 pub duration_secs: u64,
85 pub concurrency: u32,
87 pub target_ops_per_sec: u64,
89 pub warmup_secs: u64,
91}
92
93impl Default for StressConfig {
94 fn default() -> Self {
95 Self {
96 mode: StressMode::Atomics,
97 duration_secs: 30,
98 concurrency: 4,
99 target_ops_per_sec: 0,
100 warmup_secs: 5,
101 }
102 }
103}
104
105impl StressConfig {
106 pub fn atomics(duration_secs: u64, concurrency: u32) -> Self {
108 Self {
109 mode: StressMode::Atomics,
110 duration_secs,
111 concurrency,
112 ..Default::default()
113 }
114 }
115
116 pub fn worker_msg(duration_secs: u64, concurrency: u32) -> Self {
118 Self {
119 mode: StressMode::WorkerMsg,
120 duration_secs,
121 concurrency,
122 ..Default::default()
123 }
124 }
125
126 pub fn render(duration_secs: u64) -> Self {
128 Self {
129 mode: StressMode::Render,
130 duration_secs,
131 concurrency: 1,
132 ..Default::default()
133 }
134 }
135
136 pub fn trace(duration_secs: u64) -> Self {
138 Self {
139 mode: StressMode::Trace,
140 duration_secs,
141 concurrency: 1,
142 ..Default::default()
143 }
144 }
145
146 pub fn full(duration_secs: u64, concurrency: u32) -> Self {
148 Self {
149 mode: StressMode::Full,
150 duration_secs,
151 concurrency,
152 ..Default::default()
153 }
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct StressResult {
164 pub mode: StressMode,
166 pub duration: Duration,
168 pub total_ops: u64,
170 pub ops_per_sec: f64,
172 pub passed: bool,
174 pub pass_criteria: String,
176 pub actual_value: String,
178 pub memory: MemoryStats,
180 pub latency: LatencyStats,
182 pub errors: Vec<StressError>,
184}
185
186impl StressResult {
187 pub fn new(mode: StressMode) -> Self {
189 Self {
190 mode,
191 duration: Duration::ZERO,
192 total_ops: 0,
193 ops_per_sec: 0.0,
194 passed: false,
195 pass_criteria: String::new(),
196 actual_value: String::new(),
197 memory: MemoryStats::default(),
198 latency: LatencyStats::default(),
199 errors: Vec::new(),
200 }
201 }
202
203 pub fn pass(mut self, criteria: &str, actual: &str) -> Self {
205 self.passed = true;
206 self.pass_criteria = criteria.to_string();
207 self.actual_value = actual.to_string();
208 self
209 }
210
211 pub fn fail(mut self, criteria: &str, actual: &str) -> Self {
213 self.passed = false;
214 self.pass_criteria = criteria.to_string();
215 self.actual_value = actual.to_string();
216 self
217 }
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
222pub struct MemoryStats {
223 pub initial_bytes: u64,
225 pub final_bytes: u64,
227 pub peak_bytes: u64,
229 pub stable: bool,
231}
232
233impl MemoryStats {
234 pub fn growth_percent(&self) -> f64 {
236 if self.initial_bytes == 0 {
237 return 0.0;
238 }
239 ((self.final_bytes as f64 - self.initial_bytes as f64) / self.initial_bytes as f64) * 100.0
240 }
241}
242
243#[derive(Debug, Clone, Default, Serialize, Deserialize)]
245pub struct LatencyStats {
246 pub min_us: u64,
248 pub max_us: u64,
250 pub mean_us: u64,
252 pub p50_us: u64,
254 pub p95_us: u64,
256 pub p99_us: u64,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct StressError {
263 pub kind: StressErrorKind,
265 pub message: String,
267 pub time_offset: Duration,
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
273pub enum StressErrorKind {
274 LockTimeout,
276 QueueOverflow,
278 FrameDrop,
280 OutOfMemory,
282 WorkerCrash,
284 Other,
286}
287
288impl std::fmt::Display for StressErrorKind {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match self {
291 Self::LockTimeout => write!(f, "Lock Timeout"),
292 Self::QueueOverflow => write!(f, "Queue Overflow"),
293 Self::FrameDrop => write!(f, "Frame Drop"),
294 Self::OutOfMemory => write!(f, "Out of Memory"),
295 Self::WorkerCrash => write!(f, "Worker Crash"),
296 Self::Other => write!(f, "Other"),
297 }
298 }
299}
300
301#[derive(Debug)]
307pub struct StressRunner {
308 config: StressConfig,
309}
310
311impl StressRunner {
312 pub fn new(config: StressConfig) -> Self {
314 Self { config }
315 }
316
317 pub fn run(&self) -> StressResult {
319 match self.config.mode {
320 StressMode::Atomics => self.run_atomics_stress(),
321 StressMode::WorkerMsg => self.run_worker_msg_stress(),
322 StressMode::Render => self.run_render_stress(),
323 StressMode::Trace => self.run_trace_stress(),
324 StressMode::Full => self.run_full_stress(),
325 }
326 }
327
328 fn run_atomics_stress(&self) -> StressResult {
331 let start = Instant::now();
332 let mut result = StressResult::new(StressMode::Atomics);
333
334 let mut ops: u64 = 0;
336 let target_duration = Duration::from_secs(self.config.duration_secs);
337
338 while start.elapsed() < target_duration {
340 for _ in 0..1000 {
342 ops += 1;
343 std::hint::black_box(ops.wrapping_mul(31));
345 }
346 }
347
348 let elapsed = start.elapsed();
349 result.duration = elapsed;
350 result.total_ops = ops;
351 result.ops_per_sec = ops as f64 / elapsed.as_secs_f64();
352
353 const PASS_THRESHOLD: f64 = 10_000.0;
355 let ops_per_sec = result.ops_per_sec;
356 let criteria = format!("atomics throughput > {} ops/sec", PASS_THRESHOLD);
357 let actual = format!("{:.0} ops/sec", ops_per_sec);
358 if ops_per_sec >= PASS_THRESHOLD {
359 result = result.pass(&criteria, &actual);
360 } else {
361 result = result.fail(&criteria, &actual);
362 }
363
364 result.memory.stable = true;
365 result
366 }
367
368 fn run_worker_msg_stress(&self) -> StressResult {
371 let start = Instant::now();
372 let mut result = StressResult::new(StressMode::WorkerMsg);
373
374 let mut messages: u64 = 0;
375 let target_duration = Duration::from_secs(self.config.duration_secs);
376
377 while start.elapsed() < target_duration {
379 for _ in 0..500 {
380 messages += 1;
381 let payload = std::hint::black_box(vec![0u8; 64]);
383 std::hint::black_box(payload.len());
384 }
385 }
386
387 let elapsed = start.elapsed();
388 result.duration = elapsed;
389 result.total_ops = messages;
390 result.ops_per_sec = messages as f64 / elapsed.as_secs_f64();
391
392 const PASS_THRESHOLD: f64 = 5_000.0;
394 let ops_per_sec = result.ops_per_sec;
395 let criteria = format!("message throughput > {} msg/sec", PASS_THRESHOLD);
396 let actual = format!("{:.0} msg/sec", ops_per_sec);
397 if ops_per_sec >= PASS_THRESHOLD {
398 result = result.pass(&criteria, &actual);
399 } else {
400 result = result.fail(&criteria, &actual);
401 }
402
403 result.memory.stable = true;
404 result
405 }
406
407 fn run_render_stress(&self) -> StressResult {
410 let start = Instant::now();
411 let mut result = StressResult::new(StressMode::Render);
412
413 let target_duration = Duration::from_secs(self.config.duration_secs);
414 let frame_budget = Duration::from_micros(16_667); let mut frames: u64 = 0;
416 let mut dropped_frames: u64 = 0;
417
418 while start.elapsed() < target_duration {
419 let frame_start = Instant::now();
420
421 for _ in 0..1000 {
423 std::hint::black_box(frames.wrapping_add(1));
424 }
425
426 let frame_time = frame_start.elapsed();
427 frames += 1;
428
429 if frame_time > frame_budget {
430 dropped_frames += 1;
431 }
432
433 if frame_time < frame_budget {
435 std::thread::sleep(frame_budget.checked_sub(frame_time).unwrap());
436 }
437 }
438
439 let elapsed = start.elapsed();
440 result.duration = elapsed;
441 result.total_ops = frames;
442 result.ops_per_sec = frames as f64 / elapsed.as_secs_f64();
443
444 let drop_rate = dropped_frames as f64 / frames as f64;
446 let fps = result.ops_per_sec;
447 let actual = format!("{:.1} FPS, {:.1}% drops", fps, drop_rate * 100.0);
448 if drop_rate < 0.05 {
449 result = result.pass("60 FPS maintained (< 5% drops)", &actual);
450 } else {
451 result = result.fail("60 FPS maintained (< 5% drops)", &actual);
452 result.errors.push(StressError {
453 kind: StressErrorKind::FrameDrop,
454 message: format!("{} frames dropped", dropped_frames),
455 time_offset: elapsed,
456 });
457 }
458
459 result.memory.stable = true;
460 result
461 }
462
463 fn run_trace_stress(&self) -> StressResult {
466 let start = Instant::now();
467 let mut result = StressResult::new(StressMode::Trace);
468
469 let _target_duration = Duration::from_secs(self.config.duration_secs);
470
471 let baseline_start = Instant::now();
473 let mut baseline_ops: u64 = 0;
474 let baseline_duration = Duration::from_secs(self.config.duration_secs / 2);
475
476 while baseline_start.elapsed() < baseline_duration {
477 for _ in 0..1000 {
478 baseline_ops += 1;
479 std::hint::black_box(baseline_ops);
480 }
481 }
482 let baseline_elapsed = baseline_start.elapsed();
483 let baseline_rate = baseline_ops as f64 / baseline_elapsed.as_secs_f64();
484
485 let traced_start = Instant::now();
487 let mut traced_ops: u64 = 0;
488 let traced_duration = Duration::from_secs(self.config.duration_secs / 2);
489
490 while traced_start.elapsed() < traced_duration {
491 for _ in 0..1000 {
492 traced_ops += 1;
493 std::hint::black_box(std::time::Instant::now());
495 std::hint::black_box(traced_ops);
496 }
497 }
498 let traced_elapsed = traced_start.elapsed();
499 let traced_rate = traced_ops as f64 / traced_elapsed.as_secs_f64();
500
501 let elapsed = start.elapsed();
502 result.duration = elapsed;
503 result.total_ops = baseline_ops + traced_ops;
504 result.ops_per_sec = traced_rate;
505
506 let overhead = if baseline_rate > 0.0 {
508 ((baseline_rate - traced_rate) / baseline_rate) * 100.0
509 } else {
510 0.0
511 };
512
513 if overhead < 5.0 {
515 result = result.pass(
516 "tracing overhead < 5%",
517 &format!("{:.2}% overhead", overhead),
518 );
519 } else {
520 result = result.fail(
521 "tracing overhead < 5%",
522 &format!("{:.2}% overhead", overhead),
523 );
524 }
525
526 result.memory.stable = true;
527 result
528 }
529
530 fn run_full_stress(&self) -> StressResult {
533 let start = Instant::now();
534 let mut result = StressResult::new(StressMode::Full);
535
536 let sub_duration = self.config.duration_secs / 4;
538
539 let atomics_config = StressConfig::atomics(sub_duration, self.config.concurrency);
540 let atomics_result = StressRunner::new(atomics_config).run();
541
542 let worker_config = StressConfig::worker_msg(sub_duration, self.config.concurrency);
543 let worker_result = StressRunner::new(worker_config).run();
544
545 let render_config = StressConfig::render(sub_duration);
546 let render_result = StressRunner::new(render_config).run();
547
548 let trace_config = StressConfig::trace(sub_duration);
549 let trace_result = StressRunner::new(trace_config).run();
550
551 let elapsed = start.elapsed();
552 result.duration = elapsed;
553 result.total_ops = atomics_result.total_ops
554 + worker_result.total_ops
555 + render_result.total_ops
556 + trace_result.total_ops;
557
558 let all_passed = atomics_result.passed
560 && worker_result.passed
561 && render_result.passed
562 && trace_result.passed;
563
564 if all_passed {
565 result = result.pass(
566 "all stress tests pass",
567 &format!(
568 "atomics: {}, worker: {}, render: {}, trace: {}",
569 if atomics_result.passed { "✓" } else { "✗" },
570 if worker_result.passed { "✓" } else { "✗" },
571 if render_result.passed { "✓" } else { "✗" },
572 if trace_result.passed { "✓" } else { "✗" },
573 ),
574 );
575 } else {
576 result = result.fail(
577 "all stress tests pass",
578 &format!(
579 "atomics: {}, worker: {}, render: {}, trace: {}",
580 if atomics_result.passed { "✓" } else { "✗" },
581 if worker_result.passed { "✓" } else { "✗" },
582 if render_result.passed { "✓" } else { "✗" },
583 if trace_result.passed { "✓" } else { "✗" },
584 ),
585 );
586 }
587
588 result.errors.extend(atomics_result.errors);
590 result.errors.extend(worker_result.errors);
591 result.errors.extend(render_result.errors);
592 result.errors.extend(trace_result.errors);
593
594 result.memory.stable = atomics_result.memory.stable
595 && worker_result.memory.stable
596 && render_result.memory.stable
597 && trace_result.memory.stable;
598
599 result
600 }
601}
602
603pub fn render_stress_report(result: &StressResult) -> String {
609 let mut output = String::new();
610
611 let status = if result.passed {
612 "✅ PASS"
613 } else {
614 "❌ FAIL"
615 };
616
617 output.push_str(&format!("STRESS TEST: {} [{}]\n", result.mode, status));
618 output.push_str("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
619
620 output.push_str(&format!("Duration: {:?}\n", result.duration));
621 output.push_str(&format!("Operations: {}\n", result.total_ops));
622 output.push_str(&format!(
623 "Throughput: {:.0} ops/sec\n\n",
624 result.ops_per_sec
625 ));
626
627 output.push_str("Pass Criteria:\n");
628 output.push_str(&format!(" Expected: {}\n", result.pass_criteria));
629 output.push_str(&format!(" Actual: {}\n\n", result.actual_value));
630
631 output.push_str("Memory:\n");
632 output.push_str(&format!(
633 " Stable: {}\n",
634 if result.memory.stable {
635 "Yes"
636 } else {
637 "No (potential leak)"
638 }
639 ));
640 if result.memory.initial_bytes > 0 {
641 output.push_str(&format!(
642 " Growth: {:.1}%\n",
643 result.memory.growth_percent()
644 ));
645 }
646
647 if !result.errors.is_empty() {
648 output.push_str(&format!("\nErrors ({}):\n", result.errors.len()));
649 for err in &result.errors {
650 output.push_str(&format!(
651 " [{:?}] {} - {}\n",
652 err.time_offset, err.kind, err.message
653 ));
654 }
655 }
656
657 output
658}
659
660pub fn render_stress_json(result: &StressResult) -> String {
662 serde_json::to_string_pretty(result).unwrap_or_else(|_| "{}".to_string())
663}
664
665#[cfg(test)]
670#[allow(clippy::unwrap_used, clippy::expect_used)]
671mod tests {
672 use super::*;
673 use std::str::FromStr;
674
675 #[test]
676 fn test_stress_mode_from_str() {
677 assert_eq!(
678 StressMode::from_str("atomics").unwrap(),
679 StressMode::Atomics
680 );
681 assert_eq!(
682 StressMode::from_str("worker-msg").unwrap(),
683 StressMode::WorkerMsg
684 );
685 assert_eq!(StressMode::from_str("render").unwrap(), StressMode::Render);
686 assert_eq!(StressMode::from_str("trace").unwrap(), StressMode::Trace);
687 assert_eq!(StressMode::from_str("full").unwrap(), StressMode::Full);
688 }
689
690 #[test]
691 fn test_stress_mode_display() {
692 assert_eq!(StressMode::Atomics.to_string(), "atomics");
693 assert_eq!(StressMode::WorkerMsg.to_string(), "worker-msg");
694 assert_eq!(StressMode::Render.to_string(), "render");
695 }
696
697 #[test]
698 fn test_stress_config_atomics() {
699 let config = StressConfig::atomics(30, 4);
700 assert_eq!(config.mode, StressMode::Atomics);
701 assert_eq!(config.duration_secs, 30);
702 assert_eq!(config.concurrency, 4);
703 }
704
705 #[test]
706 fn test_stress_config_worker_msg() {
707 let config = StressConfig::worker_msg(60, 8);
708 assert_eq!(config.mode, StressMode::WorkerMsg);
709 assert_eq!(config.duration_secs, 60);
710 assert_eq!(config.concurrency, 8);
711 }
712
713 #[test]
714 fn test_stress_result_pass() {
715 let result = StressResult::new(StressMode::Atomics).pass("> 10k ops/sec", "15000 ops/sec");
716 assert!(result.passed);
717 assert_eq!(result.pass_criteria, "> 10k ops/sec");
718 assert_eq!(result.actual_value, "15000 ops/sec");
719 }
720
721 #[test]
722 fn test_stress_result_fail() {
723 let result = StressResult::new(StressMode::Atomics).fail("> 10k ops/sec", "5000 ops/sec");
724 assert!(!result.passed);
725 }
726
727 #[test]
728 fn test_memory_stats_growth() {
729 let stats = MemoryStats {
730 initial_bytes: 1000,
731 final_bytes: 1100,
732 peak_bytes: 1200,
733 stable: true,
734 };
735 assert!((stats.growth_percent() - 10.0).abs() < 0.001);
736 }
737
738 #[test]
739 fn test_stress_error_kind_display() {
740 assert_eq!(StressErrorKind::LockTimeout.to_string(), "Lock Timeout");
741 assert_eq!(StressErrorKind::QueueOverflow.to_string(), "Queue Overflow");
742 assert_eq!(StressErrorKind::FrameDrop.to_string(), "Frame Drop");
743 }
744
745 #[test]
746 fn test_run_atomics_stress() {
747 let config = StressConfig::atomics(1, 1); let runner = StressRunner::new(config);
749 let result = runner.run();
750
751 assert_eq!(result.mode, StressMode::Atomics);
752 assert!(result.total_ops > 0);
753 assert!(result.ops_per_sec > 0.0);
754 assert!(result.passed); }
756
757 #[test]
758 fn test_run_worker_msg_stress() {
759 let config = StressConfig::worker_msg(1, 1);
760 let runner = StressRunner::new(config);
761 let result = runner.run();
762
763 assert_eq!(result.mode, StressMode::WorkerMsg);
764 assert!(result.total_ops > 0);
765 assert!(result.passed);
766 }
767
768 #[test]
769 fn test_run_render_stress() {
770 let config = StressConfig::render(1);
771 let runner = StressRunner::new(config);
772 let result = runner.run();
773
774 assert_eq!(result.mode, StressMode::Render);
775 assert!(result.total_ops > 0);
776 assert!(result.ops_per_sec > 50.0 && result.ops_per_sec < 70.0);
778 }
779
780 #[test]
781 fn test_run_trace_stress() {
782 let config = StressConfig::trace(2); let runner = StressRunner::new(config);
784 let result = runner.run();
785
786 assert_eq!(result.mode, StressMode::Trace);
787 assert!(result.total_ops > 0);
788 }
789
790 #[test]
791 fn test_run_full_stress() {
792 let config = StressConfig::full(4, 1); let runner = StressRunner::new(config);
794 let result = runner.run();
795
796 assert_eq!(result.mode, StressMode::Full);
797 assert!(result.total_ops > 0);
798 }
799
800 #[test]
801 fn test_render_stress_report() {
802 let mut result = StressResult::new(StressMode::Atomics);
803 result.duration = Duration::from_secs(30);
804 result.total_ops = 300_000;
805 result.ops_per_sec = 10_000.0;
806 result = result.pass("> 10k ops/sec", "10000 ops/sec");
807
808 let report = render_stress_report(&result);
809 assert!(report.contains("STRESS TEST: atomics"));
810 assert!(report.contains("PASS"));
811 assert!(report.contains("10000 ops/sec"));
812 }
813
814 #[test]
815 fn test_render_stress_json() {
816 let result = StressResult::new(StressMode::Atomics);
817 let json = render_stress_json(&result);
818 assert!(json.contains("Atomics"));
819 assert!(json.contains("mode"));
820 }
821
822 #[test]
823 fn test_stress_mode_display_all() {
824 assert_eq!(format!("{}", StressMode::Atomics), "atomics");
826 assert_eq!(format!("{}", StressMode::WorkerMsg), "worker-msg");
827 assert_eq!(format!("{}", StressMode::Render), "render");
828 assert_eq!(format!("{}", StressMode::Trace), "trace");
829 assert_eq!(format!("{}", StressMode::Full), "full");
830 }
831
832 #[test]
833 fn test_stress_mode_from_str_all_variants() {
834 assert_eq!(
835 "atomics".parse::<StressMode>().unwrap(),
836 StressMode::Atomics
837 );
838 assert_eq!(
839 "worker-msg".parse::<StressMode>().unwrap(),
840 StressMode::WorkerMsg
841 );
842 assert_eq!(
843 "workermsg".parse::<StressMode>().unwrap(),
844 StressMode::WorkerMsg
845 );
846 assert_eq!(
847 "worker_msg".parse::<StressMode>().unwrap(),
848 StressMode::WorkerMsg
849 );
850 assert_eq!("render".parse::<StressMode>().unwrap(), StressMode::Render);
851 assert_eq!("trace".parse::<StressMode>().unwrap(), StressMode::Trace);
852 assert_eq!("full".parse::<StressMode>().unwrap(), StressMode::Full);
853 }
854
855 #[test]
856 fn test_stress_mode_from_str_unknown() {
857 let result = "unknown_mode".parse::<StressMode>();
858 assert!(result.is_err());
859 assert!(result.unwrap_err().contains("Unknown stress mode"));
860 }
861
862 #[test]
865 fn test_stress_config_render() {
866 let config = StressConfig::render(45);
867 assert_eq!(config.mode, StressMode::Render);
868 assert_eq!(config.duration_secs, 45);
869 assert_eq!(config.concurrency, 1);
870 }
871
872 #[test]
873 fn test_stress_config_trace() {
874 let config = StressConfig::trace(20);
875 assert_eq!(config.mode, StressMode::Trace);
876 assert_eq!(config.duration_secs, 20);
877 assert_eq!(config.concurrency, 1);
878 }
879
880 #[test]
881 fn test_stress_config_full() {
882 let config = StressConfig::full(120, 16);
883 assert_eq!(config.mode, StressMode::Full);
884 assert_eq!(config.duration_secs, 120);
885 assert_eq!(config.concurrency, 16);
886 }
887
888 #[test]
889 fn test_stress_config_default() {
890 let config = StressConfig::default();
891 assert_eq!(config.mode, StressMode::Atomics);
892 assert_eq!(config.duration_secs, 30);
893 assert_eq!(config.concurrency, 4);
894 assert_eq!(config.target_ops_per_sec, 0);
895 assert_eq!(config.warmup_secs, 5);
896 }
897
898 #[test]
899 fn test_memory_stats_growth_zero_initial() {
900 let stats = MemoryStats {
901 initial_bytes: 0,
902 final_bytes: 1000,
903 peak_bytes: 1000,
904 stable: true,
905 };
906 assert_eq!(stats.growth_percent(), 0.0);
907 }
908
909 #[test]
910 fn test_stress_error_kind_display_all() {
911 assert_eq!(StressErrorKind::LockTimeout.to_string(), "Lock Timeout");
912 assert_eq!(StressErrorKind::QueueOverflow.to_string(), "Queue Overflow");
913 assert_eq!(StressErrorKind::FrameDrop.to_string(), "Frame Drop");
914 assert_eq!(StressErrorKind::OutOfMemory.to_string(), "Out of Memory");
915 assert_eq!(StressErrorKind::WorkerCrash.to_string(), "Worker Crash");
916 assert_eq!(StressErrorKind::Other.to_string(), "Other");
917 }
918
919 #[test]
920 fn test_render_stress_report_with_memory_growth() {
921 let mut result = StressResult::new(StressMode::Atomics);
922 result.duration = Duration::from_secs(30);
923 result.total_ops = 300_000;
924 result.ops_per_sec = 10_000.0;
925 result.memory = MemoryStats {
926 initial_bytes: 1000,
927 final_bytes: 1500,
928 peak_bytes: 1600,
929 stable: false,
930 };
931 result = result.fail("> 10k ops/sec", "10000 ops/sec");
932
933 let report = render_stress_report(&result);
934 assert!(report.contains("No (potential leak)"));
935 assert!(report.contains("Growth:"));
936 assert!(report.contains("50.0%"));
937 }
938
939 #[test]
940 fn test_render_stress_report_with_errors() {
941 let mut result = StressResult::new(StressMode::Render);
942 result.duration = Duration::from_secs(30);
943 result.errors.push(StressError {
944 kind: StressErrorKind::FrameDrop,
945 message: "10 frames dropped".to_string(),
946 time_offset: Duration::from_secs(15),
947 });
948 result.errors.push(StressError {
949 kind: StressErrorKind::LockTimeout,
950 message: "Lock timed out".to_string(),
951 time_offset: Duration::from_secs(20),
952 });
953
954 let report = render_stress_report(&result);
955 assert!(report.contains("Errors (2)"));
956 assert!(report.contains("Frame Drop"));
957 assert!(report.contains("Lock Timeout"));
958 }
959
960 #[test]
961 fn test_latency_stats_default() {
962 let stats = LatencyStats::default();
963 assert_eq!(stats.min_us, 0);
964 assert_eq!(stats.max_us, 0);
965 assert_eq!(stats.mean_us, 0);
966 assert_eq!(stats.p50_us, 0);
967 assert_eq!(stats.p95_us, 0);
968 assert_eq!(stats.p99_us, 0);
969 }
970
971 #[test]
972 fn test_stress_error_serialization() {
973 let error = StressError {
974 kind: StressErrorKind::OutOfMemory,
975 message: "Allocation failed".to_string(),
976 time_offset: Duration::from_millis(500),
977 };
978
979 let json = serde_json::to_string(&error).unwrap();
980 assert!(json.contains("OutOfMemory"));
981 assert!(json.contains("Allocation failed"));
982
983 let parsed: StressError = serde_json::from_str(&json).unwrap();
984 assert_eq!(parsed.kind, StressErrorKind::OutOfMemory);
985 }
986
987 #[test]
988 fn test_stress_mode_default() {
989 let mode = StressMode::default();
990 assert_eq!(mode, StressMode::Atomics);
991 }
992
993 #[test]
994 fn test_memory_stats_default() {
995 let stats = MemoryStats::default();
996 assert_eq!(stats.initial_bytes, 0);
997 assert_eq!(stats.final_bytes, 0);
998 assert_eq!(stats.peak_bytes, 0);
999 assert!(!stats.stable);
1000 }
1001
1002 #[test]
1003 fn test_stress_result_new() {
1004 let result = StressResult::new(StressMode::WorkerMsg);
1005 assert_eq!(result.mode, StressMode::WorkerMsg);
1006 assert!(!result.passed);
1007 assert!(result.pass_criteria.is_empty());
1008 assert!(result.actual_value.is_empty());
1009 assert!(result.errors.is_empty());
1010 }
1011
1012 #[test]
1013 fn test_render_stress_json_error_handling() {
1014 let result = StressResult::new(StressMode::Atomics);
1015 let json = render_stress_json(&result);
1016 assert!(!json.is_empty());
1017 assert!(json.starts_with('{'));
1018 }
1019
1020 #[test]
1021 fn test_stress_config_serde() {
1022 let config = StressConfig::atomics(60, 8);
1023 let json = serde_json::to_string(&config).unwrap();
1024 assert!(json.contains("Atomics"));
1025 assert!(json.contains("60"));
1026 assert!(json.contains('8'));
1027
1028 let parsed: StressConfig = serde_json::from_str(&json).unwrap();
1029 assert_eq!(parsed.mode, StressMode::Atomics);
1030 assert_eq!(parsed.duration_secs, 60);
1031 }
1032}