1use std::collections::VecDeque;
16use std::fmt;
17use std::time::{Duration, Instant};
18
19#[derive(Debug, Clone)]
34pub struct VuMeterConfig {
35 pub min_level: f32,
37 pub max_level: f32,
39 pub update_rate_hz: f32,
41 pub smoothing_tolerance: f32,
43 pub max_stale_ms: u64,
45}
46
47impl Default for VuMeterConfig {
48 fn default() -> Self {
49 Self {
50 min_level: 0.0,
51 max_level: 1.0,
52 update_rate_hz: 30.0,
53 smoothing_tolerance: 0.1,
54 max_stale_ms: 100,
55 }
56 }
57}
58
59impl VuMeterConfig {
60 #[must_use]
62 pub fn with_min_level(mut self, level: f32) -> Self {
63 self.min_level = level.clamp(0.0, 1.0);
64 self
65 }
66
67 #[must_use]
69 pub fn with_max_level(mut self, level: f32) -> Self {
70 self.max_level = level.clamp(0.0, 1.0);
71 self
72 }
73
74 #[must_use]
76 pub fn with_update_rate_hz(mut self, rate: f32) -> Self {
77 self.update_rate_hz = rate.max(1.0);
78 self
79 }
80
81 #[must_use]
83 pub fn with_max_stale_ms(mut self, ms: u64) -> Self {
84 self.max_stale_ms = ms;
85 self
86 }
87
88 pub fn validate_sample(&self, level: f32) -> Result<(), VuMeterError> {
90 if level < 0.0 {
91 return Err(VuMeterError::NegativeLevel(level));
92 }
93 if level > self.max_level + self.smoothing_tolerance {
94 return Err(VuMeterError::Clipping(level));
95 }
96 Ok(())
97 }
98}
99
100#[derive(Debug, Clone)]
102pub enum VuMeterError {
103 NegativeLevel(f32),
105 Clipping(f32),
107 Stale {
109 last_update_ms: u64,
111 current_ms: u64,
113 },
114 SlowUpdateRate {
116 measured_hz: f32,
118 expected_hz: f32,
120 },
121 NotAnimating {
123 sample_count: usize,
125 value: f32,
127 },
128}
129
130impl fmt::Display for VuMeterError {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 Self::NegativeLevel(v) => write!(f, "VU meter level is negative: {v}"),
134 Self::Clipping(v) => write!(f, "VU meter clipping at: {v}"),
135 Self::Stale {
136 last_update_ms,
137 current_ms,
138 } => {
139 write!(
140 f,
141 "VU meter stale: last update {}ms ago",
142 current_ms - last_update_ms
143 )
144 }
145 Self::SlowUpdateRate {
146 measured_hz,
147 expected_hz,
148 } => {
149 write!(
150 f,
151 "VU meter update rate too slow: {measured_hz:.1}Hz < {expected_hz:.1}Hz"
152 )
153 }
154 Self::NotAnimating {
155 sample_count,
156 value,
157 } => {
158 write!(
159 f,
160 "VU meter not animating: {sample_count} samples all at {value}"
161 )
162 }
163 }
164 }
165}
166
167impl std::error::Error for VuMeterError {}
168
169#[derive(Debug, Clone)]
175pub struct StateTransition {
176 pub from: String,
178 pub to: String,
180 pub timestamp_ms: f64,
182 pub duration_ms: f64,
184}
185
186#[derive(Debug, Clone)]
188pub struct PartialResult {
189 pub timestamp_ms: f64,
191 pub text: String,
193 pub is_final: bool,
195}
196
197#[derive(Debug, Clone)]
199pub struct VuMeterSample {
200 pub timestamp_ms: f64,
202 pub level: f32,
204}
205
206#[derive(Debug, Clone, Default)]
226pub struct TestExecutionStats {
227 pub states_captured: u64,
229 pub bytes_raw: u64,
231 pub bytes_compressed: u64,
233 pub same_fill_pages: u64,
235 start_time: Option<Instant>,
237 end_time: Option<Instant>,
239}
240
241impl TestExecutionStats {
242 #[must_use]
244 pub fn new() -> Self {
245 Self {
246 states_captured: 0,
247 bytes_raw: 0,
248 bytes_compressed: 0,
249 same_fill_pages: 0,
250 start_time: None,
251 end_time: None,
252 }
253 }
254
255 pub fn start(&mut self) {
257 self.start_time = Some(Instant::now());
258 }
259
260 pub fn stop(&mut self) {
262 self.end_time = Some(Instant::now());
263 }
264
265 pub fn record_state_capture(&mut self, raw_bytes: u64, compressed_bytes: u64) {
267 self.states_captured += 1;
268 self.bytes_raw += raw_bytes;
269 self.bytes_compressed += compressed_bytes;
270
271 if raw_bytes > 0 && (compressed_bytes as f64 / raw_bytes as f64) < 0.1 {
273 self.same_fill_pages += 1;
274 }
275 }
276
277 #[must_use]
279 pub fn compression_ratio(&self) -> f64 {
280 if self.bytes_compressed == 0 {
281 return 0.0;
282 }
283 self.bytes_raw as f64 / self.bytes_compressed as f64
284 }
285
286 #[must_use]
288 pub fn efficiency(&self) -> f64 {
289 if self.bytes_raw == 0 {
290 return 0.0;
291 }
292 1.0 - (self.bytes_compressed as f64 / self.bytes_raw as f64)
293 }
294
295 #[must_use]
297 pub fn storage_savings_mb(&self) -> f64 {
298 (self.bytes_raw.saturating_sub(self.bytes_compressed)) as f64 / 1_000_000.0
299 }
300
301 #[must_use]
303 pub fn compress_throughput(&self) -> f64 {
304 match (self.start_time, self.end_time) {
305 (Some(start), Some(end)) => {
306 let duration_secs = end.duration_since(start).as_secs_f64();
307 if duration_secs > 0.0 {
308 self.bytes_raw as f64 / duration_secs
309 } else {
310 0.0
311 }
312 }
313 _ => 0.0,
314 }
315 }
316
317 #[must_use]
319 pub fn same_fill_ratio(&self) -> f64 {
320 if self.states_captured == 0 {
321 return 0.0;
322 }
323 self.same_fill_pages as f64 / self.states_captured as f64
324 }
325
326 pub fn reset(&mut self) {
328 self.states_captured = 0;
329 self.bytes_raw = 0;
330 self.bytes_compressed = 0;
331 self.same_fill_pages = 0;
332 self.start_time = None;
333 self.end_time = None;
334 }
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum CompressionAlgorithm {
344 Lz4,
346 Zstd,
348 Png,
350 Rle,
352}
353
354#[derive(Debug, Clone)]
369pub enum ScreenshotContent {
370 UiDominated {
372 entropy: f32,
374 },
375 GameWorld {
377 entropy: f32,
379 },
380 HighEntropy {
382 entropy: f32,
384 },
385 Uniform {
387 fill_value: u8,
389 },
390}
391
392impl ScreenshotContent {
393 #[must_use]
397 pub fn classify(pixels: &[u8]) -> Self {
398 if pixels.is_empty() {
399 return Self::Uniform { fill_value: 0 };
400 }
401
402 let mut frequencies = [0u64; 256];
404 for &byte in pixels {
405 frequencies[byte as usize] += 1;
406 }
407
408 let total = pixels.len() as f64;
410 let (max_idx, max_count) = frequencies
411 .iter()
412 .enumerate()
413 .max_by_key(|(_, &count)| count)
414 .map(|(idx, &count)| (idx, count))
415 .unwrap_or((0, 0));
416
417 if max_count as f64 / total > 0.95 {
418 return Self::Uniform {
419 fill_value: max_idx as u8,
420 };
421 }
422
423 let entropy: f32 = frequencies
425 .iter()
426 .filter(|&&count| count > 0)
427 .map(|&count| {
428 let p = count as f64 / total;
429 -(p * p.log2()) as f32
430 })
431 .sum();
432
433 if entropy < 3.0 {
435 Self::UiDominated { entropy }
436 } else if entropy < 6.0 {
437 Self::GameWorld { entropy }
438 } else {
439 Self::HighEntropy { entropy }
440 }
441 }
442
443 #[must_use]
445 pub fn entropy(&self) -> f32 {
446 match self {
447 Self::UiDominated { entropy }
448 | Self::GameWorld { entropy }
449 | Self::HighEntropy { entropy } => *entropy,
450 Self::Uniform { .. } => 0.0,
451 }
452 }
453
454 #[must_use]
456 pub fn recommended_algorithm(&self) -> CompressionAlgorithm {
457 match self {
458 Self::Uniform { .. } => CompressionAlgorithm::Rle,
459 Self::UiDominated { .. } => CompressionAlgorithm::Png,
460 Self::GameWorld { .. } => CompressionAlgorithm::Zstd,
461 Self::HighEntropy { .. } => CompressionAlgorithm::Lz4,
462 }
463 }
464
465 #[must_use]
467 pub fn expected_ratio_hint(&self) -> &'static str {
468 match self {
469 Self::Uniform { .. } => "excellent (>100:1)",
470 Self::UiDominated { .. } => "good (5:1 - 20:1)",
471 Self::GameWorld { .. } => "moderate (2:1 - 5:1)",
472 Self::HighEntropy { .. } => "poor (<2:1)",
473 }
474 }
475}
476
477#[derive(Debug, Clone)]
495pub struct StreamingUxValidator {
496 max_latency: Duration,
498 buffer_underrun_threshold: usize,
500 max_dropped_frames: usize,
502 min_fps: f64,
504 ttfb_timeout: Duration,
506 metrics: Vec<StreamingMetricRecord>,
508 buffer_underruns: usize,
510 dropped_frames: usize,
512 frame_times: VecDeque<u64>,
514 first_byte_time: Option<Instant>,
516 start_time: Option<Instant>,
518 state: StreamingState,
520 state_history: Vec<(StreamingState, Instant)>,
522}
523
524#[derive(Debug, Clone)]
526pub struct StreamingMetricRecord {
527 pub metric: StreamingMetric,
529 pub timestamp: Instant,
531}
532
533#[derive(Debug, Clone)]
535pub enum StreamingMetric {
536 Latency(Duration),
538 FrameRendered {
540 timestamp: u64,
542 },
543 FrameDropped,
545 BufferUnderrun,
547 FirstByteReceived,
549 BufferLevel(f32),
551 AudioChunk {
553 samples: usize,
555 sample_rate: u32,
557 },
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
562pub enum StreamingState {
563 Idle,
565 Buffering,
567 Streaming,
569 Stalled,
571 Error,
573 Completed,
575}
576
577impl Default for StreamingState {
578 fn default() -> Self {
579 Self::Idle
580 }
581}
582
583impl fmt::Display for StreamingState {
584 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585 match self {
586 Self::Idle => write!(f, "Idle"),
587 Self::Buffering => write!(f, "Buffering"),
588 Self::Streaming => write!(f, "Streaming"),
589 Self::Stalled => write!(f, "Stalled"),
590 Self::Error => write!(f, "Error"),
591 Self::Completed => write!(f, "Completed"),
592 }
593 }
594}
595
596#[derive(Debug, Clone)]
598pub enum StreamingValidationError {
599 LatencyExceeded {
601 measured: Duration,
603 max: Duration,
605 },
606 BufferUnderrunThreshold {
608 count: usize,
610 threshold: usize,
612 },
613 DroppedFrameThreshold {
615 count: usize,
617 max: usize,
619 },
620 FpsBelowMinimum {
622 measured: f64,
624 min: f64,
626 },
627 TtfbExceeded {
629 measured: Duration,
631 max: Duration,
633 },
634 InvalidStateTransition {
636 from: StreamingState,
638 to: StreamingState,
640 },
641 EndedInError,
643}
644
645impl fmt::Display for StreamingValidationError {
646 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
647 match self {
648 Self::LatencyExceeded { measured, max } => {
649 write!(f, "Latency exceeded: {measured:?} > {max:?} (max allowed)")
650 }
651 Self::BufferUnderrunThreshold { count, threshold } => {
652 write!(
653 f,
654 "Buffer underruns exceeded threshold: {count} > {threshold}"
655 )
656 }
657 Self::DroppedFrameThreshold { count, max } => {
658 write!(f, "Dropped frames exceeded: {count} > {max} (max allowed)")
659 }
660 Self::FpsBelowMinimum { measured, min } => {
661 write!(f, "FPS below minimum: {measured:.1} < {min:.1} (required)")
662 }
663 Self::TtfbExceeded { measured, max } => {
664 write!(
665 f,
666 "Time to first byte exceeded: {measured:?} > {max:?} (max allowed)"
667 )
668 }
669 Self::InvalidStateTransition { from, to } => {
670 write!(f, "Invalid state transition: {from} -> {to}")
671 }
672 Self::EndedInError => write!(f, "Streaming ended in error state"),
673 }
674 }
675}
676
677impl std::error::Error for StreamingValidationError {}
678
679impl Default for StreamingUxValidator {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685impl StreamingUxValidator {
686 #[must_use]
688 pub fn new() -> Self {
689 Self {
690 max_latency: Duration::from_millis(200),
691 buffer_underrun_threshold: 5,
692 max_dropped_frames: 10,
693 min_fps: 24.0,
694 ttfb_timeout: Duration::from_secs(3),
695 metrics: Vec::new(),
696 buffer_underruns: 0,
697 dropped_frames: 0,
698 frame_times: VecDeque::with_capacity(120),
699 first_byte_time: None,
700 start_time: None,
701 state: StreamingState::Idle,
702 state_history: Vec::new(),
703 }
704 }
705
706 #[must_use]
708 pub fn for_audio() -> Self {
709 Self::new()
710 .with_max_latency(Duration::from_millis(100))
711 .with_buffer_underrun_threshold(3)
712 .with_ttfb_timeout(Duration::from_secs(2))
713 }
714
715 #[must_use]
717 pub fn for_video() -> Self {
718 Self::new()
719 .with_max_latency(Duration::from_millis(500))
720 .with_min_fps(30.0)
721 .with_max_dropped_frames(5)
722 }
723
724 #[must_use]
726 pub fn with_max_latency(mut self, latency: Duration) -> Self {
727 self.max_latency = latency;
728 self
729 }
730
731 #[must_use]
733 pub fn with_buffer_underrun_threshold(mut self, threshold: usize) -> Self {
734 self.buffer_underrun_threshold = threshold;
735 self
736 }
737
738 #[must_use]
740 pub fn with_max_dropped_frames(mut self, max: usize) -> Self {
741 self.max_dropped_frames = max;
742 self
743 }
744
745 #[must_use]
747 pub fn with_min_fps(mut self, fps: f64) -> Self {
748 self.min_fps = fps;
749 self
750 }
751
752 #[must_use]
754 pub fn with_ttfb_timeout(mut self, timeout: Duration) -> Self {
755 self.ttfb_timeout = timeout;
756 self
757 }
758
759 pub fn start(&mut self) {
761 self.start_time = Some(Instant::now());
762 self.transition_to(StreamingState::Buffering);
763 }
764
765 pub fn record_metric(&mut self, metric: StreamingMetric) {
767 let now = Instant::now();
768
769 match &metric {
770 StreamingMetric::Latency(latency) => {
771 if self.state == StreamingState::Buffering && *latency < self.max_latency {
773 self.transition_to(StreamingState::Streaming);
774 }
775 }
776 StreamingMetric::FrameRendered { timestamp } => {
777 self.frame_times.push_back(*timestamp);
778 while self.frame_times.len() > 120 {
780 self.frame_times.pop_front();
781 }
782 if self.state == StreamingState::Stalled {
784 self.transition_to(StreamingState::Streaming);
785 }
786 }
787 StreamingMetric::FrameDropped => {
788 self.dropped_frames += 1;
789 }
790 StreamingMetric::BufferUnderrun => {
791 self.buffer_underruns += 1;
792 if self.state == StreamingState::Streaming {
793 self.transition_to(StreamingState::Stalled);
794 }
795 }
796 StreamingMetric::FirstByteReceived => {
797 self.first_byte_time = Some(now);
798 if self.state == StreamingState::Idle {
799 self.transition_to(StreamingState::Buffering);
800 }
801 }
802 StreamingMetric::BufferLevel(level) => {
803 if *level < 0.1 && self.state == StreamingState::Streaming {
805 self.transition_to(StreamingState::Stalled);
806 } else if *level > 0.3 && self.state == StreamingState::Stalled {
807 self.transition_to(StreamingState::Streaming);
808 }
809 }
810 StreamingMetric::AudioChunk { .. } => {
811 if self.state == StreamingState::Buffering {
813 self.transition_to(StreamingState::Streaming);
814 }
815 }
816 }
817
818 self.metrics.push(StreamingMetricRecord {
819 metric,
820 timestamp: now,
821 });
822 }
823
824 fn transition_to(&mut self, new_state: StreamingState) {
826 if self.state != new_state {
827 self.state_history.push((self.state, Instant::now()));
828 self.state = new_state;
829 }
830 }
831
832 pub fn complete(&mut self) {
834 self.transition_to(StreamingState::Completed);
835 }
836
837 pub fn error(&mut self) {
839 self.transition_to(StreamingState::Error);
840 }
841
842 #[must_use]
844 pub fn state(&self) -> StreamingState {
845 self.state
846 }
847
848 #[must_use]
850 pub fn buffer_underruns(&self) -> usize {
851 self.buffer_underruns
852 }
853
854 #[must_use]
856 pub fn dropped_frames(&self) -> usize {
857 self.dropped_frames
858 }
859
860 #[must_use]
862 pub fn average_fps(&self) -> f64 {
863 if self.frame_times.len() < 2 {
864 return 0.0;
865 }
866
867 let first = *self.frame_times.front().unwrap_or(&0);
868 let last = *self.frame_times.back().unwrap_or(&0);
869 let duration_ms = last.saturating_sub(first);
870
871 if duration_ms == 0 {
872 return 0.0;
873 }
874
875 let frame_count = self.frame_times.len() - 1;
876 (frame_count as f64 * 1000.0) / duration_ms as f64
877 }
878
879 pub fn validate(&self) -> Result<StreamingValidationResult, StreamingValidationError> {
884 let mut errors = Vec::new();
885
886 for record in &self.metrics {
888 if let StreamingMetric::Latency(latency) = &record.metric {
889 if *latency > self.max_latency {
890 errors.push(StreamingValidationError::LatencyExceeded {
891 measured: *latency,
892 max: self.max_latency,
893 });
894 }
895 }
896 }
897
898 if self.buffer_underruns > self.buffer_underrun_threshold {
900 errors.push(StreamingValidationError::BufferUnderrunThreshold {
901 count: self.buffer_underruns,
902 threshold: self.buffer_underrun_threshold,
903 });
904 }
905
906 if self.dropped_frames > self.max_dropped_frames {
908 errors.push(StreamingValidationError::DroppedFrameThreshold {
909 count: self.dropped_frames,
910 max: self.max_dropped_frames,
911 });
912 }
913
914 let fps = self.average_fps();
916 if fps > 0.0 && fps < self.min_fps {
917 errors.push(StreamingValidationError::FpsBelowMinimum {
918 measured: fps,
919 min: self.min_fps,
920 });
921 }
922
923 if let (Some(start), Some(first_byte)) = (self.start_time, self.first_byte_time) {
925 let ttfb = first_byte.duration_since(start);
926 if ttfb > self.ttfb_timeout {
927 errors.push(StreamingValidationError::TtfbExceeded {
928 measured: ttfb,
929 max: self.ttfb_timeout,
930 });
931 }
932 }
933
934 if self.state == StreamingState::Error {
936 errors.push(StreamingValidationError::EndedInError);
937 }
938
939 if errors.is_empty() {
940 Ok(StreamingValidationResult {
941 buffer_underruns: self.buffer_underruns,
942 dropped_frames: self.dropped_frames,
943 average_fps: fps,
944 max_latency_recorded: self.max_recorded_latency(),
945 total_frames: self.frame_times.len(),
946 })
947 } else {
948 Err(errors.remove(0))
949 }
950 }
951
952 #[must_use]
954 pub fn validate_all(&self) -> Vec<StreamingValidationError> {
955 let mut errors = Vec::new();
956
957 for record in &self.metrics {
958 if let StreamingMetric::Latency(latency) = &record.metric {
959 if *latency > self.max_latency {
960 errors.push(StreamingValidationError::LatencyExceeded {
961 measured: *latency,
962 max: self.max_latency,
963 });
964 }
965 }
966 }
967
968 if self.buffer_underruns > self.buffer_underrun_threshold {
969 errors.push(StreamingValidationError::BufferUnderrunThreshold {
970 count: self.buffer_underruns,
971 threshold: self.buffer_underrun_threshold,
972 });
973 }
974
975 if self.dropped_frames > self.max_dropped_frames {
976 errors.push(StreamingValidationError::DroppedFrameThreshold {
977 count: self.dropped_frames,
978 max: self.max_dropped_frames,
979 });
980 }
981
982 let fps = self.average_fps();
983 if fps > 0.0 && fps < self.min_fps {
984 errors.push(StreamingValidationError::FpsBelowMinimum {
985 measured: fps,
986 min: self.min_fps,
987 });
988 }
989
990 if self.state == StreamingState::Error {
991 errors.push(StreamingValidationError::EndedInError);
992 }
993
994 errors
995 }
996
997 fn max_recorded_latency(&self) -> Duration {
999 self.metrics
1000 .iter()
1001 .filter_map(|r| {
1002 if let StreamingMetric::Latency(l) = &r.metric {
1003 Some(*l)
1004 } else {
1005 None
1006 }
1007 })
1008 .max()
1009 .unwrap_or(Duration::ZERO)
1010 }
1011
1012 #[must_use]
1014 pub fn state_history(&self) -> &[(StreamingState, Instant)] {
1015 &self.state_history
1016 }
1017
1018 pub fn reset(&mut self) {
1020 self.metrics.clear();
1021 self.buffer_underruns = 0;
1022 self.dropped_frames = 0;
1023 self.frame_times.clear();
1024 self.first_byte_time = None;
1025 self.start_time = None;
1026 self.state = StreamingState::Idle;
1027 self.state_history.clear();
1028 }
1029}
1030
1031#[derive(Debug, Clone)]
1033pub struct StreamingValidationResult {
1034 pub buffer_underruns: usize,
1036 pub dropped_frames: usize,
1038 pub average_fps: f64,
1040 pub max_latency_recorded: Duration,
1042 pub total_frames: usize,
1044}
1045
1046#[cfg(feature = "browser")]
1048impl StreamingUxValidator {
1049 pub async fn track_state_cdp(
1057 page: &chromiumoxide::Page,
1058 selector: &str,
1059 ) -> Result<Self, StreamingValidationError> {
1060 let js = format!(
1062 r#"
1063 (function() {{
1064 window.__probar_state_history = [];
1065 window.__probar_start_time = performance.now();
1066 window.__probar_last_state = '';
1067
1068 const el = document.querySelector('{}');
1069 if (!el) {{
1070 return {{ error: 'Element not found: {}' }};
1071 }}
1072
1073 const observer = new MutationObserver((mutations) => {{
1074 const newState = el.textContent || el.innerText || '';
1075 if (newState !== window.__probar_last_state) {{
1076 const now = performance.now();
1077 const elapsed = now - window.__probar_start_time;
1078 window.__probar_state_history.push({{
1079 from: window.__probar_last_state,
1080 to: newState,
1081 timestamp: elapsed,
1082 }});
1083 window.__probar_last_state = newState;
1084 }}
1085 }});
1086
1087 observer.observe(el, {{
1088 characterData: true,
1089 childList: true,
1090 subtree: true
1091 }});
1092
1093 window.__probar_state_observer = observer;
1094 return {{ success: true }};
1095 }})()
1096 "#,
1097 selector, selector
1098 );
1099
1100 let _: serde_json::Value = page
1101 .evaluate(js)
1102 .await
1103 .map_err(|_e| StreamingValidationError::InvalidStateTransition {
1104 from: StreamingState::Idle,
1105 to: StreamingState::Error,
1106 })?
1107 .into_value()
1108 .map_err(|_| StreamingValidationError::InvalidStateTransition {
1109 from: StreamingState::Idle,
1110 to: StreamingState::Error,
1111 })?;
1112
1113 Ok(Self::new())
1114 }
1115
1116 pub async fn track_vu_meter_cdp(
1124 &mut self,
1125 page: &chromiumoxide::Page,
1126 selector: &str,
1127 ) -> Result<(), StreamingValidationError> {
1128 let js = format!(
1129 r#"
1130 (function() {{
1131 window.__probar_vu_samples = [];
1132 window.__probar_vu_start_time = performance.now();
1133
1134 const el = document.querySelector('{}');
1135 if (!el) {{
1136 return {{ error: 'VU meter element not found: {}' }};
1137 }}
1138
1139 function sampleVu() {{
1140 const elapsed = performance.now() - window.__probar_vu_start_time;
1141 // Try to get level from data attribute, width, or computed style
1142 let level = parseFloat(el.dataset.level) || 0;
1143 if (!level) {{
1144 const style = getComputedStyle(el);
1145 const widthPct = parseFloat(style.width) / parseFloat(style.maxWidth || 100);
1146 level = isNaN(widthPct) ? 0 : widthPct;
1147 }}
1148 window.__probar_vu_samples.push([elapsed, level]);
1149
1150 if (window.__probar_vu_running) {{
1151 requestAnimationFrame(sampleVu);
1152 }}
1153 }}
1154
1155 window.__probar_vu_running = true;
1156 requestAnimationFrame(sampleVu);
1157 return {{ success: true }};
1158 }})()
1159 "#,
1160 selector, selector
1161 );
1162
1163 let _: serde_json::Value = page
1164 .evaluate(js)
1165 .await
1166 .map_err(|_| StreamingValidationError::EndedInError)?
1167 .into_value()
1168 .map_err(|_| StreamingValidationError::EndedInError)?;
1169
1170 Ok(())
1171 }
1172
1173 pub async fn track_partials_cdp(
1181 &mut self,
1182 page: &chromiumoxide::Page,
1183 selector: &str,
1184 ) -> Result<(), StreamingValidationError> {
1185 let js = format!(
1186 r#"
1187 (function() {{
1188 window.__probar_partials = [];
1189 window.__probar_partials_start = performance.now();
1190 window.__probar_last_partial = '';
1191
1192 const el = document.querySelector('{}');
1193 if (!el) {{
1194 return {{ error: 'Partial results element not found: {}' }};
1195 }}
1196
1197 const observer = new MutationObserver((mutations) => {{
1198 const text = el.textContent || el.innerText || '';
1199 if (text !== window.__probar_last_partial) {{
1200 const elapsed = performance.now() - window.__probar_partials_start;
1201 window.__probar_partials.push([elapsed, text]);
1202 window.__probar_last_partial = text;
1203 }}
1204 }});
1205
1206 observer.observe(el, {{
1207 characterData: true,
1208 childList: true,
1209 subtree: true
1210 }});
1211
1212 window.__probar_partials_observer = observer;
1213 return {{ success: true }};
1214 }})()
1215 "#,
1216 selector, selector
1217 );
1218
1219 let _: serde_json::Value = page
1220 .evaluate(js)
1221 .await
1222 .map_err(|_| StreamingValidationError::EndedInError)?
1223 .into_value()
1224 .map_err(|_| StreamingValidationError::EndedInError)?;
1225
1226 Ok(())
1227 }
1228
1229 pub async fn collect_state_history_cdp(
1234 &self,
1235 page: &chromiumoxide::Page,
1236 ) -> Result<Vec<StateTransition>, StreamingValidationError> {
1237 let js = r#"
1238 (function() {
1239 const history = window.__probar_state_history || [];
1240 return history.map((h, i, arr) => ({
1241 from: h.from,
1242 to: h.to,
1243 timestamp: h.timestamp,
1244 duration_ms: i < arr.length - 1 ? arr[i + 1].timestamp - h.timestamp : 0
1245 }));
1246 })()
1247 "#;
1248
1249 let result: Vec<serde_json::Value> = page
1250 .evaluate(js)
1251 .await
1252 .map_err(|_| StreamingValidationError::EndedInError)?
1253 .into_value()
1254 .map_err(|_| StreamingValidationError::EndedInError)?;
1255
1256 Ok(result
1257 .into_iter()
1258 .map(|v| StateTransition {
1259 from: v["from"].as_str().unwrap_or("").to_string(),
1260 to: v["to"].as_str().unwrap_or("").to_string(),
1261 timestamp_ms: v["timestamp"].as_f64().unwrap_or(0.0),
1262 duration_ms: v["duration_ms"].as_f64().unwrap_or(0.0),
1263 })
1264 .collect())
1265 }
1266
1267 pub async fn collect_vu_samples_cdp(
1272 &self,
1273 page: &chromiumoxide::Page,
1274 ) -> Result<Vec<VuMeterSample>, StreamingValidationError> {
1275 let _: serde_json::Value = page
1277 .evaluate("window.__probar_vu_running = false; true")
1278 .await
1279 .map_err(|_| StreamingValidationError::EndedInError)?
1280 .into_value()
1281 .map_err(|_| StreamingValidationError::EndedInError)?;
1282
1283 let js = "window.__probar_vu_samples || []";
1284 let result: Vec<Vec<f64>> = page
1285 .evaluate(js)
1286 .await
1287 .map_err(|_| StreamingValidationError::EndedInError)?
1288 .into_value()
1289 .map_err(|_| StreamingValidationError::EndedInError)?;
1290
1291 Ok(result
1292 .into_iter()
1293 .map(|arr| VuMeterSample {
1294 timestamp_ms: arr.first().copied().unwrap_or(0.0),
1295 level: arr.get(1).copied().unwrap_or(0.0) as f32,
1296 })
1297 .collect())
1298 }
1299
1300 pub async fn collect_partials_cdp(
1305 &self,
1306 page: &chromiumoxide::Page,
1307 ) -> Result<Vec<PartialResult>, StreamingValidationError> {
1308 let js = "window.__probar_partials || []";
1309 let result: Vec<Vec<serde_json::Value>> = page
1310 .evaluate(js)
1311 .await
1312 .map_err(|_| StreamingValidationError::EndedInError)?
1313 .into_value()
1314 .map_err(|_| StreamingValidationError::EndedInError)?;
1315
1316 let partials: Vec<PartialResult> = result
1317 .into_iter()
1318 .filter_map(|arr| {
1319 let ts = arr.first()?.as_f64()?;
1320 let text = arr.get(1)?.as_str()?.to_string();
1321 Some(PartialResult {
1322 timestamp_ms: ts,
1323 text,
1324 is_final: false,
1325 })
1326 })
1327 .collect();
1328
1329 if partials.is_empty() {
1331 return Ok(partials);
1332 }
1333
1334 let mut result = partials;
1335 if let Some(last) = result.last_mut() {
1336 last.is_final = true;
1337 }
1338 Ok(result)
1339 }
1340
1341 pub async fn assert_state_sequence_cdp(
1346 &self,
1347 page: &chromiumoxide::Page,
1348 expected: &[&str],
1349 ) -> Result<(), StreamingValidationError> {
1350 let history = self.collect_state_history_cdp(page).await?;
1351 let states: Vec<&str> = history.iter().map(|t| t.to.as_str()).collect();
1352
1353 let mut expected_iter = expected.iter();
1354 let mut current_expected = expected_iter.next();
1355
1356 for state in &states {
1357 if let Some(exp) = current_expected {
1358 if state == exp {
1359 current_expected = expected_iter.next();
1360 }
1361 }
1362 }
1363
1364 if current_expected.is_some() {
1365 return Err(StreamingValidationError::InvalidStateTransition {
1366 from: StreamingState::Idle,
1367 to: StreamingState::Error,
1368 });
1369 }
1370
1371 Ok(())
1372 }
1373
1374 pub async fn assert_vu_meter_active_cdp(
1379 &self,
1380 page: &chromiumoxide::Page,
1381 min_level: f32,
1382 duration_ms: u64,
1383 ) -> Result<(), StreamingValidationError> {
1384 let samples = self.collect_vu_samples_cdp(page).await?;
1385
1386 let mut active_duration: f64 = 0.0;
1387 let mut last_ts: Option<f64> = None;
1388
1389 for sample in &samples {
1390 if sample.level >= min_level {
1391 if let Some(prev_ts) = last_ts {
1392 active_duration += sample.timestamp_ms - prev_ts;
1393 }
1394 }
1395 last_ts = Some(sample.timestamp_ms);
1396 }
1397
1398 if active_duration < duration_ms as f64 {
1399 return Err(StreamingValidationError::TtfbExceeded {
1400 measured: Duration::from_millis(active_duration as u64),
1401 max: Duration::from_millis(duration_ms),
1402 });
1403 }
1404
1405 Ok(())
1406 }
1407
1408 pub async fn assert_no_jank_cdp(
1413 &self,
1414 page: &chromiumoxide::Page,
1415 max_gap_ms: f64,
1416 ) -> Result<(), StreamingValidationError> {
1417 let history = self.collect_state_history_cdp(page).await?;
1418
1419 for transition in &history {
1420 if transition.duration_ms > max_gap_ms {
1421 return Err(StreamingValidationError::LatencyExceeded {
1422 measured: Duration::from_millis(transition.duration_ms as u64),
1423 max: Duration::from_millis(max_gap_ms as u64),
1424 });
1425 }
1426 }
1427
1428 Ok(())
1429 }
1430
1431 pub async fn assert_partials_before_final_cdp(
1436 &self,
1437 page: &chromiumoxide::Page,
1438 ) -> Result<(), StreamingValidationError> {
1439 let partials = self.collect_partials_cdp(page).await?;
1440
1441 if partials.len() < 2 {
1443 return Err(StreamingValidationError::EndedInError);
1444 }
1445
1446 let non_final_count = partials.iter().filter(|p| !p.is_final).count();
1448 if non_final_count == 0 {
1449 return Err(StreamingValidationError::EndedInError);
1450 }
1451
1452 Ok(())
1453 }
1454
1455 pub async fn avg_transition_time_ms_cdp(
1460 &self,
1461 page: &chromiumoxide::Page,
1462 ) -> Result<f64, StreamingValidationError> {
1463 let history = self.collect_state_history_cdp(page).await?;
1464
1465 if history.is_empty() {
1466 return Ok(0.0);
1467 }
1468
1469 let total: f64 = history.iter().map(|t| t.duration_ms).sum();
1470 Ok(total / history.len() as f64)
1471 }
1472}
1473
1474#[cfg(test)]
1475#[allow(clippy::unwrap_used, clippy::expect_used)]
1476mod tests {
1477 use super::*;
1478
1479 #[test]
1484 fn f029_latency_exceeded() {
1485 let mut validator =
1487 StreamingUxValidator::new().with_max_latency(Duration::from_millis(100));
1488
1489 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(150)));
1490
1491 let result = validator.validate();
1492 assert!(result.is_err());
1493 let err = result.unwrap_err();
1494 assert!(matches!(
1495 err,
1496 StreamingValidationError::LatencyExceeded { .. }
1497 ));
1498 }
1499
1500 #[test]
1501 fn f030_latency_acceptable() {
1502 let mut validator =
1504 StreamingUxValidator::new().with_max_latency(Duration::from_millis(100));
1505
1506 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
1507
1508 assert!(validator.validate().is_ok());
1509 }
1510
1511 #[test]
1512 fn f031_buffer_underrun_threshold() {
1513 let mut validator = StreamingUxValidator::new().with_buffer_underrun_threshold(2);
1515
1516 validator.record_metric(StreamingMetric::BufferUnderrun);
1517 validator.record_metric(StreamingMetric::BufferUnderrun);
1518 validator.record_metric(StreamingMetric::BufferUnderrun);
1519
1520 let result = validator.validate();
1521 assert!(result.is_err());
1522 assert!(matches!(
1523 result.unwrap_err(),
1524 StreamingValidationError::BufferUnderrunThreshold { .. }
1525 ));
1526 }
1527
1528 #[test]
1529 fn f032_dropped_frames_threshold() {
1530 let mut validator = StreamingUxValidator::new().with_max_dropped_frames(2);
1532
1533 for _ in 0..5 {
1534 validator.record_metric(StreamingMetric::FrameDropped);
1535 }
1536
1537 let result = validator.validate();
1538 assert!(result.is_err());
1539 assert!(matches!(
1540 result.unwrap_err(),
1541 StreamingValidationError::DroppedFrameThreshold { .. }
1542 ));
1543 }
1544
1545 #[test]
1550 fn f033_state_idle_to_buffering() {
1551 let mut validator = StreamingUxValidator::new();
1553 assert_eq!(validator.state(), StreamingState::Idle);
1554
1555 validator.record_metric(StreamingMetric::FirstByteReceived);
1556 assert_eq!(validator.state(), StreamingState::Buffering);
1557 }
1558
1559 #[test]
1560 fn f034_state_buffering_to_streaming() {
1561 let mut validator = StreamingUxValidator::new();
1563 validator.start();
1564 assert_eq!(validator.state(), StreamingState::Buffering);
1565
1566 validator.record_metric(StreamingMetric::AudioChunk {
1567 samples: 1024,
1568 sample_rate: 16000,
1569 });
1570 assert_eq!(validator.state(), StreamingState::Streaming);
1571 }
1572
1573 #[test]
1574 fn f035_state_streaming_to_stalled() {
1575 let mut validator = StreamingUxValidator::new();
1577 validator.start();
1578 validator.record_metric(StreamingMetric::AudioChunk {
1579 samples: 1024,
1580 sample_rate: 16000,
1581 });
1582 assert_eq!(validator.state(), StreamingState::Streaming);
1583
1584 validator.record_metric(StreamingMetric::BufferUnderrun);
1585 assert_eq!(validator.state(), StreamingState::Stalled);
1586 }
1587
1588 #[test]
1589 fn f036_state_recovery_from_stalled() {
1590 let mut validator = StreamingUxValidator::new();
1592 validator.start();
1593 validator.record_metric(StreamingMetric::AudioChunk {
1594 samples: 1024,
1595 sample_rate: 16000,
1596 });
1597 validator.record_metric(StreamingMetric::BufferUnderrun);
1598 assert_eq!(validator.state(), StreamingState::Stalled);
1599
1600 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
1601 assert_eq!(validator.state(), StreamingState::Streaming);
1602 }
1603
1604 #[test]
1609 fn f037_fps_calculation() {
1610 let mut validator = StreamingUxValidator::new();
1612
1613 for i in 0..31 {
1615 validator.record_metric(StreamingMetric::FrameRendered {
1616 timestamp: i * 33, });
1618 }
1619
1620 let fps = validator.average_fps();
1621 assert!((fps - 30.0).abs() < 1.0, "FPS was {fps}, expected ~30");
1623 }
1624
1625 #[test]
1626 fn f038_fps_below_minimum() {
1627 let mut validator = StreamingUxValidator::new().with_min_fps(30.0);
1629
1630 for i in 0..16 {
1632 validator.record_metric(StreamingMetric::FrameRendered {
1633 timestamp: i * 66, });
1635 }
1636
1637 let result = validator.validate();
1638 assert!(result.is_err());
1639 assert!(matches!(
1640 result.unwrap_err(),
1641 StreamingValidationError::FpsBelowMinimum { .. }
1642 ));
1643 }
1644
1645 #[test]
1650 fn test_default_validator() {
1651 let validator = StreamingUxValidator::new();
1652 assert_eq!(validator.state(), StreamingState::Idle);
1653 assert_eq!(validator.buffer_underruns(), 0);
1654 assert_eq!(validator.dropped_frames(), 0);
1655 }
1656
1657 #[test]
1658 fn test_audio_preset() {
1659 let validator = StreamingUxValidator::for_audio();
1660 assert_eq!(validator.max_latency, Duration::from_millis(100));
1661 assert_eq!(validator.buffer_underrun_threshold, 3);
1662 }
1663
1664 #[test]
1665 fn test_video_preset() {
1666 let validator = StreamingUxValidator::for_video();
1667 assert_eq!(validator.max_latency, Duration::from_millis(500));
1668 assert!((validator.min_fps - 30.0).abs() < f64::EPSILON);
1669 }
1670
1671 #[test]
1672 fn test_complete_transition() {
1673 let mut validator = StreamingUxValidator::new();
1674 validator.complete();
1675 assert_eq!(validator.state(), StreamingState::Completed);
1676 }
1677
1678 #[test]
1679 fn test_error_transition() {
1680 let mut validator = StreamingUxValidator::new();
1681 validator.error();
1682 assert_eq!(validator.state(), StreamingState::Error);
1683 assert!(validator.validate().is_err());
1684 }
1685
1686 #[test]
1687 fn test_reset() {
1688 let mut validator = StreamingUxValidator::new();
1689 validator.start();
1690 validator.record_metric(StreamingMetric::BufferUnderrun);
1691 validator.record_metric(StreamingMetric::FrameDropped);
1692
1693 validator.reset();
1694 assert_eq!(validator.state(), StreamingState::Idle);
1695 assert_eq!(validator.buffer_underruns(), 0);
1696 assert_eq!(validator.dropped_frames(), 0);
1697 }
1698
1699 #[test]
1700 fn test_validate_all_errors() {
1701 let mut validator = StreamingUxValidator::new()
1702 .with_max_latency(Duration::from_millis(50))
1703 .with_buffer_underrun_threshold(1);
1704
1705 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
1706 validator.record_metric(StreamingMetric::BufferUnderrun);
1707 validator.record_metric(StreamingMetric::BufferUnderrun);
1708
1709 let errors = validator.validate_all();
1710 assert!(errors.len() >= 2);
1711 }
1712
1713 #[test]
1714 fn test_state_history() {
1715 let mut validator = StreamingUxValidator::new();
1716 validator.start();
1717 validator.record_metric(StreamingMetric::AudioChunk {
1718 samples: 1024,
1719 sample_rate: 16000,
1720 });
1721
1722 let history = validator.state_history();
1723 assert!(!history.is_empty());
1724 assert_eq!(history[0].0, StreamingState::Idle);
1725 }
1726
1727 #[test]
1728 fn test_buffer_level_transitions() {
1729 let mut validator = StreamingUxValidator::new();
1730 validator.start();
1731 validator.record_metric(StreamingMetric::AudioChunk {
1732 samples: 1024,
1733 sample_rate: 16000,
1734 });
1735 assert_eq!(validator.state(), StreamingState::Streaming);
1736
1737 validator.record_metric(StreamingMetric::BufferLevel(0.05));
1739 assert_eq!(validator.state(), StreamingState::Stalled);
1740
1741 validator.record_metric(StreamingMetric::BufferLevel(0.5));
1743 assert_eq!(validator.state(), StreamingState::Streaming);
1744 }
1745
1746 #[test]
1747 fn test_streaming_state_display() {
1748 assert_eq!(format!("{}", StreamingState::Idle), "Idle");
1749 assert_eq!(format!("{}", StreamingState::Streaming), "Streaming");
1750 assert_eq!(format!("{}", StreamingState::Stalled), "Stalled");
1751 }
1752
1753 #[test]
1758 fn f039_vu_meter_negative_level_rejected() {
1759 let config = VuMeterConfig::default();
1761 let result = config.validate_sample(-0.5);
1762 assert!(result.is_err());
1763 assert!(matches!(
1764 result.unwrap_err(),
1765 VuMeterError::NegativeLevel(_)
1766 ));
1767 }
1768
1769 #[test]
1770 fn f040_vu_meter_clipping_detected() {
1771 let config = VuMeterConfig::default().with_max_level(1.0);
1773 let result = config.validate_sample(1.5);
1775 assert!(result.is_err());
1776 assert!(matches!(result.unwrap_err(), VuMeterError::Clipping(_)));
1777 }
1778
1779 #[test]
1780 fn f041_vu_meter_valid_level_accepted() {
1781 let config = VuMeterConfig::default();
1783 assert!(config.validate_sample(0.5).is_ok());
1784 assert!(config.validate_sample(0.0).is_ok());
1785 assert!(config.validate_sample(1.0).is_ok());
1786 }
1787
1788 #[test]
1789 fn f042_vu_meter_config_builder() {
1790 let config = VuMeterConfig::default()
1792 .with_min_level(0.1)
1793 .with_max_level(0.9)
1794 .with_update_rate_hz(60.0)
1795 .with_max_stale_ms(50);
1796
1797 assert!((config.min_level - 0.1).abs() < f32::EPSILON);
1798 assert!((config.max_level - 0.9).abs() < f32::EPSILON);
1799 assert!((config.update_rate_hz - 60.0).abs() < f32::EPSILON);
1800 assert_eq!(config.max_stale_ms, 50);
1801 }
1802
1803 #[test]
1804 fn f043_vu_meter_level_clamping() {
1805 let config = VuMeterConfig::default()
1807 .with_min_level(-5.0)
1808 .with_max_level(10.0);
1809
1810 assert!((config.min_level - 0.0).abs() < f32::EPSILON);
1812 assert!((config.max_level - 1.0).abs() < f32::EPSILON);
1813 }
1814
1815 #[test]
1816 fn f044_vu_meter_min_update_rate() {
1817 let config = VuMeterConfig::default().with_update_rate_hz(0.1);
1819
1820 assert!((config.update_rate_hz - 1.0).abs() < f32::EPSILON);
1821 }
1822
1823 #[test]
1824 fn f045_vu_meter_error_display() {
1825 let negative = VuMeterError::NegativeLevel(-0.5);
1827 assert!(negative.to_string().contains("negative"));
1828
1829 let clipping = VuMeterError::Clipping(1.5);
1830 assert!(clipping.to_string().contains("clipping"));
1831
1832 let stale = VuMeterError::Stale {
1833 last_update_ms: 100,
1834 current_ms: 300,
1835 };
1836 assert!(stale.to_string().contains("stale"));
1837
1838 let slow = VuMeterError::SlowUpdateRate {
1839 measured_hz: 10.0,
1840 expected_hz: 30.0,
1841 };
1842 assert!(slow.to_string().contains("slow"));
1843
1844 let not_animating = VuMeterError::NotAnimating {
1845 sample_count: 10,
1846 value: 0.5,
1847 };
1848 assert!(not_animating.to_string().contains("not animating"));
1849 }
1850
1851 #[test]
1852 fn f046_state_transition_tracking() {
1853 let transition = StateTransition {
1855 from: "Idle".to_string(),
1856 to: "Recording".to_string(),
1857 timestamp_ms: 1000.0,
1858 duration_ms: 500.0,
1859 };
1860
1861 assert_eq!(transition.from, "Idle");
1862 assert_eq!(transition.to, "Recording");
1863 assert!((transition.timestamp_ms - 1000.0).abs() < f64::EPSILON);
1864 assert!((transition.duration_ms - 500.0).abs() < f64::EPSILON);
1865 }
1866
1867 #[test]
1868 fn f047_partial_result_tracking() {
1869 let partial = PartialResult {
1871 timestamp_ms: 1500.0,
1872 text: "Hello wo".to_string(),
1873 is_final: false,
1874 };
1875
1876 assert!(!partial.is_final);
1877 assert_eq!(partial.text, "Hello wo");
1878
1879 let final_result = PartialResult {
1880 timestamp_ms: 2000.0,
1881 text: "Hello world".to_string(),
1882 is_final: true,
1883 };
1884
1885 assert!(final_result.is_final);
1886 }
1887
1888 #[test]
1889 fn f048_vu_meter_sample_tracking() {
1890 let samples = vec![
1892 VuMeterSample {
1893 timestamp_ms: 0.0,
1894 level: 0.1,
1895 },
1896 VuMeterSample {
1897 timestamp_ms: 33.3,
1898 level: 0.3,
1899 },
1900 VuMeterSample {
1901 timestamp_ms: 66.6,
1902 level: 0.5,
1903 },
1904 VuMeterSample {
1905 timestamp_ms: 100.0,
1906 level: 0.4,
1907 },
1908 ];
1909
1910 let avg: f32 = samples.iter().map(|s| s.level).sum::<f32>() / samples.len() as f32;
1912 assert!((avg - 0.325).abs() < 0.01);
1913
1914 let duration = samples.last().unwrap().timestamp_ms - samples.first().unwrap().timestamp_ms;
1916 assert!((duration - 100.0).abs() < f64::EPSILON);
1917 }
1918
1919 #[test]
1924 fn f049_test_execution_stats_creation() {
1925 let stats = TestExecutionStats::new();
1927 assert_eq!(stats.states_captured, 0);
1928 assert_eq!(stats.bytes_raw, 0);
1929 assert_eq!(stats.bytes_compressed, 0);
1930 assert_eq!(stats.same_fill_pages, 0);
1931 }
1932
1933 #[test]
1934 fn f050_test_execution_stats_recording() {
1935 let mut stats = TestExecutionStats::new();
1937 stats.record_state_capture(4096, 1024);
1938 stats.record_state_capture(4096, 2048);
1939
1940 assert_eq!(stats.states_captured, 2);
1941 assert_eq!(stats.bytes_raw, 8192);
1942 assert_eq!(stats.bytes_compressed, 3072);
1943 }
1944
1945 #[test]
1946 fn f051_test_execution_stats_compression_ratio() {
1947 let mut stats = TestExecutionStats::new();
1949 stats.record_state_capture(4000, 1000);
1950
1951 let ratio = stats.compression_ratio();
1952 assert!((ratio - 4.0).abs() < 0.01);
1953 }
1954
1955 #[test]
1956 fn f052_test_execution_stats_efficiency() {
1957 let mut stats = TestExecutionStats::new();
1959 stats.record_state_capture(1000, 250); let efficiency = stats.efficiency();
1962 assert!((efficiency - 0.75).abs() < 0.01);
1963 }
1964
1965 #[test]
1966 fn f053_test_execution_stats_storage_savings() {
1967 let mut stats = TestExecutionStats::new();
1969 stats.record_state_capture(5_000_000, 1_000_000); let savings = stats.storage_savings_mb();
1972 assert!((savings - 4.0).abs() < 0.01);
1973 }
1974
1975 #[test]
1976 fn f054_test_execution_stats_same_fill_detection() {
1977 let mut stats = TestExecutionStats::new();
1979 stats.record_state_capture(4096, 100); stats.record_state_capture(4096, 1024); assert_eq!(stats.same_fill_pages, 1);
1983 assert!((stats.same_fill_ratio() - 0.5).abs() < 0.01);
1984 }
1985
1986 #[test]
1987 fn f055_test_execution_stats_reset() {
1988 let mut stats = TestExecutionStats::new();
1990 stats.record_state_capture(4096, 1024);
1991 stats.reset();
1992
1993 assert_eq!(stats.states_captured, 0);
1994 assert_eq!(stats.bytes_raw, 0);
1995 assert_eq!(stats.bytes_compressed, 0);
1996 }
1997
1998 #[test]
1999 fn f056_test_execution_stats_edge_cases() {
2000 let mut stats = TestExecutionStats::new();
2002
2003 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
2005 assert!((stats.efficiency() - 0.0).abs() < f64::EPSILON);
2006 assert!((stats.same_fill_ratio() - 0.0).abs() < f64::EPSILON);
2007
2008 stats.record_state_capture(1000, 0);
2010 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON); }
2012
2013 #[test]
2018 fn f057_screenshot_content_uniform_detection() {
2019 let pixels: Vec<u8> = vec![255; 1000];
2021 let content = ScreenshotContent::classify(&pixels);
2022
2023 assert!(matches!(
2024 content,
2025 ScreenshotContent::Uniform { fill_value: 255 }
2026 ));
2027 assert!((content.entropy() - 0.0).abs() < f32::EPSILON);
2028 }
2029
2030 #[test]
2031 fn f058_screenshot_content_ui_dominated() {
2032 let mut pixels = vec![255u8; 900]; pixels.extend(vec![0u8; 50]); pixels.extend(vec![128u8; 50]); let content = ScreenshotContent::classify(&pixels);
2039 assert!(matches!(
2042 content,
2043 ScreenshotContent::UiDominated { .. } | ScreenshotContent::Uniform { .. }
2044 ));
2045 }
2046
2047 #[test]
2048 fn f059_screenshot_content_high_entropy() {
2049 let pixels: Vec<u8> = (0..1000).map(|i| ((i * 127 + 37) % 256) as u8).collect();
2052 let content = ScreenshotContent::classify(&pixels);
2053
2054 assert!(matches!(
2056 content,
2057 ScreenshotContent::GameWorld { .. } | ScreenshotContent::HighEntropy { .. }
2058 ));
2059 }
2060
2061 #[test]
2062 fn f060_screenshot_content_compression_algorithm() {
2063 let uniform = ScreenshotContent::Uniform { fill_value: 0 };
2065 assert_eq!(uniform.recommended_algorithm(), CompressionAlgorithm::Rle);
2066
2067 let ui = ScreenshotContent::UiDominated { entropy: 2.0 };
2068 assert_eq!(ui.recommended_algorithm(), CompressionAlgorithm::Png);
2069
2070 let game = ScreenshotContent::GameWorld { entropy: 4.5 };
2071 assert_eq!(game.recommended_algorithm(), CompressionAlgorithm::Zstd);
2072
2073 let high = ScreenshotContent::HighEntropy { entropy: 7.0 };
2074 assert_eq!(high.recommended_algorithm(), CompressionAlgorithm::Lz4);
2075 }
2076
2077 #[test]
2078 fn f061_screenshot_content_ratio_hints() {
2079 let uniform = ScreenshotContent::Uniform { fill_value: 0 };
2081 assert!(uniform.expected_ratio_hint().contains("excellent"));
2082
2083 let ui = ScreenshotContent::UiDominated { entropy: 2.0 };
2084 assert!(ui.expected_ratio_hint().contains("good"));
2085
2086 let game = ScreenshotContent::GameWorld { entropy: 4.5 };
2087 assert!(game.expected_ratio_hint().contains("moderate"));
2088
2089 let high = ScreenshotContent::HighEntropy { entropy: 7.0 };
2090 assert!(high.expected_ratio_hint().contains("poor"));
2091 }
2092
2093 #[test]
2094 fn f062_screenshot_content_empty_input() {
2095 let content = ScreenshotContent::classify(&[]);
2097 assert!(matches!(
2098 content,
2099 ScreenshotContent::Uniform { fill_value: 0 }
2100 ));
2101 }
2102
2103 #[test]
2104 fn f063_screenshot_content_entropy_extraction() {
2105 let variants = [
2107 ScreenshotContent::UiDominated { entropy: 1.5 },
2108 ScreenshotContent::GameWorld { entropy: 4.0 },
2109 ScreenshotContent::HighEntropy { entropy: 7.5 },
2110 ScreenshotContent::Uniform { fill_value: 128 },
2111 ];
2112
2113 let entropies: Vec<f32> = variants.iter().map(|v| v.entropy()).collect();
2114 assert!((entropies[0] - 1.5).abs() < f32::EPSILON);
2115 assert!((entropies[1] - 4.0).abs() < f32::EPSILON);
2116 assert!((entropies[2] - 7.5).abs() < f32::EPSILON);
2117 assert!((entropies[3] - 0.0).abs() < f32::EPSILON); }
2119
2120 #[test]
2125 fn test_execution_stats_start_stop_throughput() {
2126 let mut stats = TestExecutionStats::new();
2128 stats.start();
2129
2130 stats.record_state_capture(1_000_000, 100_000);
2132 stats.record_state_capture(1_000_000, 100_000);
2133
2134 stats.stop();
2135
2136 let throughput = stats.compress_throughput();
2138 assert!(throughput >= 0.0);
2140 }
2141
2142 #[test]
2143 fn test_execution_stats_throughput_no_timing() {
2144 let mut stats = TestExecutionStats::new();
2146 stats.record_state_capture(1000, 100);
2147
2148 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
2150 }
2151
2152 #[test]
2153 fn test_execution_stats_throughput_start_only() {
2154 let mut stats = TestExecutionStats::new();
2156 stats.start();
2157 stats.record_state_capture(1000, 100);
2158
2159 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
2161 }
2162
2163 #[test]
2164 fn test_streaming_validation_error_display() {
2165 let latency_err = StreamingValidationError::LatencyExceeded {
2167 measured: Duration::from_millis(500),
2168 max: Duration::from_millis(100),
2169 };
2170 assert!(latency_err.to_string().contains("exceeded"));
2171
2172 let underrun_err = StreamingValidationError::BufferUnderrunThreshold {
2173 count: 10,
2174 threshold: 5,
2175 };
2176 assert!(underrun_err.to_string().contains("Buffer underruns"));
2177
2178 let dropped_err = StreamingValidationError::DroppedFrameThreshold { count: 20, max: 10 };
2179 assert!(dropped_err.to_string().contains("Dropped frames"));
2180
2181 let fps_err = StreamingValidationError::FpsBelowMinimum {
2182 measured: 15.0,
2183 min: 30.0,
2184 };
2185 assert!(fps_err.to_string().contains("FPS below"));
2186
2187 let ttfb_err = StreamingValidationError::TtfbExceeded {
2188 measured: Duration::from_secs(5),
2189 max: Duration::from_secs(2),
2190 };
2191 assert!(ttfb_err.to_string().contains("first byte"));
2192
2193 let transition_err = StreamingValidationError::InvalidStateTransition {
2194 from: StreamingState::Idle,
2195 to: StreamingState::Completed,
2196 };
2197 assert!(transition_err.to_string().contains("Invalid state"));
2198
2199 let error_err = StreamingValidationError::EndedInError;
2200 assert!(error_err.to_string().contains("error state"));
2201 }
2202
2203 #[test]
2204 fn test_streaming_state_default() {
2205 let state: StreamingState = Default::default();
2206 assert_eq!(state, StreamingState::Idle);
2207 }
2208
2209 #[test]
2210 fn test_streaming_state_display_all_variants() {
2211 assert_eq!(format!("{}", StreamingState::Idle), "Idle");
2212 assert_eq!(format!("{}", StreamingState::Buffering), "Buffering");
2213 assert_eq!(format!("{}", StreamingState::Streaming), "Streaming");
2214 assert_eq!(format!("{}", StreamingState::Stalled), "Stalled");
2215 assert_eq!(format!("{}", StreamingState::Error), "Error");
2216 assert_eq!(format!("{}", StreamingState::Completed), "Completed");
2217 }
2218
2219 #[test]
2220 fn test_streaming_metric_record_creation() {
2221 let record = StreamingMetricRecord {
2222 metric: StreamingMetric::BufferUnderrun,
2223 timestamp: Instant::now(),
2224 };
2225 assert!(matches!(record.metric, StreamingMetric::BufferUnderrun));
2226 }
2227
2228 #[test]
2229 fn test_streaming_ux_validator_default() {
2230 let validator: StreamingUxValidator = Default::default();
2231 assert_eq!(validator.state(), StreamingState::Idle);
2232 }
2233
2234 #[test]
2235 fn test_ttfb_validation() {
2236 let mut validator =
2237 StreamingUxValidator::new().with_ttfb_timeout(Duration::from_millis(100));
2238
2239 validator.start();
2241
2242 std::thread::sleep(Duration::from_millis(150));
2244
2245 validator.record_metric(StreamingMetric::FirstByteReceived);
2247
2248 let result = validator.validate();
2249 assert!(result.is_err());
2251 if let Err(err) = result {
2252 assert!(matches!(err, StreamingValidationError::TtfbExceeded { .. }));
2253 }
2254 }
2255
2256 #[test]
2257 fn test_ttfb_validation_success() {
2258 let mut validator = StreamingUxValidator::new().with_ttfb_timeout(Duration::from_secs(5));
2259
2260 validator.start();
2262 validator.record_metric(StreamingMetric::FirstByteReceived);
2263
2264 validator.record_metric(StreamingMetric::AudioChunk {
2266 samples: 1024,
2267 sample_rate: 16000,
2268 });
2269
2270 let result = validator.validate();
2271 assert!(result.is_ok());
2272 }
2273
2274 #[test]
2275 fn test_compression_algorithm_enum() {
2276 assert_ne!(CompressionAlgorithm::Lz4, CompressionAlgorithm::Zstd);
2278 assert_ne!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Png);
2279 assert_ne!(CompressionAlgorithm::Png, CompressionAlgorithm::Rle);
2280 }
2281
2282 #[test]
2283 fn test_vu_meter_config_debug() {
2284 let config = VuMeterConfig::default();
2285 let debug_str = format!("{:?}", config);
2286 assert!(debug_str.contains("VuMeterConfig"));
2287 }
2288
2289 #[test]
2290 fn test_state_transition_debug() {
2291 let transition = StateTransition {
2292 from: "Idle".to_string(),
2293 to: "Recording".to_string(),
2294 timestamp_ms: 1000.0,
2295 duration_ms: 500.0,
2296 };
2297 let debug_str = format!("{:?}", transition);
2298 assert!(debug_str.contains("StateTransition"));
2299 }
2300
2301 #[test]
2302 fn test_partial_result_debug() {
2303 let partial = PartialResult {
2304 timestamp_ms: 1500.0,
2305 text: "Hello".to_string(),
2306 is_final: false,
2307 };
2308 let debug_str = format!("{:?}", partial);
2309 assert!(debug_str.contains("PartialResult"));
2310 }
2311
2312 #[test]
2313 fn test_vu_meter_sample_debug() {
2314 let sample = VuMeterSample {
2315 timestamp_ms: 100.0,
2316 level: 0.5,
2317 };
2318 let debug_str = format!("{:?}", sample);
2319 assert!(debug_str.contains("VuMeterSample"));
2320 }
2321
2322 #[test]
2323 fn test_test_execution_stats_debug() {
2324 let stats = TestExecutionStats::new();
2325 let debug_str = format!("{:?}", stats);
2326 assert!(debug_str.contains("TestExecutionStats"));
2327 }
2328
2329 #[test]
2330 fn test_screenshot_content_debug() {
2331 let content = ScreenshotContent::UiDominated { entropy: 2.0 };
2332 let debug_str = format!("{:?}", content);
2333 assert!(debug_str.contains("UiDominated"));
2334 }
2335
2336 #[test]
2337 fn test_streaming_metric_debug() {
2338 let metric = StreamingMetric::Latency(Duration::from_millis(50));
2339 let debug_str = format!("{:?}", metric);
2340 assert!(debug_str.contains("Latency"));
2341 }
2342
2343 #[test]
2344 fn test_streaming_validation_error_as_error() {
2345 let err = StreamingValidationError::EndedInError;
2347 let _: &dyn std::error::Error = &err;
2348 }
2349
2350 #[test]
2351 fn test_vu_meter_error_as_error() {
2352 let err = VuMeterError::NegativeLevel(-0.5);
2354 let _: &dyn std::error::Error = &err;
2355 }
2356
2357 #[test]
2358 fn test_streaming_metric_all_variants() {
2359 let metrics = vec![
2361 StreamingMetric::Latency(Duration::from_millis(50)),
2362 StreamingMetric::FrameRendered { timestamp: 1000 },
2363 StreamingMetric::FrameDropped,
2364 StreamingMetric::BufferUnderrun,
2365 StreamingMetric::FirstByteReceived,
2366 StreamingMetric::BufferLevel(0.5),
2367 StreamingMetric::AudioChunk {
2368 samples: 1024,
2369 sample_rate: 16000,
2370 },
2371 ];
2372
2373 assert_eq!(metrics.len(), 7);
2374 }
2375
2376 #[test]
2377 fn test_streaming_ux_validator_clone() {
2378 let validator = StreamingUxValidator::new()
2379 .with_max_latency(Duration::from_millis(100))
2380 .with_buffer_underrun_threshold(3);
2381
2382 let cloned = validator;
2383 assert_eq!(cloned.state(), StreamingState::Idle);
2384 }
2385
2386 #[test]
2387 fn test_test_execution_stats_clone() {
2388 let mut stats = TestExecutionStats::new();
2389 stats.record_state_capture(1000, 100);
2390
2391 let cloned = stats.clone();
2392 assert_eq!(cloned.states_captured, 1);
2393 }
2394
2395 #[test]
2396 fn test_vu_meter_config_clone() {
2397 let config = VuMeterConfig::default().with_min_level(0.2);
2398 let cloned = config;
2399 assert!((cloned.min_level - 0.2).abs() < f32::EPSILON);
2400 }
2401
2402 #[test]
2403 fn test_state_transition_clone() {
2404 let transition = StateTransition {
2405 from: "Idle".to_string(),
2406 to: "Recording".to_string(),
2407 timestamp_ms: 1000.0,
2408 duration_ms: 500.0,
2409 };
2410 let cloned = transition;
2411 assert_eq!(cloned.from, "Idle");
2412 }
2413
2414 #[test]
2417 fn test_vu_meter_stale_error_display() {
2418 let err = VuMeterError::Stale {
2419 last_update_ms: 100,
2420 current_ms: 300,
2421 };
2422 let display = format!("{}", err);
2423 assert!(display.contains("stale"));
2424 assert!(display.contains("200ms"));
2425 }
2426
2427 #[test]
2428 fn test_vu_meter_slow_update_rate_error_display() {
2429 let err = VuMeterError::SlowUpdateRate {
2430 measured_hz: 15.0,
2431 expected_hz: 30.0,
2432 };
2433 let display = format!("{}", err);
2434 assert!(display.contains("15.0Hz"));
2435 assert!(display.contains("30.0Hz"));
2436 }
2437
2438 #[test]
2439 fn test_vu_meter_not_animating_error_display() {
2440 let err = VuMeterError::NotAnimating {
2441 sample_count: 100,
2442 value: 0.5,
2443 };
2444 let display = format!("{}", err);
2445 assert!(display.contains("100 samples"));
2446 assert!(display.contains("0.5"));
2447 }
2448
2449 #[test]
2450 fn test_screenshot_content_game_world() {
2451 let mut pixels = Vec::with_capacity(1000);
2453 for i in 0..1000 {
2454 pixels.push((i % 64) as u8); }
2456 let content = ScreenshotContent::classify(&pixels);
2457 match content {
2459 ScreenshotContent::GameWorld { entropy } => {
2460 assert!((3.0..6.0).contains(&entropy));
2461 }
2462 ScreenshotContent::HighEntropy { entropy } => {
2463 assert!(entropy >= 6.0);
2465 }
2466 _ => {}
2467 }
2468 }
2469
2470 #[test]
2471 fn test_streaming_validation_error_invalid_transition() {
2472 let err = StreamingValidationError::InvalidStateTransition {
2473 from: StreamingState::Idle,
2474 to: StreamingState::Streaming,
2475 };
2476 let display = format!("{}", err);
2477 assert!(display.contains("Invalid state transition"));
2478 assert!(display.contains("Idle"));
2479 assert!(display.contains("Streaming"));
2480 }
2481
2482 #[test]
2483 fn test_streaming_latency_transition() {
2484 let mut validator = StreamingUxValidator::new();
2485 validator.start();
2486 assert_eq!(validator.state(), StreamingState::Buffering);
2487
2488 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(10)));
2490 assert_eq!(validator.state(), StreamingState::Streaming);
2491 }
2492
2493 #[test]
2494 fn test_streaming_buffer_level() {
2495 let mut validator = StreamingUxValidator::new();
2496 validator.start();
2497
2498 validator.record_metric(StreamingMetric::BufferLevel(0.75));
2500 assert_eq!(validator.state(), StreamingState::Buffering);
2501 }
2502
2503 #[test]
2504 fn test_streaming_frame_times_overflow() {
2505 let mut validator = StreamingUxValidator::new();
2506 validator.start();
2507 validator.record_metric(StreamingMetric::AudioChunk {
2508 samples: 1024,
2509 sample_rate: 16000,
2510 });
2511
2512 for i in 0..150 {
2514 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 33 });
2515 }
2516
2517 let fps = validator.average_fps();
2519 assert!(fps > 0.0);
2520 }
2521
2522 #[test]
2523 fn test_streaming_metrics_all_variants_coverage() {
2524 let mut validator = StreamingUxValidator::new();
2525 validator.start();
2526
2527 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
2529 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 0 });
2530 validator.record_metric(StreamingMetric::FrameDropped);
2531 validator.record_metric(StreamingMetric::BufferUnderrun);
2532 validator.record_metric(StreamingMetric::FirstByteReceived);
2533 validator.record_metric(StreamingMetric::BufferLevel(0.5));
2534 validator.record_metric(StreamingMetric::AudioChunk {
2535 samples: 1024,
2536 sample_rate: 16000,
2537 });
2538
2539 assert!(validator.dropped_frames() >= 1);
2540 assert!(validator.buffer_underruns() >= 1);
2541 }
2542
2543 #[test]
2544 fn test_max_recorded_latency() {
2545 let mut validator = StreamingUxValidator::new();
2546 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
2547 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
2548 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(75)));
2549
2550 let result = validator.validate();
2552 assert!(result.is_ok());
2553 }
2554
2555 #[test]
2556 fn test_validate_all_with_fps_error() {
2557 let mut validator = StreamingUxValidator::new()
2558 .with_min_fps(60.0)
2559 .with_max_dropped_frames(0);
2560
2561 for i in 0..10 {
2563 validator.record_metric(StreamingMetric::FrameRendered {
2564 timestamp: i * 100, });
2566 }
2567 validator.record_metric(StreamingMetric::FrameDropped);
2568
2569 let errors = validator.validate_all();
2570 assert!(!errors.is_empty());
2571 }
2572
2573 #[test]
2574 fn test_screenshot_content_entropy_boundaries() {
2575 let mut pixels = Vec::with_capacity(1000);
2577 for i in 0..1000 {
2578 pixels.push((i % 4) as u8); }
2580 let content = ScreenshotContent::classify(&pixels);
2581 if let ScreenshotContent::UiDominated { entropy } = content {
2583 assert!(entropy < 3.0);
2584 }
2585 }
2586
2587 #[test]
2588 fn test_test_execution_stats_default() {
2589 let stats: TestExecutionStats = Default::default();
2590 assert_eq!(stats.states_captured, 0);
2591 }
2592
2593 #[test]
2594 fn test_streaming_validation_result_success() {
2595 let mut validator = StreamingUxValidator::new();
2596 validator.start();
2597 validator.record_metric(StreamingMetric::FirstByteReceived);
2598 validator.record_metric(StreamingMetric::AudioChunk {
2599 samples: 1024,
2600 sample_rate: 16000,
2601 });
2602
2603 for i in 0..60 {
2605 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 33 });
2606 }
2607
2608 validator.complete();
2609
2610 let result = validator.validate();
2611 assert!(result.is_ok());
2612 if let Ok(result) = result {
2613 assert!(result.max_latency_recorded >= Duration::ZERO);
2614 assert!(result.average_fps >= 0.0);
2615 }
2616 }
2617
2618 #[test]
2619 fn test_partial_result_clone() {
2620 let result = PartialResult {
2621 timestamp_ms: 100.0,
2622 text: "test".to_string(),
2623 is_final: false,
2624 };
2625 let cloned = result;
2626 assert_eq!(cloned.text, "test");
2627 }
2628
2629 #[test]
2630 fn test_vu_meter_sample_clone() {
2631 let sample = VuMeterSample {
2632 timestamp_ms: 100.0,
2633 level: 0.5,
2634 };
2635 let cloned = sample;
2636 assert!((cloned.level - 0.5).abs() < f32::EPSILON);
2637 }
2638
2639 #[test]
2640 fn test_streaming_metric_record_clone() {
2641 let record = StreamingMetricRecord {
2642 metric: StreamingMetric::BufferLevel(0.5),
2643 timestamp: Instant::now(),
2644 };
2645 let cloned = record;
2646 assert!(matches!(cloned.metric, StreamingMetric::BufferLevel(..)));
2647 }
2648
2649 #[test]
2650 fn test_streaming_validation_error_clone() {
2651 let err = StreamingValidationError::LatencyExceeded {
2652 measured: Duration::from_millis(150),
2653 max: Duration::from_millis(100),
2654 };
2655 let cloned = err;
2656 assert!(matches!(
2657 cloned,
2658 StreamingValidationError::LatencyExceeded { .. }
2659 ));
2660 }
2661
2662 #[test]
2663 fn test_vu_meter_error_clone() {
2664 let err = VuMeterError::Clipping(1.5);
2665 let cloned = err;
2666 assert!(matches!(cloned, VuMeterError::Clipping(..)));
2667 }
2668
2669 #[test]
2674 fn test_average_fps_with_zero_duration() {
2675 let mut validator = StreamingUxValidator::new();
2676 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2678 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2679
2680 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2682 }
2683
2684 #[test]
2685 fn test_average_fps_single_frame() {
2686 let mut validator = StreamingUxValidator::new();
2687 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2689
2690 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2691 }
2692
2693 #[test]
2694 fn test_streaming_validation_result_fields() {
2695 let mut validator = StreamingUxValidator::new()
2696 .with_max_latency(Duration::from_secs(10))
2697 .with_buffer_underrun_threshold(100)
2698 .with_max_dropped_frames(100)
2699 .with_min_fps(1.0);
2700
2701 validator.start();
2702 validator.record_metric(StreamingMetric::FirstByteReceived);
2703 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
2704
2705 for i in 0..60 {
2707 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
2708 }
2709
2710 let result = validator.validate().unwrap();
2711 assert_eq!(result.buffer_underruns, 0);
2712 assert_eq!(result.dropped_frames, 0);
2713 assert!(result.average_fps > 0.0);
2714 assert!(result.total_frames > 0);
2715 assert!(result.max_latency_recorded >= Duration::ZERO);
2716 }
2717
2718 #[test]
2719 fn test_max_recorded_latency_empty() {
2720 let validator = StreamingUxValidator::new();
2721 let result = validator.validate();
2723 assert!(result.is_ok());
2724 }
2725
2726 #[test]
2727 fn test_compression_ratio_with_zero_raw() {
2728 let mut stats = TestExecutionStats::new();
2729 stats.bytes_raw = 0;
2731 stats.bytes_compressed = 100;
2732 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
2734 }
2735
2736 #[test]
2737 fn test_throughput_with_zero_duration() {
2738 let mut stats = TestExecutionStats::new();
2739 stats.start();
2740 stats.record_state_capture(1000, 100);
2741 stats.stop();
2743
2744 let throughput = stats.compress_throughput();
2746 assert!(throughput >= 0.0);
2747 }
2748
2749 #[test]
2750 fn test_vu_meter_error_stale_clone() {
2751 let err = VuMeterError::Stale {
2752 last_update_ms: 100,
2753 current_ms: 200,
2754 };
2755 let cloned = err;
2756 assert!(matches!(
2757 cloned,
2758 VuMeterError::Stale {
2759 last_update_ms: 100,
2760 current_ms: 200,
2761 }
2762 ));
2763 }
2764
2765 #[test]
2766 fn test_vu_meter_error_slow_update_rate_clone() {
2767 let err = VuMeterError::SlowUpdateRate {
2768 measured_hz: 15.0,
2769 expected_hz: 30.0,
2770 };
2771 let cloned = err;
2772 match cloned {
2773 VuMeterError::SlowUpdateRate {
2774 measured_hz,
2775 expected_hz,
2776 } => {
2777 assert!((measured_hz - 15.0).abs() < f32::EPSILON);
2778 assert!((expected_hz - 30.0).abs() < f32::EPSILON);
2779 }
2780 _ => panic!("Expected SlowUpdateRate"),
2781 }
2782 }
2783
2784 #[test]
2785 fn test_vu_meter_error_not_animating_clone() {
2786 let err = VuMeterError::NotAnimating {
2787 sample_count: 10,
2788 value: 0.5,
2789 };
2790 let cloned = err;
2791 match cloned {
2792 VuMeterError::NotAnimating {
2793 sample_count,
2794 value,
2795 } => {
2796 assert_eq!(sample_count, 10);
2797 assert!((value - 0.5).abs() < f32::EPSILON);
2798 }
2799 _ => panic!("Expected NotAnimating"),
2800 }
2801 }
2802
2803 #[test]
2804 fn test_streaming_validation_error_all_clone_variants() {
2805 let errors: Vec<StreamingValidationError> = vec![
2807 StreamingValidationError::LatencyExceeded {
2808 measured: Duration::from_millis(200),
2809 max: Duration::from_millis(100),
2810 },
2811 StreamingValidationError::BufferUnderrunThreshold {
2812 count: 10,
2813 threshold: 5,
2814 },
2815 StreamingValidationError::DroppedFrameThreshold { count: 20, max: 10 },
2816 StreamingValidationError::FpsBelowMinimum {
2817 measured: 15.0,
2818 min: 30.0,
2819 },
2820 StreamingValidationError::TtfbExceeded {
2821 measured: Duration::from_secs(5),
2822 max: Duration::from_secs(2),
2823 },
2824 StreamingValidationError::InvalidStateTransition {
2825 from: StreamingState::Idle,
2826 to: StreamingState::Completed,
2827 },
2828 StreamingValidationError::EndedInError,
2829 ];
2830
2831 for err in errors {
2832 let cloned = err.clone();
2833 let _ = cloned.to_string();
2835 }
2836 }
2837
2838 #[test]
2839 fn test_streaming_metric_clone_all_variants() {
2840 let metrics = vec![
2841 StreamingMetric::Latency(Duration::from_millis(100)),
2842 StreamingMetric::FrameRendered { timestamp: 1000 },
2843 StreamingMetric::FrameDropped,
2844 StreamingMetric::BufferUnderrun,
2845 StreamingMetric::FirstByteReceived,
2846 StreamingMetric::BufferLevel(0.75),
2847 StreamingMetric::AudioChunk {
2848 samples: 2048,
2849 sample_rate: 44100,
2850 },
2851 ];
2852
2853 for metric in metrics {
2854 let cloned = metric.clone();
2855 let _ = format!("{:?}", cloned);
2856 }
2857 }
2858
2859 #[test]
2860 fn test_buffer_level_no_transition_when_not_streaming() {
2861 let mut validator = StreamingUxValidator::new();
2862 validator.record_metric(StreamingMetric::BufferLevel(0.05));
2864 assert_eq!(validator.state(), StreamingState::Idle);
2865
2866 validator.record_metric(StreamingMetric::BufferLevel(0.5));
2868 assert_eq!(validator.state(), StreamingState::Idle);
2869 }
2870
2871 #[test]
2872 fn test_latency_no_transition_when_exceeds_max() {
2873 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
2874 validator.start();
2875 assert_eq!(validator.state(), StreamingState::Buffering);
2876
2877 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
2879 assert_eq!(validator.state(), StreamingState::Buffering);
2880 }
2881
2882 #[test]
2883 fn test_frame_rendered_no_transition_when_not_stalled() {
2884 let mut validator = StreamingUxValidator::new();
2885 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2887 assert_eq!(validator.state(), StreamingState::Idle);
2888
2889 validator.start();
2891 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 200 });
2892 assert_eq!(validator.state(), StreamingState::Buffering);
2893 }
2894
2895 #[test]
2896 fn test_audio_chunk_no_transition_when_not_buffering() {
2897 let mut validator = StreamingUxValidator::new();
2898 validator.record_metric(StreamingMetric::AudioChunk {
2900 samples: 1024,
2901 sample_rate: 16000,
2902 });
2903 assert_eq!(validator.state(), StreamingState::Idle);
2904
2905 validator.start();
2907 validator.record_metric(StreamingMetric::AudioChunk {
2908 samples: 1024,
2909 sample_rate: 16000,
2910 });
2911 assert_eq!(validator.state(), StreamingState::Streaming);
2912 validator.record_metric(StreamingMetric::AudioChunk {
2913 samples: 1024,
2914 sample_rate: 16000,
2915 });
2916 assert_eq!(validator.state(), StreamingState::Streaming);
2917 }
2918
2919 #[test]
2920 fn test_first_byte_no_transition_when_not_idle() {
2921 let mut validator = StreamingUxValidator::new();
2922 validator.start(); validator.record_metric(StreamingMetric::FirstByteReceived);
2924 assert_eq!(validator.state(), StreamingState::Buffering);
2925 }
2926
2927 #[test]
2928 fn test_buffer_underrun_no_transition_when_not_streaming() {
2929 let mut validator = StreamingUxValidator::new();
2930 validator.record_metric(StreamingMetric::BufferUnderrun);
2932 assert_eq!(validator.state(), StreamingState::Idle);
2933 assert_eq!(validator.buffer_underruns(), 1);
2934
2935 validator.start();
2937 validator.record_metric(StreamingMetric::BufferUnderrun);
2938 assert_eq!(validator.state(), StreamingState::Buffering);
2939 assert_eq!(validator.buffer_underruns(), 2);
2940 }
2941
2942 #[test]
2943 fn test_transition_to_same_state() {
2944 let mut validator = StreamingUxValidator::new();
2945 validator.start();
2946 let history_len = validator.state_history().len();
2947
2948 validator.record_metric(StreamingMetric::BufferLevel(0.5)); assert_eq!(validator.state_history().len(), history_len);
2951 }
2952
2953 #[test]
2954 fn test_screenshot_content_single_byte() {
2955 let content = ScreenshotContent::classify(&[128]);
2957 assert!(matches!(
2958 content,
2959 ScreenshotContent::Uniform { fill_value: 128 }
2960 ));
2961 }
2962
2963 #[test]
2964 fn test_screenshot_content_two_bytes_same() {
2965 let content = ScreenshotContent::classify(&[42, 42]);
2966 assert!(matches!(
2967 content,
2968 ScreenshotContent::Uniform { fill_value: 42 }
2969 ));
2970 }
2971
2972 #[test]
2973 fn test_screenshot_content_near_uniform_threshold() {
2974 let mut pixels = vec![255u8; 94];
2976 pixels.extend(vec![0u8; 6]);
2977 let content = ScreenshotContent::classify(&pixels);
2978 assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
2979 }
2980
2981 #[test]
2982 fn test_screenshot_content_exactly_at_uniform_threshold() {
2983 let mut pixels = vec![255u8; 96];
2985 pixels.extend(vec![0u8; 4]);
2986 let content = ScreenshotContent::classify(&pixels);
2987 assert!(matches!(
2988 content,
2989 ScreenshotContent::Uniform { fill_value: 255 }
2990 ));
2991 }
2992
2993 #[test]
2994 fn test_screenshot_content_entropy_at_boundary_3() {
2995 let mut pixels = Vec::new();
2997 for _ in 0..125 {
2999 for v in 0u8..8u8 {
3000 pixels.push(v);
3001 }
3002 }
3003 let content = ScreenshotContent::classify(&pixels);
3004 let entropy = content.entropy();
3006 assert!((2.5..=3.5).contains(&entropy));
3007 }
3008
3009 #[test]
3010 fn test_screenshot_content_entropy_at_boundary_6() {
3011 let mut pixels = Vec::new();
3013 for _ in 0..16 {
3015 for v in 0u8..64u8 {
3016 pixels.push(v);
3017 }
3018 }
3019 let content = ScreenshotContent::classify(&pixels);
3020 let entropy = content.entropy();
3021 assert!((5.5..=6.5).contains(&entropy));
3022 }
3023
3024 #[test]
3025 fn test_screenshot_content_maximum_entropy() {
3026 let mut pixels = Vec::new();
3028 for _ in 0..4 {
3029 for v in 0u8..=255u8 {
3030 pixels.push(v);
3031 }
3032 }
3033 let content = ScreenshotContent::classify(&pixels);
3034 assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3035 assert!(content.entropy() > 7.0);
3036 }
3037
3038 #[test]
3039 fn test_validate_multiple_latency_exceeded() {
3040 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
3041
3042 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
3044 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(150)));
3045 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(200)));
3046
3047 let errors = validator.validate_all();
3048 assert_eq!(errors.len(), 3);
3049 for err in errors {
3050 assert!(matches!(
3051 err,
3052 StreamingValidationError::LatencyExceeded { .. }
3053 ));
3054 }
3055 }
3056
3057 #[test]
3058 fn test_streaming_validation_result_debug() {
3059 let result = StreamingValidationResult {
3060 buffer_underruns: 2,
3061 dropped_frames: 5,
3062 average_fps: 30.0,
3063 max_latency_recorded: Duration::from_millis(100),
3064 total_frames: 1000,
3065 };
3066 let debug = format!("{:?}", result);
3067 assert!(debug.contains("StreamingValidationResult"));
3068 assert!(debug.contains("buffer_underruns"));
3069 }
3070
3071 #[test]
3072 fn test_streaming_validation_result_clone() {
3073 let result = StreamingValidationResult {
3074 buffer_underruns: 3,
3075 dropped_frames: 7,
3076 average_fps: 60.0,
3077 max_latency_recorded: Duration::from_millis(50),
3078 total_frames: 2000,
3079 };
3080 let cloned = result;
3081 assert_eq!(cloned.buffer_underruns, 3);
3082 assert_eq!(cloned.dropped_frames, 7);
3083 assert!((cloned.average_fps - 60.0).abs() < f64::EPSILON);
3084 assert_eq!(cloned.max_latency_recorded, Duration::from_millis(50));
3085 assert_eq!(cloned.total_frames, 2000);
3086 }
3087
3088 #[test]
3089 fn test_vu_meter_config_smoothing_tolerance() {
3090 let config = VuMeterConfig {
3091 min_level: 0.0,
3092 max_level: 1.0,
3093 update_rate_hz: 30.0,
3094 smoothing_tolerance: 0.2,
3095 max_stale_ms: 100,
3096 };
3097
3098 assert!(config.validate_sample(1.19).is_ok());
3100
3101 assert!(config.validate_sample(1.21).is_err());
3103 }
3104
3105 #[test]
3106 fn test_compression_algorithm_debug() {
3107 let algos = [
3108 CompressionAlgorithm::Lz4,
3109 CompressionAlgorithm::Zstd,
3110 CompressionAlgorithm::Png,
3111 CompressionAlgorithm::Rle,
3112 ];
3113 for algo in algos {
3114 let debug = format!("{:?}", algo);
3115 assert!(!debug.is_empty());
3116 }
3117 }
3118
3119 #[test]
3120 fn test_compression_algorithm_copy() {
3121 let algo = CompressionAlgorithm::Lz4;
3122 let copied = algo;
3123 assert_eq!(algo, copied);
3124 }
3125
3126 #[test]
3127 fn test_test_execution_stats_large_values() {
3128 let mut stats = TestExecutionStats::new();
3129 stats.record_state_capture(u64::MAX / 2, u64::MAX / 4);
3130
3131 let ratio = stats.compression_ratio();
3133 assert!(ratio > 0.0);
3134
3135 let efficiency = stats.efficiency();
3136 assert!(efficiency > 0.0 && efficiency < 1.0);
3137 }
3138
3139 #[test]
3140 fn test_storage_savings_small_values() {
3141 let mut stats = TestExecutionStats::new();
3142 stats.record_state_capture(500_000, 400_000); let savings = stats.storage_savings_mb();
3145 assert!((savings - 0.1).abs() < 0.01);
3146 }
3147
3148 #[test]
3149 fn test_storage_savings_compressed_larger_than_raw() {
3150 let mut stats = TestExecutionStats::new();
3151 stats.bytes_raw = 100;
3153 stats.bytes_compressed = 200;
3154
3155 let savings = stats.storage_savings_mb();
3156 assert!((savings - 0.0).abs() < f64::EPSILON);
3157 }
3158
3159 #[test]
3160 fn test_same_fill_exactly_at_threshold() {
3161 let mut stats = TestExecutionStats::new();
3162 stats.record_state_capture(1000, 100);
3164 assert_eq!(stats.same_fill_pages, 0);
3166
3167 stats.record_state_capture(1000, 99);
3169 assert_eq!(stats.same_fill_pages, 1);
3170 }
3171
3172 #[test]
3173 fn test_streaming_ux_validator_debug() {
3174 let validator = StreamingUxValidator::new();
3175 let debug = format!("{:?}", validator);
3176 assert!(debug.contains("StreamingUxValidator"));
3177 }
3178
3179 #[test]
3180 fn test_streaming_metric_record_debug() {
3181 let record = StreamingMetricRecord {
3182 metric: StreamingMetric::FrameDropped,
3183 timestamp: Instant::now(),
3184 };
3185 let debug = format!("{:?}", record);
3186 assert!(debug.contains("StreamingMetricRecord"));
3187 }
3188
3189 #[test]
3190 fn test_validate_all_empty_metrics() {
3191 let validator = StreamingUxValidator::new();
3192 let errors = validator.validate_all();
3193 assert!(errors.is_empty());
3194 }
3195
3196 #[test]
3197 fn test_validate_with_error_state() {
3198 let mut validator = StreamingUxValidator::new();
3199 validator.error();
3200
3201 let result = validator.validate();
3202 assert!(result.is_err());
3203 assert!(matches!(
3204 result.unwrap_err(),
3205 StreamingValidationError::EndedInError
3206 ));
3207 }
3208
3209 #[test]
3210 fn test_validate_all_multiple_error_types() {
3211 let mut validator = StreamingUxValidator::new()
3212 .with_max_latency(Duration::from_millis(10))
3213 .with_buffer_underrun_threshold(0)
3214 .with_max_dropped_frames(0)
3215 .with_min_fps(100.0);
3216
3217 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3218 validator.record_metric(StreamingMetric::BufferUnderrun);
3219 validator.record_metric(StreamingMetric::FrameDropped);
3220
3221 for i in 0..5 {
3223 validator.record_metric(StreamingMetric::FrameRendered {
3224 timestamp: i * 500, });
3226 }
3227
3228 validator.error();
3229
3230 let errors = validator.validate_all();
3231 assert!(errors.len() >= 4);
3233 }
3234
3235 #[test]
3236 fn test_vu_meter_error_negative_level_value() {
3237 let config = VuMeterConfig::default();
3238 let result = config.validate_sample(-10.5);
3239 match result {
3240 Err(VuMeterError::NegativeLevel(v)) => {
3241 assert!((v - (-10.5)).abs() < f32::EPSILON);
3242 }
3243 _ => panic!("Expected NegativeLevel error"),
3244 }
3245 }
3246
3247 #[test]
3248 fn test_vu_meter_error_clipping_value() {
3249 let config = VuMeterConfig::default().with_max_level(0.5);
3250 let result = config.validate_sample(2.0);
3251 match result {
3252 Err(VuMeterError::Clipping(v)) => {
3253 assert!((v - 2.0).abs() < f32::EPSILON);
3254 }
3255 _ => panic!("Expected Clipping error"),
3256 }
3257 }
3258
3259 #[test]
3260 fn test_streaming_state_hash() {
3261 use std::collections::HashSet;
3262 let mut set = HashSet::new();
3263 set.insert(StreamingState::Idle);
3264 set.insert(StreamingState::Buffering);
3265 set.insert(StreamingState::Streaming);
3266 set.insert(StreamingState::Stalled);
3267 set.insert(StreamingState::Error);
3268 set.insert(StreamingState::Completed);
3269
3270 assert_eq!(set.len(), 6);
3271 assert!(set.contains(&StreamingState::Idle));
3272 }
3273
3274 #[test]
3275 fn test_screenshot_content_clone() {
3276 let contents = vec![
3277 ScreenshotContent::Uniform { fill_value: 128 },
3278 ScreenshotContent::UiDominated { entropy: 2.5 },
3279 ScreenshotContent::GameWorld { entropy: 4.5 },
3280 ScreenshotContent::HighEntropy { entropy: 7.0 },
3281 ];
3282
3283 for content in contents {
3284 let cloned = content.clone();
3285 assert!((cloned.entropy() - content.entropy()).abs() < f32::EPSILON);
3286 }
3287 }
3288
3289 #[test]
3290 fn test_test_execution_stats_all_fields() {
3291 let mut stats = TestExecutionStats::new();
3292 stats.start();
3293 stats.record_state_capture(1000, 100);
3294 stats.record_state_capture(2000, 50); stats.stop();
3296
3297 assert_eq!(stats.states_captured, 2);
3298 assert_eq!(stats.bytes_raw, 3000);
3299 assert_eq!(stats.bytes_compressed, 150);
3300 assert_eq!(stats.same_fill_pages, 1);
3301 }
3302
3303 #[test]
3304 fn test_frame_times_exactly_120() {
3305 let mut validator = StreamingUxValidator::new();
3306 for i in 0..120 {
3308 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3309 }
3310 assert!(validator.average_fps() > 0.0);
3311 }
3312
3313 #[test]
3314 fn test_frame_times_121() {
3315 let mut validator = StreamingUxValidator::new();
3316 for i in 0..121 {
3318 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3319 }
3320 assert!(validator.average_fps() > 0.0);
3322 }
3323
3324 #[test]
3325 fn test_state_transition_fields_access() {
3326 let transition = StateTransition {
3327 from: "State1".to_string(),
3328 to: "State2".to_string(),
3329 timestamp_ms: 12345.67,
3330 duration_ms: 890.12,
3331 };
3332
3333 assert_eq!(transition.from.as_str(), "State1");
3334 assert_eq!(transition.to.as_str(), "State2");
3335 assert!((transition.timestamp_ms - 12345.67).abs() < f64::EPSILON);
3336 assert!((transition.duration_ms - 890.12).abs() < f64::EPSILON);
3337 }
3338
3339 #[test]
3340 fn test_partial_result_fields_access() {
3341 let partial = PartialResult {
3342 timestamp_ms: 999.99,
3343 text: "Hello World".to_string(),
3344 is_final: true,
3345 };
3346
3347 assert!((partial.timestamp_ms - 999.99).abs() < f64::EPSILON);
3348 assert_eq!(partial.text.as_str(), "Hello World");
3349 assert!(partial.is_final);
3350 }
3351
3352 #[test]
3353 fn test_vu_meter_sample_fields_access() {
3354 let sample = VuMeterSample {
3355 timestamp_ms: 1234.5,
3356 level: 0.789,
3357 };
3358
3359 assert!((sample.timestamp_ms - 1234.5).abs() < f64::EPSILON);
3360 assert!((sample.level - 0.789).abs() < f32::EPSILON);
3361 }
3362
3363 #[test]
3364 fn test_streaming_metric_record_fields_access() {
3365 let timestamp = Instant::now();
3366 let record = StreamingMetricRecord {
3367 metric: StreamingMetric::BufferLevel(0.42),
3368 timestamp,
3369 };
3370
3371 assert!(matches!(record.metric, StreamingMetric::BufferLevel(..)));
3372 assert_eq!(record.timestamp, timestamp);
3373 }
3374
3375 #[test]
3376 fn test_validate_returns_first_error() {
3377 let mut validator = StreamingUxValidator::new()
3378 .with_max_latency(Duration::from_millis(10))
3379 .with_buffer_underrun_threshold(0);
3380
3381 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3383 validator.record_metric(StreamingMetric::BufferUnderrun);
3384
3385 let result = validator.validate();
3386 assert!(result.is_err());
3387 assert!(matches!(
3389 result.unwrap_err(),
3390 StreamingValidationError::LatencyExceeded { .. }
3391 ));
3392 }
3393
3394 #[test]
3395 fn test_validate_fps_error_only_when_positive() {
3396 let mut validator = StreamingUxValidator::new().with_min_fps(100.0);
3397
3398 let result = validator.validate();
3400 assert!(result.is_ok());
3401
3402 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 0 });
3404 let result = validator.validate();
3405 assert!(result.is_ok());
3406 }
3407
3408 #[test]
3409 fn test_buffer_level_recovery_threshold() {
3410 let mut validator = StreamingUxValidator::new();
3411 validator.start();
3412 validator.record_metric(StreamingMetric::AudioChunk {
3413 samples: 1024,
3414 sample_rate: 16000,
3415 });
3416 assert_eq!(validator.state(), StreamingState::Streaming);
3417
3418 validator.record_metric(StreamingMetric::BufferLevel(0.05));
3420 assert_eq!(validator.state(), StreamingState::Stalled);
3421
3422 validator.record_metric(StreamingMetric::BufferLevel(0.3));
3424 assert_eq!(validator.state(), StreamingState::Stalled);
3425
3426 validator.record_metric(StreamingMetric::BufferLevel(0.31));
3428 assert_eq!(validator.state(), StreamingState::Streaming);
3429 }
3430
3431 #[test]
3432 fn test_buffer_level_stall_threshold() {
3433 let mut validator = StreamingUxValidator::new();
3434 validator.start();
3435 validator.record_metric(StreamingMetric::AudioChunk {
3436 samples: 1024,
3437 sample_rate: 16000,
3438 });
3439 assert_eq!(validator.state(), StreamingState::Streaming);
3440
3441 validator.record_metric(StreamingMetric::BufferLevel(0.1));
3443 assert_eq!(validator.state(), StreamingState::Streaming);
3444
3445 validator.record_metric(StreamingMetric::BufferLevel(0.09));
3447 assert_eq!(validator.state(), StreamingState::Stalled);
3448 }
3449
3450 #[test]
3455 fn test_vu_meter_config_default_values() {
3456 let config = VuMeterConfig::default();
3457 assert!((config.min_level - 0.0).abs() < f32::EPSILON);
3458 assert!((config.max_level - 1.0).abs() < f32::EPSILON);
3459 assert!((config.update_rate_hz - 30.0).abs() < f32::EPSILON);
3460 assert!((config.smoothing_tolerance - 0.1).abs() < f32::EPSILON);
3461 assert_eq!(config.max_stale_ms, 100);
3462 }
3463
3464 #[test]
3465 fn test_vu_meter_error_display_all_variants() {
3466 let err = VuMeterError::NegativeLevel(-0.25);
3468 let display = format!("{}", err);
3469 assert!(display.contains("-0.25"));
3470 assert!(display.contains("negative"));
3471
3472 let err = VuMeterError::Clipping(1.75);
3474 let display = format!("{}", err);
3475 assert!(display.contains("1.75"));
3476 assert!(display.contains("clipping"));
3477
3478 let err = VuMeterError::Stale {
3480 last_update_ms: 50,
3481 current_ms: 250,
3482 };
3483 let display = format!("{}", err);
3484 assert!(display.contains("200ms"));
3485
3486 let err = VuMeterError::SlowUpdateRate {
3488 measured_hz: 20.0,
3489 expected_hz: 60.0,
3490 };
3491 let display = format!("{}", err);
3492 assert!(display.contains("20.0Hz"));
3493 assert!(display.contains("60.0Hz"));
3494
3495 let err = VuMeterError::NotAnimating {
3497 sample_count: 50,
3498 value: 0.75,
3499 };
3500 let display = format!("{}", err);
3501 assert!(display.contains("50 samples"));
3502 assert!(display.contains("0.75"));
3503 }
3504
3505 #[test]
3506 fn test_streaming_validation_error_display_all_variants() {
3507 let err = StreamingValidationError::LatencyExceeded {
3508 measured: Duration::from_millis(300),
3509 max: Duration::from_millis(100),
3510 };
3511 assert!(err.to_string().contains("300"));
3512
3513 let err = StreamingValidationError::BufferUnderrunThreshold {
3514 count: 15,
3515 threshold: 5,
3516 };
3517 assert!(err.to_string().contains("15"));
3518 assert!(err.to_string().contains('5'));
3519
3520 let err = StreamingValidationError::DroppedFrameThreshold { count: 25, max: 10 };
3521 assert!(err.to_string().contains("25"));
3522 assert!(err.to_string().contains("10"));
3523
3524 let err = StreamingValidationError::FpsBelowMinimum {
3525 measured: 20.5,
3526 min: 60.0,
3527 };
3528 assert!(err.to_string().contains("20.5"));
3529 assert!(err.to_string().contains("60.0"));
3530
3531 let err = StreamingValidationError::TtfbExceeded {
3532 measured: Duration::from_secs(10),
3533 max: Duration::from_secs(3),
3534 };
3535 let display = err.to_string();
3536 assert!(display.contains("first byte"));
3537
3538 let err = StreamingValidationError::InvalidStateTransition {
3539 from: StreamingState::Buffering,
3540 to: StreamingState::Completed,
3541 };
3542 let display = err.to_string();
3543 assert!(display.contains("Buffering"));
3544 assert!(display.contains("Completed"));
3545
3546 let err = StreamingValidationError::EndedInError;
3547 assert!(err.to_string().contains("error state"));
3548 }
3549
3550 #[test]
3551 fn test_streaming_state_display_coverage() {
3552 assert_eq!(format!("{}", StreamingState::Idle), "Idle");
3554 assert_eq!(format!("{}", StreamingState::Buffering), "Buffering");
3555 assert_eq!(format!("{}", StreamingState::Streaming), "Streaming");
3556 assert_eq!(format!("{}", StreamingState::Stalled), "Stalled");
3557 assert_eq!(format!("{}", StreamingState::Error), "Error");
3558 assert_eq!(format!("{}", StreamingState::Completed), "Completed");
3559 }
3560
3561 #[test]
3562 fn test_test_execution_stats_zero_raw_bytes() {
3563 let stats = TestExecutionStats::new();
3564 assert!((stats.efficiency() - 0.0).abs() < f64::EPSILON);
3566 }
3567
3568 #[test]
3569 fn test_test_execution_stats_zero_compressed_bytes() {
3570 let mut stats = TestExecutionStats::new();
3571 stats.bytes_raw = 1000;
3572 stats.bytes_compressed = 0;
3573 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
3575 }
3576
3577 #[test]
3578 fn test_test_execution_stats_zero_states_captured() {
3579 let stats = TestExecutionStats::new();
3580 assert!((stats.same_fill_ratio() - 0.0).abs() < f64::EPSILON);
3582 }
3583
3584 #[test]
3585 fn test_test_execution_stats_throughput_no_start() {
3586 let mut stats = TestExecutionStats::new();
3587 stats.stop();
3588 stats.record_state_capture(1000, 100);
3589 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3591 }
3592
3593 #[test]
3594 fn test_test_execution_stats_throughput_only_end() {
3595 let mut stats = TestExecutionStats::new();
3596 stats.stop();
3597 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3599 }
3600
3601 #[test]
3602 fn test_test_execution_stats_same_fill_with_zero_raw() {
3603 let mut stats = TestExecutionStats::new();
3604 stats.record_state_capture(0, 0);
3606 assert_eq!(stats.same_fill_pages, 0);
3607 }
3608
3609 #[test]
3610 fn test_screenshot_content_classify_single_pixel() {
3611 let content = ScreenshotContent::classify(&[42]);
3613 assert!(matches!(
3614 content,
3615 ScreenshotContent::Uniform { fill_value: 42 }
3616 ));
3617 }
3618
3619 #[test]
3620 fn test_screenshot_content_all_different_values() {
3621 let pixels: Vec<u8> = (0..=255).collect();
3623 let content = ScreenshotContent::classify(&pixels);
3624 assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3625 assert!(content.entropy() > 7.5);
3627 }
3628
3629 #[test]
3630 fn test_screenshot_content_entropy_method_uniform() {
3631 let content = ScreenshotContent::Uniform { fill_value: 200 };
3632 assert!((content.entropy() - 0.0).abs() < f32::EPSILON);
3633 }
3634
3635 #[test]
3636 fn test_screenshot_content_recommended_algorithm_all() {
3637 assert_eq!(
3638 ScreenshotContent::Uniform { fill_value: 0 }.recommended_algorithm(),
3639 CompressionAlgorithm::Rle
3640 );
3641 assert_eq!(
3642 ScreenshotContent::UiDominated { entropy: 2.0 }.recommended_algorithm(),
3643 CompressionAlgorithm::Png
3644 );
3645 assert_eq!(
3646 ScreenshotContent::GameWorld { entropy: 4.5 }.recommended_algorithm(),
3647 CompressionAlgorithm::Zstd
3648 );
3649 assert_eq!(
3650 ScreenshotContent::HighEntropy { entropy: 7.0 }.recommended_algorithm(),
3651 CompressionAlgorithm::Lz4
3652 );
3653 }
3654
3655 #[test]
3656 fn test_screenshot_content_expected_ratio_hint_all() {
3657 assert!(ScreenshotContent::Uniform { fill_value: 0 }
3658 .expected_ratio_hint()
3659 .contains("excellent"));
3660 assert!(ScreenshotContent::UiDominated { entropy: 2.0 }
3661 .expected_ratio_hint()
3662 .contains("good"));
3663 assert!(ScreenshotContent::GameWorld { entropy: 4.5 }
3664 .expected_ratio_hint()
3665 .contains("moderate"));
3666 assert!(ScreenshotContent::HighEntropy { entropy: 7.0 }
3667 .expected_ratio_hint()
3668 .contains("poor"));
3669 }
3670
3671 #[test]
3672 fn test_streaming_ux_validator_builder_chain() {
3673 let validator = StreamingUxValidator::new()
3674 .with_max_latency(Duration::from_millis(150))
3675 .with_buffer_underrun_threshold(10)
3676 .with_max_dropped_frames(20)
3677 .with_min_fps(45.0)
3678 .with_ttfb_timeout(Duration::from_secs(5));
3679
3680 assert_eq!(validator.max_latency, Duration::from_millis(150));
3681 assert_eq!(validator.buffer_underrun_threshold, 10);
3682 assert_eq!(validator.max_dropped_frames, 20);
3683 assert!((validator.min_fps - 45.0).abs() < f64::EPSILON);
3684 assert_eq!(validator.ttfb_timeout, Duration::from_secs(5));
3685 }
3686
3687 #[test]
3688 fn test_streaming_validator_for_audio_preset() {
3689 let validator = StreamingUxValidator::for_audio();
3690 assert_eq!(validator.max_latency, Duration::from_millis(100));
3691 assert_eq!(validator.buffer_underrun_threshold, 3);
3692 assert_eq!(validator.ttfb_timeout, Duration::from_secs(2));
3693 }
3694
3695 #[test]
3696 fn test_streaming_validator_for_video_preset() {
3697 let validator = StreamingUxValidator::for_video();
3698 assert_eq!(validator.max_latency, Duration::from_millis(500));
3699 assert!((validator.min_fps - 30.0).abs() < f64::EPSILON);
3700 assert_eq!(validator.max_dropped_frames, 5);
3701 }
3702
3703 #[test]
3704 fn test_streaming_validator_average_fps_no_frames() {
3705 let validator = StreamingUxValidator::new();
3706 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3707 }
3708
3709 #[test]
3710 fn test_streaming_validator_average_fps_one_frame() {
3711 let mut validator = StreamingUxValidator::new();
3712 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3713 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3714 }
3715
3716 #[test]
3717 fn test_streaming_validator_average_fps_same_timestamp() {
3718 let mut validator = StreamingUxValidator::new();
3719 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3720 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3721 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3722 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3724 }
3725
3726 #[test]
3727 fn test_streaming_validator_max_recorded_latency_none() {
3728 let validator = StreamingUxValidator::new();
3729 let result = validator.validate();
3730 assert!(result.is_ok());
3731 let res = result.unwrap();
3732 assert_eq!(res.max_latency_recorded, Duration::ZERO);
3733 }
3734
3735 #[test]
3736 fn test_streaming_validator_validate_no_ttfb() {
3737 let mut validator = StreamingUxValidator::new();
3738 validator.start();
3739 let result = validator.validate();
3741 assert!(result.is_ok());
3742 }
3743
3744 #[test]
3745 fn test_streaming_validator_complete_and_validate() {
3746 let mut validator = StreamingUxValidator::new();
3747 validator.complete();
3748 assert_eq!(validator.state(), StreamingState::Completed);
3749 let result = validator.validate();
3750 assert!(result.is_ok());
3751 }
3752
3753 #[test]
3754 fn test_streaming_validator_error_and_validate() {
3755 let mut validator = StreamingUxValidator::new();
3756 validator.error();
3757 assert_eq!(validator.state(), StreamingState::Error);
3758 let result = validator.validate();
3759 assert!(result.is_err());
3760 }
3761
3762 #[test]
3763 fn test_streaming_validator_state_history_empty() {
3764 let validator = StreamingUxValidator::new();
3765 assert!(validator.state_history().is_empty());
3766 }
3767
3768 #[test]
3769 fn test_streaming_validator_state_history_with_transitions() {
3770 let mut validator = StreamingUxValidator::new();
3771 validator.start();
3772 validator.record_metric(StreamingMetric::AudioChunk {
3773 samples: 1024,
3774 sample_rate: 16000,
3775 });
3776 validator.complete();
3777
3778 let history = validator.state_history();
3779 assert!(history.len() >= 2);
3780 }
3781
3782 #[test]
3783 fn test_streaming_validator_reset_full() {
3784 let mut validator = StreamingUxValidator::new();
3785 validator.start();
3786 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3787 validator.record_metric(StreamingMetric::BufferUnderrun);
3788 validator.record_metric(StreamingMetric::FrameDropped);
3789 validator.record_metric(StreamingMetric::FirstByteReceived);
3790 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
3791
3792 validator.reset();
3793
3794 assert_eq!(validator.state(), StreamingState::Idle);
3795 assert_eq!(validator.buffer_underruns(), 0);
3796 assert_eq!(validator.dropped_frames(), 0);
3797 assert!(validator.state_history().is_empty());
3798 }
3799
3800 #[test]
3801 fn test_streaming_validator_validate_all_with_error_state() {
3802 let mut validator = StreamingUxValidator::new();
3803 validator.error();
3804 let errors = validator.validate_all();
3805 assert!(errors
3806 .iter()
3807 .any(|e| matches!(e, StreamingValidationError::EndedInError)));
3808 }
3809
3810 #[test]
3811 fn test_streaming_validation_result_fields_all() {
3812 let result = StreamingValidationResult {
3813 buffer_underruns: 1,
3814 dropped_frames: 2,
3815 average_fps: 30.0,
3816 max_latency_recorded: Duration::from_millis(50),
3817 total_frames: 100,
3818 };
3819 assert_eq!(result.buffer_underruns, 1);
3820 assert_eq!(result.dropped_frames, 2);
3821 assert!((result.average_fps - 30.0).abs() < f64::EPSILON);
3822 assert_eq!(result.max_latency_recorded, Duration::from_millis(50));
3823 assert_eq!(result.total_frames, 100);
3824 }
3825
3826 #[test]
3827 fn test_state_transition_struct_fields_all() {
3828 let transition = StateTransition {
3829 from: "StateA".to_string(),
3830 to: "StateB".to_string(),
3831 timestamp_ms: 100.0,
3832 duration_ms: 50.0,
3833 };
3834 assert_eq!(&transition.from, "StateA");
3835 assert_eq!(&transition.to, "StateB");
3836 assert!((transition.timestamp_ms - 100.0).abs() < f64::EPSILON);
3837 assert!((transition.duration_ms - 50.0).abs() < f64::EPSILON);
3838 }
3839
3840 #[test]
3841 fn test_partial_result_struct_fields() {
3842 let partial = PartialResult {
3843 timestamp_ms: 200.0,
3844 text: "partial text".to_string(),
3845 is_final: false,
3846 };
3847 assert!((partial.timestamp_ms - 200.0).abs() < f64::EPSILON);
3848 assert_eq!(&partial.text, "partial text");
3849 assert!(!partial.is_final);
3850
3851 let final_result = PartialResult {
3852 timestamp_ms: 300.0,
3853 text: "final text".to_string(),
3854 is_final: true,
3855 };
3856 assert!(final_result.is_final);
3857 }
3858
3859 #[test]
3860 fn test_vu_meter_sample_struct_fields() {
3861 let sample = VuMeterSample {
3862 timestamp_ms: 150.0,
3863 level: 0.65,
3864 };
3865 assert!((sample.timestamp_ms - 150.0).abs() < f64::EPSILON);
3866 assert!((sample.level - 0.65).abs() < f32::EPSILON);
3867 }
3868
3869 #[test]
3870 fn test_streaming_metric_record_struct() {
3871 let now = Instant::now();
3872 let record = StreamingMetricRecord {
3873 metric: StreamingMetric::FrameDropped,
3874 timestamp: now,
3875 };
3876 assert!(matches!(record.metric, StreamingMetric::FrameDropped));
3877 assert_eq!(record.timestamp, now);
3878 }
3879
3880 #[test]
3881 fn test_streaming_metric_latency_variant() {
3882 let metric = StreamingMetric::Latency(Duration::from_millis(123));
3883 if let StreamingMetric::Latency(d) = metric {
3884 assert_eq!(d, Duration::from_millis(123));
3885 } else {
3886 panic!("Expected Latency variant");
3887 }
3888 }
3889
3890 #[test]
3891 fn test_streaming_metric_frame_rendered_variant() {
3892 let metric = StreamingMetric::FrameRendered { timestamp: 999 };
3893 if let StreamingMetric::FrameRendered { timestamp } = metric {
3894 assert_eq!(timestamp, 999);
3895 } else {
3896 panic!("Expected FrameRendered variant");
3897 }
3898 }
3899
3900 #[test]
3901 fn test_streaming_metric_buffer_level_variant() {
3902 let metric = StreamingMetric::BufferLevel(0.42);
3903 if let StreamingMetric::BufferLevel(level) = metric {
3904 assert!((level - 0.42).abs() < f32::EPSILON);
3905 } else {
3906 panic!("Expected BufferLevel variant");
3907 }
3908 }
3909
3910 #[test]
3911 fn test_streaming_metric_audio_chunk_variant() {
3912 let metric = StreamingMetric::AudioChunk {
3913 samples: 2048,
3914 sample_rate: 44100,
3915 };
3916 if let StreamingMetric::AudioChunk {
3917 samples,
3918 sample_rate,
3919 } = metric
3920 {
3921 assert_eq!(samples, 2048);
3922 assert_eq!(sample_rate, 44100);
3923 } else {
3924 panic!("Expected AudioChunk variant");
3925 }
3926 }
3927
3928 #[test]
3929 fn test_compression_algorithm_eq() {
3930 assert_eq!(CompressionAlgorithm::Lz4, CompressionAlgorithm::Lz4);
3931 assert_eq!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Zstd);
3932 assert_eq!(CompressionAlgorithm::Png, CompressionAlgorithm::Png);
3933 assert_eq!(CompressionAlgorithm::Rle, CompressionAlgorithm::Rle);
3934 }
3935
3936 #[test]
3937 fn test_streaming_state_eq() {
3938 assert_eq!(StreamingState::Idle, StreamingState::Idle);
3939 assert_eq!(StreamingState::Buffering, StreamingState::Buffering);
3940 assert_eq!(StreamingState::Streaming, StreamingState::Streaming);
3941 assert_eq!(StreamingState::Stalled, StreamingState::Stalled);
3942 assert_eq!(StreamingState::Error, StreamingState::Error);
3943 assert_eq!(StreamingState::Completed, StreamingState::Completed);
3944 }
3945
3946 #[test]
3947 fn test_streaming_state_ne() {
3948 assert_ne!(StreamingState::Idle, StreamingState::Buffering);
3949 assert_ne!(StreamingState::Streaming, StreamingState::Stalled);
3950 assert_ne!(StreamingState::Error, StreamingState::Completed);
3951 }
3952
3953 #[test]
3954 fn test_vu_meter_error_debug() {
3955 let errors = vec![
3956 VuMeterError::NegativeLevel(-1.0),
3957 VuMeterError::Clipping(2.0),
3958 VuMeterError::Stale {
3959 last_update_ms: 100,
3960 current_ms: 200,
3961 },
3962 VuMeterError::SlowUpdateRate {
3963 measured_hz: 10.0,
3964 expected_hz: 30.0,
3965 },
3966 VuMeterError::NotAnimating {
3967 sample_count: 5,
3968 value: 0.5,
3969 },
3970 ];
3971
3972 for err in errors {
3973 let debug = format!("{:?}", err);
3974 assert!(!debug.is_empty());
3975 }
3976 }
3977
3978 #[test]
3979 fn test_streaming_validation_error_debug() {
3980 let errors: Vec<StreamingValidationError> = vec![
3981 StreamingValidationError::LatencyExceeded {
3982 measured: Duration::from_millis(100),
3983 max: Duration::from_millis(50),
3984 },
3985 StreamingValidationError::BufferUnderrunThreshold {
3986 count: 5,
3987 threshold: 3,
3988 },
3989 StreamingValidationError::DroppedFrameThreshold { count: 10, max: 5 },
3990 StreamingValidationError::FpsBelowMinimum {
3991 measured: 15.0,
3992 min: 30.0,
3993 },
3994 StreamingValidationError::TtfbExceeded {
3995 measured: Duration::from_secs(5),
3996 max: Duration::from_secs(2),
3997 },
3998 StreamingValidationError::InvalidStateTransition {
3999 from: StreamingState::Idle,
4000 to: StreamingState::Error,
4001 },
4002 StreamingValidationError::EndedInError,
4003 ];
4004
4005 for err in errors {
4006 let debug = format!("{:?}", err);
4007 assert!(!debug.is_empty());
4008 }
4009 }
4010
4011 #[test]
4012 fn test_screenshot_content_classify_boundary_uniform() {
4013 let mut pixels = vec![100u8; 96];
4015 pixels.extend(vec![200u8; 4]);
4016 let content = ScreenshotContent::classify(&pixels);
4017 assert!(matches!(
4018 content,
4019 ScreenshotContent::Uniform { fill_value: 100 }
4020 ));
4021 }
4022
4023 #[test]
4024 fn test_screenshot_content_classify_just_under_uniform() {
4025 let mut pixels = vec![100u8; 94];
4027 pixels.extend(vec![200u8; 6]);
4028 let content = ScreenshotContent::classify(&pixels);
4029 assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
4031 }
4032
4033 #[test]
4034 fn test_test_execution_stats_reset_clears_timing() {
4035 let mut stats = TestExecutionStats::new();
4036 stats.start();
4037 stats.record_state_capture(1000, 100);
4038 stats.stop();
4039
4040 assert!(stats.compress_throughput() > 0.0 || stats.bytes_raw > 0);
4042
4043 stats.reset();
4044
4045 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
4047 assert_eq!(stats.states_captured, 0);
4048 assert_eq!(stats.bytes_raw, 0);
4049 assert_eq!(stats.bytes_compressed, 0);
4050 assert_eq!(stats.same_fill_pages, 0);
4051 }
4052
4053 #[test]
4054 fn test_frame_times_cap_at_120() {
4055 let mut validator = StreamingUxValidator::new();
4056
4057 for i in 0..200 {
4059 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
4060 }
4061
4062 let fps = validator.average_fps();
4064 assert!(fps > 0.0);
4065 }
4066
4067 #[test]
4068 fn test_latency_metric_triggers_buffering_to_streaming() {
4069 let mut validator =
4070 StreamingUxValidator::new().with_max_latency(Duration::from_millis(200));
4071 validator.start();
4072 assert_eq!(validator.state(), StreamingState::Buffering);
4073
4074 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
4076 assert_eq!(validator.state(), StreamingState::Streaming);
4077 }
4078
4079 #[test]
4080 fn test_latency_metric_high_latency_no_transition() {
4081 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
4082 validator.start();
4083 assert_eq!(validator.state(), StreamingState::Buffering);
4084
4085 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
4087 assert_eq!(validator.state(), StreamingState::Buffering);
4088 }
4089
4090 #[test]
4091 fn test_buffer_level_stall_only_when_streaming() {
4092 let mut validator = StreamingUxValidator::new();
4093 validator.record_metric(StreamingMetric::BufferLevel(0.01));
4095 assert_eq!(validator.state(), StreamingState::Idle);
4096
4097 validator.start();
4099 validator.record_metric(StreamingMetric::BufferLevel(0.01));
4100 assert_eq!(validator.state(), StreamingState::Buffering);
4101 }
4102
4103 #[test]
4104 fn test_buffer_level_recovery_only_when_stalled() {
4105 let mut validator = StreamingUxValidator::new();
4106 validator.start();
4107 validator.record_metric(StreamingMetric::AudioChunk {
4108 samples: 1024,
4109 sample_rate: 16000,
4110 });
4111 assert_eq!(validator.state(), StreamingState::Streaming);
4112
4113 validator.record_metric(StreamingMetric::BufferLevel(0.9));
4115 assert_eq!(validator.state(), StreamingState::Streaming);
4116 }
4117
4118 #[test]
4119 fn test_frame_rendered_recovery_from_stalled() {
4120 let mut validator = StreamingUxValidator::new();
4121 validator.start();
4122 validator.record_metric(StreamingMetric::AudioChunk {
4123 samples: 1024,
4124 sample_rate: 16000,
4125 });
4126 validator.record_metric(StreamingMetric::BufferLevel(0.01)); assert_eq!(validator.state(), StreamingState::Stalled);
4129
4130 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
4132 assert_eq!(validator.state(), StreamingState::Streaming);
4133 }
4134
4135 #[test]
4136 fn test_audio_chunk_only_transitions_from_buffering() {
4137 let mut validator = StreamingUxValidator::new();
4138
4139 validator.record_metric(StreamingMetric::AudioChunk {
4141 samples: 1024,
4142 sample_rate: 16000,
4143 });
4144 assert_eq!(validator.state(), StreamingState::Idle);
4145 }
4146
4147 #[test]
4148 fn test_first_byte_received_only_transitions_from_idle() {
4149 let mut validator = StreamingUxValidator::new();
4150 validator.start(); validator.record_metric(StreamingMetric::FirstByteReceived);
4154 assert_eq!(validator.state(), StreamingState::Buffering);
4155 }
4156
4157 #[test]
4158 fn test_buffer_underrun_only_stalls_when_streaming() {
4159 let mut validator = StreamingUxValidator::new();
4160
4161 validator.record_metric(StreamingMetric::BufferUnderrun);
4163 assert_eq!(validator.state(), StreamingState::Idle);
4164
4165 validator.start();
4167 validator.record_metric(StreamingMetric::BufferUnderrun);
4168 assert_eq!(validator.state(), StreamingState::Buffering);
4169 }
4170
4171 #[test]
4172 fn test_validate_all_fps_error() {
4173 let mut validator = StreamingUxValidator::new().with_min_fps(60.0);
4174
4175 for i in 0..10 {
4177 validator.record_metric(StreamingMetric::FrameRendered {
4178 timestamp: i * 100, });
4180 }
4181
4182 let errors = validator.validate_all();
4183 assert!(errors
4184 .iter()
4185 .any(|e| matches!(e, StreamingValidationError::FpsBelowMinimum { .. })));
4186 }
4187
4188 #[test]
4189 fn test_validate_all_buffer_underrun_error() {
4190 let mut validator = StreamingUxValidator::new().with_buffer_underrun_threshold(1);
4191
4192 validator.record_metric(StreamingMetric::BufferUnderrun);
4193 validator.record_metric(StreamingMetric::BufferUnderrun);
4194
4195 let errors = validator.validate_all();
4196 assert!(errors
4197 .iter()
4198 .any(|e| matches!(e, StreamingValidationError::BufferUnderrunThreshold { .. })));
4199 }
4200
4201 #[test]
4202 fn test_validate_all_dropped_frames_error() {
4203 let mut validator = StreamingUxValidator::new().with_max_dropped_frames(1);
4204
4205 validator.record_metric(StreamingMetric::FrameDropped);
4206 validator.record_metric(StreamingMetric::FrameDropped);
4207
4208 let errors = validator.validate_all();
4209 assert!(errors
4210 .iter()
4211 .any(|e| matches!(e, StreamingValidationError::DroppedFrameThreshold { .. })));
4212 }
4213
4214 #[test]
4215 fn test_streaming_state_copy_clone() {
4216 let state = StreamingState::Streaming;
4217 let copied = state;
4218 let cloned = state;
4219 assert_eq!(copied, cloned);
4220 assert_eq!(state, StreamingState::Streaming);
4221 }
4222
4223 #[test]
4224 fn test_compression_algorithm_copy_clone() {
4225 let algo = CompressionAlgorithm::Zstd;
4226 let copied = algo;
4227 let cloned = algo;
4228 assert_eq!(copied, cloned);
4229 assert_eq!(algo, CompressionAlgorithm::Zstd);
4230 }
4231}