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!(
781 result.ops_per_sec > 0.0 && result.ops_per_sec.is_finite(),
782 "render stress should produce a positive finite FPS, got {}",
783 result.ops_per_sec
784 );
785 }
786
787 #[test]
788 fn test_run_trace_stress() {
789 let config = StressConfig::trace(2); let runner = StressRunner::new(config);
791 let result = runner.run();
792
793 assert_eq!(result.mode, StressMode::Trace);
794 assert!(result.total_ops > 0);
795 }
796
797 #[test]
798 fn test_run_full_stress() {
799 let config = StressConfig::full(4, 1); let runner = StressRunner::new(config);
801 let result = runner.run();
802
803 assert_eq!(result.mode, StressMode::Full);
804 assert!(result.total_ops > 0);
805 }
806
807 #[test]
808 fn test_render_stress_report() {
809 let mut result = StressResult::new(StressMode::Atomics);
810 result.duration = Duration::from_secs(30);
811 result.total_ops = 300_000;
812 result.ops_per_sec = 10_000.0;
813 result = result.pass("> 10k ops/sec", "10000 ops/sec");
814
815 let report = render_stress_report(&result);
816 assert!(report.contains("STRESS TEST: atomics"));
817 assert!(report.contains("PASS"));
818 assert!(report.contains("10000 ops/sec"));
819 }
820
821 #[test]
822 fn test_render_stress_json() {
823 let result = StressResult::new(StressMode::Atomics);
824 let json = render_stress_json(&result);
825 assert!(json.contains("Atomics"));
826 assert!(json.contains("mode"));
827 }
828
829 #[test]
830 fn test_stress_mode_display_all() {
831 assert_eq!(format!("{}", StressMode::Atomics), "atomics");
833 assert_eq!(format!("{}", StressMode::WorkerMsg), "worker-msg");
834 assert_eq!(format!("{}", StressMode::Render), "render");
835 assert_eq!(format!("{}", StressMode::Trace), "trace");
836 assert_eq!(format!("{}", StressMode::Full), "full");
837 }
838
839 #[test]
840 fn test_stress_mode_from_str_all_variants() {
841 assert_eq!(
842 "atomics".parse::<StressMode>().unwrap(),
843 StressMode::Atomics
844 );
845 assert_eq!(
846 "worker-msg".parse::<StressMode>().unwrap(),
847 StressMode::WorkerMsg
848 );
849 assert_eq!(
850 "workermsg".parse::<StressMode>().unwrap(),
851 StressMode::WorkerMsg
852 );
853 assert_eq!(
854 "worker_msg".parse::<StressMode>().unwrap(),
855 StressMode::WorkerMsg
856 );
857 assert_eq!("render".parse::<StressMode>().unwrap(), StressMode::Render);
858 assert_eq!("trace".parse::<StressMode>().unwrap(), StressMode::Trace);
859 assert_eq!("full".parse::<StressMode>().unwrap(), StressMode::Full);
860 }
861
862 #[test]
863 fn test_stress_mode_from_str_unknown() {
864 let result = "unknown_mode".parse::<StressMode>();
865 assert!(result.is_err());
866 assert!(result.unwrap_err().contains("Unknown stress mode"));
867 }
868
869 #[test]
872 fn test_stress_config_render() {
873 let config = StressConfig::render(45);
874 assert_eq!(config.mode, StressMode::Render);
875 assert_eq!(config.duration_secs, 45);
876 assert_eq!(config.concurrency, 1);
877 }
878
879 #[test]
880 fn test_stress_config_trace() {
881 let config = StressConfig::trace(20);
882 assert_eq!(config.mode, StressMode::Trace);
883 assert_eq!(config.duration_secs, 20);
884 assert_eq!(config.concurrency, 1);
885 }
886
887 #[test]
888 fn test_stress_config_full() {
889 let config = StressConfig::full(120, 16);
890 assert_eq!(config.mode, StressMode::Full);
891 assert_eq!(config.duration_secs, 120);
892 assert_eq!(config.concurrency, 16);
893 }
894
895 #[test]
896 fn test_stress_config_default() {
897 let config = StressConfig::default();
898 assert_eq!(config.mode, StressMode::Atomics);
899 assert_eq!(config.duration_secs, 30);
900 assert_eq!(config.concurrency, 4);
901 assert_eq!(config.target_ops_per_sec, 0);
902 assert_eq!(config.warmup_secs, 5);
903 }
904
905 #[test]
906 fn test_memory_stats_growth_zero_initial() {
907 let stats = MemoryStats {
908 initial_bytes: 0,
909 final_bytes: 1000,
910 peak_bytes: 1000,
911 stable: true,
912 };
913 assert_eq!(stats.growth_percent(), 0.0);
914 }
915
916 #[test]
917 fn test_stress_error_kind_display_all() {
918 assert_eq!(StressErrorKind::LockTimeout.to_string(), "Lock Timeout");
919 assert_eq!(StressErrorKind::QueueOverflow.to_string(), "Queue Overflow");
920 assert_eq!(StressErrorKind::FrameDrop.to_string(), "Frame Drop");
921 assert_eq!(StressErrorKind::OutOfMemory.to_string(), "Out of Memory");
922 assert_eq!(StressErrorKind::WorkerCrash.to_string(), "Worker Crash");
923 assert_eq!(StressErrorKind::Other.to_string(), "Other");
924 }
925
926 #[test]
927 fn test_render_stress_report_with_memory_growth() {
928 let mut result = StressResult::new(StressMode::Atomics);
929 result.duration = Duration::from_secs(30);
930 result.total_ops = 300_000;
931 result.ops_per_sec = 10_000.0;
932 result.memory = MemoryStats {
933 initial_bytes: 1000,
934 final_bytes: 1500,
935 peak_bytes: 1600,
936 stable: false,
937 };
938 result = result.fail("> 10k ops/sec", "10000 ops/sec");
939
940 let report = render_stress_report(&result);
941 assert!(report.contains("No (potential leak)"));
942 assert!(report.contains("Growth:"));
943 assert!(report.contains("50.0%"));
944 }
945
946 #[test]
947 fn test_render_stress_report_with_errors() {
948 let mut result = StressResult::new(StressMode::Render);
949 result.duration = Duration::from_secs(30);
950 result.errors.push(StressError {
951 kind: StressErrorKind::FrameDrop,
952 message: "10 frames dropped".to_string(),
953 time_offset: Duration::from_secs(15),
954 });
955 result.errors.push(StressError {
956 kind: StressErrorKind::LockTimeout,
957 message: "Lock timed out".to_string(),
958 time_offset: Duration::from_secs(20),
959 });
960
961 let report = render_stress_report(&result);
962 assert!(report.contains("Errors (2)"));
963 assert!(report.contains("Frame Drop"));
964 assert!(report.contains("Lock Timeout"));
965 }
966
967 #[test]
968 fn test_latency_stats_default() {
969 let stats = LatencyStats::default();
970 assert_eq!(stats.min_us, 0);
971 assert_eq!(stats.max_us, 0);
972 assert_eq!(stats.mean_us, 0);
973 assert_eq!(stats.p50_us, 0);
974 assert_eq!(stats.p95_us, 0);
975 assert_eq!(stats.p99_us, 0);
976 }
977
978 #[test]
979 fn test_stress_error_serialization() {
980 let error = StressError {
981 kind: StressErrorKind::OutOfMemory,
982 message: "Allocation failed".to_string(),
983 time_offset: Duration::from_millis(500),
984 };
985
986 let json = serde_json::to_string(&error).unwrap();
987 assert!(json.contains("OutOfMemory"));
988 assert!(json.contains("Allocation failed"));
989
990 let parsed: StressError = serde_json::from_str(&json).unwrap();
991 assert_eq!(parsed.kind, StressErrorKind::OutOfMemory);
992 }
993
994 #[test]
995 fn test_stress_mode_default() {
996 let mode = StressMode::default();
997 assert_eq!(mode, StressMode::Atomics);
998 }
999
1000 #[test]
1001 fn test_memory_stats_default() {
1002 let stats = MemoryStats::default();
1003 assert_eq!(stats.initial_bytes, 0);
1004 assert_eq!(stats.final_bytes, 0);
1005 assert_eq!(stats.peak_bytes, 0);
1006 assert!(!stats.stable);
1007 }
1008
1009 #[test]
1010 fn test_stress_result_new() {
1011 let result = StressResult::new(StressMode::WorkerMsg);
1012 assert_eq!(result.mode, StressMode::WorkerMsg);
1013 assert!(!result.passed);
1014 assert!(result.pass_criteria.is_empty());
1015 assert!(result.actual_value.is_empty());
1016 assert!(result.errors.is_empty());
1017 }
1018
1019 #[test]
1020 fn test_render_stress_json_error_handling() {
1021 let result = StressResult::new(StressMode::Atomics);
1022 let json = render_stress_json(&result);
1023 assert!(!json.is_empty());
1024 assert!(json.starts_with('{'));
1025 }
1026
1027 #[test]
1028 fn test_stress_config_serde() {
1029 let config = StressConfig::atomics(60, 8);
1030 let json = serde_json::to_string(&config).unwrap();
1031 assert!(json.contains("Atomics"));
1032 assert!(json.contains("60"));
1033 assert!(json.contains('8'));
1034
1035 let parsed: StressConfig = serde_json::from_str(&json).unwrap();
1036 assert_eq!(parsed.mode, StressMode::Atomics);
1037 assert_eq!(parsed.duration_secs, 60);
1038 }
1039}