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 match content {
2582 ScreenshotContent::UiDominated { entropy } => {
2583 assert!(entropy < 3.0);
2584 }
2585 _ => {} }
2587 }
2588
2589 #[test]
2590 fn test_test_execution_stats_default() {
2591 let stats: TestExecutionStats = Default::default();
2592 assert_eq!(stats.states_captured, 0);
2593 }
2594
2595 #[test]
2596 fn test_streaming_validation_result_success() {
2597 let mut validator = StreamingUxValidator::new();
2598 validator.start();
2599 validator.record_metric(StreamingMetric::FirstByteReceived);
2600 validator.record_metric(StreamingMetric::AudioChunk {
2601 samples: 1024,
2602 sample_rate: 16000,
2603 });
2604
2605 for i in 0..60 {
2607 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 33 });
2608 }
2609
2610 validator.complete();
2611
2612 let result = validator.validate();
2613 assert!(result.is_ok());
2614 if let Ok(result) = result {
2615 assert!(result.max_latency_recorded >= Duration::ZERO);
2616 assert!(result.average_fps >= 0.0);
2617 }
2618 }
2619
2620 #[test]
2621 fn test_partial_result_clone() {
2622 let result = PartialResult {
2623 timestamp_ms: 100.0,
2624 text: "test".to_string(),
2625 is_final: false,
2626 };
2627 let cloned = result;
2628 assert_eq!(cloned.text, "test");
2629 }
2630
2631 #[test]
2632 fn test_vu_meter_sample_clone() {
2633 let sample = VuMeterSample {
2634 timestamp_ms: 100.0,
2635 level: 0.5,
2636 };
2637 let cloned = sample;
2638 assert!((cloned.level - 0.5).abs() < f32::EPSILON);
2639 }
2640
2641 #[test]
2642 fn test_streaming_metric_record_clone() {
2643 let record = StreamingMetricRecord {
2644 metric: StreamingMetric::BufferLevel(0.5),
2645 timestamp: Instant::now(),
2646 };
2647 let cloned = record;
2648 assert!(matches!(cloned.metric, StreamingMetric::BufferLevel(..)));
2649 }
2650
2651 #[test]
2652 fn test_streaming_validation_error_clone() {
2653 let err = StreamingValidationError::LatencyExceeded {
2654 measured: Duration::from_millis(150),
2655 max: Duration::from_millis(100),
2656 };
2657 let cloned = err;
2658 assert!(matches!(
2659 cloned,
2660 StreamingValidationError::LatencyExceeded { .. }
2661 ));
2662 }
2663
2664 #[test]
2665 fn test_vu_meter_error_clone() {
2666 let err = VuMeterError::Clipping(1.5);
2667 let cloned = err;
2668 assert!(matches!(cloned, VuMeterError::Clipping(..)));
2669 }
2670
2671 #[test]
2676 fn test_average_fps_with_zero_duration() {
2677 let mut validator = StreamingUxValidator::new();
2678 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2680 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2681
2682 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2684 }
2685
2686 #[test]
2687 fn test_average_fps_single_frame() {
2688 let mut validator = StreamingUxValidator::new();
2689 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2691
2692 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2693 }
2694
2695 #[test]
2696 fn test_streaming_validation_result_fields() {
2697 let mut validator = StreamingUxValidator::new()
2698 .with_max_latency(Duration::from_secs(10))
2699 .with_buffer_underrun_threshold(100)
2700 .with_max_dropped_frames(100)
2701 .with_min_fps(1.0);
2702
2703 validator.start();
2704 validator.record_metric(StreamingMetric::FirstByteReceived);
2705 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
2706
2707 for i in 0..60 {
2709 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
2710 }
2711
2712 let result = validator.validate().unwrap();
2713 assert_eq!(result.buffer_underruns, 0);
2714 assert_eq!(result.dropped_frames, 0);
2715 assert!(result.average_fps > 0.0);
2716 assert!(result.total_frames > 0);
2717 assert!(result.max_latency_recorded >= Duration::ZERO);
2718 }
2719
2720 #[test]
2721 fn test_max_recorded_latency_empty() {
2722 let validator = StreamingUxValidator::new();
2723 let result = validator.validate();
2725 assert!(result.is_ok());
2726 }
2727
2728 #[test]
2729 fn test_compression_ratio_with_zero_raw() {
2730 let mut stats = TestExecutionStats::new();
2731 stats.bytes_raw = 0;
2733 stats.bytes_compressed = 100;
2734 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
2736 }
2737
2738 #[test]
2739 fn test_throughput_with_zero_duration() {
2740 let mut stats = TestExecutionStats::new();
2741 stats.start();
2742 stats.record_state_capture(1000, 100);
2743 stats.stop();
2745
2746 let throughput = stats.compress_throughput();
2748 assert!(throughput >= 0.0);
2749 }
2750
2751 #[test]
2752 fn test_vu_meter_error_stale_clone() {
2753 let err = VuMeterError::Stale {
2754 last_update_ms: 100,
2755 current_ms: 200,
2756 };
2757 let cloned = err;
2758 assert!(matches!(
2759 cloned,
2760 VuMeterError::Stale {
2761 last_update_ms: 100,
2762 current_ms: 200,
2763 }
2764 ));
2765 }
2766
2767 #[test]
2768 fn test_vu_meter_error_slow_update_rate_clone() {
2769 let err = VuMeterError::SlowUpdateRate {
2770 measured_hz: 15.0,
2771 expected_hz: 30.0,
2772 };
2773 let cloned = err;
2774 match cloned {
2775 VuMeterError::SlowUpdateRate {
2776 measured_hz,
2777 expected_hz,
2778 } => {
2779 assert!((measured_hz - 15.0).abs() < f32::EPSILON);
2780 assert!((expected_hz - 30.0).abs() < f32::EPSILON);
2781 }
2782 _ => panic!("Expected SlowUpdateRate"),
2783 }
2784 }
2785
2786 #[test]
2787 fn test_vu_meter_error_not_animating_clone() {
2788 let err = VuMeterError::NotAnimating {
2789 sample_count: 10,
2790 value: 0.5,
2791 };
2792 let cloned = err;
2793 match cloned {
2794 VuMeterError::NotAnimating {
2795 sample_count,
2796 value,
2797 } => {
2798 assert_eq!(sample_count, 10);
2799 assert!((value - 0.5).abs() < f32::EPSILON);
2800 }
2801 _ => panic!("Expected NotAnimating"),
2802 }
2803 }
2804
2805 #[test]
2806 fn test_streaming_validation_error_all_clone_variants() {
2807 let errors: Vec<StreamingValidationError> = vec![
2809 StreamingValidationError::LatencyExceeded {
2810 measured: Duration::from_millis(200),
2811 max: Duration::from_millis(100),
2812 },
2813 StreamingValidationError::BufferUnderrunThreshold {
2814 count: 10,
2815 threshold: 5,
2816 },
2817 StreamingValidationError::DroppedFrameThreshold { count: 20, max: 10 },
2818 StreamingValidationError::FpsBelowMinimum {
2819 measured: 15.0,
2820 min: 30.0,
2821 },
2822 StreamingValidationError::TtfbExceeded {
2823 measured: Duration::from_secs(5),
2824 max: Duration::from_secs(2),
2825 },
2826 StreamingValidationError::InvalidStateTransition {
2827 from: StreamingState::Idle,
2828 to: StreamingState::Completed,
2829 },
2830 StreamingValidationError::EndedInError,
2831 ];
2832
2833 for err in errors {
2834 let cloned = err.clone();
2835 let _ = cloned.to_string();
2837 }
2838 }
2839
2840 #[test]
2841 fn test_streaming_metric_clone_all_variants() {
2842 let metrics = vec![
2843 StreamingMetric::Latency(Duration::from_millis(100)),
2844 StreamingMetric::FrameRendered { timestamp: 1000 },
2845 StreamingMetric::FrameDropped,
2846 StreamingMetric::BufferUnderrun,
2847 StreamingMetric::FirstByteReceived,
2848 StreamingMetric::BufferLevel(0.75),
2849 StreamingMetric::AudioChunk {
2850 samples: 2048,
2851 sample_rate: 44100,
2852 },
2853 ];
2854
2855 for metric in metrics {
2856 let cloned = metric.clone();
2857 let _ = format!("{:?}", cloned);
2858 }
2859 }
2860
2861 #[test]
2862 fn test_buffer_level_no_transition_when_not_streaming() {
2863 let mut validator = StreamingUxValidator::new();
2864 validator.record_metric(StreamingMetric::BufferLevel(0.05));
2866 assert_eq!(validator.state(), StreamingState::Idle);
2867
2868 validator.record_metric(StreamingMetric::BufferLevel(0.5));
2870 assert_eq!(validator.state(), StreamingState::Idle);
2871 }
2872
2873 #[test]
2874 fn test_latency_no_transition_when_exceeds_max() {
2875 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
2876 validator.start();
2877 assert_eq!(validator.state(), StreamingState::Buffering);
2878
2879 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
2881 assert_eq!(validator.state(), StreamingState::Buffering);
2882 }
2883
2884 #[test]
2885 fn test_frame_rendered_no_transition_when_not_stalled() {
2886 let mut validator = StreamingUxValidator::new();
2887 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2889 assert_eq!(validator.state(), StreamingState::Idle);
2890
2891 validator.start();
2893 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 200 });
2894 assert_eq!(validator.state(), StreamingState::Buffering);
2895 }
2896
2897 #[test]
2898 fn test_audio_chunk_no_transition_when_not_buffering() {
2899 let mut validator = StreamingUxValidator::new();
2900 validator.record_metric(StreamingMetric::AudioChunk {
2902 samples: 1024,
2903 sample_rate: 16000,
2904 });
2905 assert_eq!(validator.state(), StreamingState::Idle);
2906
2907 validator.start();
2909 validator.record_metric(StreamingMetric::AudioChunk {
2910 samples: 1024,
2911 sample_rate: 16000,
2912 });
2913 assert_eq!(validator.state(), StreamingState::Streaming);
2914 validator.record_metric(StreamingMetric::AudioChunk {
2915 samples: 1024,
2916 sample_rate: 16000,
2917 });
2918 assert_eq!(validator.state(), StreamingState::Streaming);
2919 }
2920
2921 #[test]
2922 fn test_first_byte_no_transition_when_not_idle() {
2923 let mut validator = StreamingUxValidator::new();
2924 validator.start(); validator.record_metric(StreamingMetric::FirstByteReceived);
2926 assert_eq!(validator.state(), StreamingState::Buffering);
2927 }
2928
2929 #[test]
2930 fn test_buffer_underrun_no_transition_when_not_streaming() {
2931 let mut validator = StreamingUxValidator::new();
2932 validator.record_metric(StreamingMetric::BufferUnderrun);
2934 assert_eq!(validator.state(), StreamingState::Idle);
2935 assert_eq!(validator.buffer_underruns(), 1);
2936
2937 validator.start();
2939 validator.record_metric(StreamingMetric::BufferUnderrun);
2940 assert_eq!(validator.state(), StreamingState::Buffering);
2941 assert_eq!(validator.buffer_underruns(), 2);
2942 }
2943
2944 #[test]
2945 fn test_transition_to_same_state() {
2946 let mut validator = StreamingUxValidator::new();
2947 validator.start();
2948 let history_len = validator.state_history().len();
2949
2950 validator.record_metric(StreamingMetric::BufferLevel(0.5)); assert_eq!(validator.state_history().len(), history_len);
2953 }
2954
2955 #[test]
2956 fn test_screenshot_content_single_byte() {
2957 let content = ScreenshotContent::classify(&[128]);
2959 assert!(matches!(
2960 content,
2961 ScreenshotContent::Uniform { fill_value: 128 }
2962 ));
2963 }
2964
2965 #[test]
2966 fn test_screenshot_content_two_bytes_same() {
2967 let content = ScreenshotContent::classify(&[42, 42]);
2968 assert!(matches!(
2969 content,
2970 ScreenshotContent::Uniform { fill_value: 42 }
2971 ));
2972 }
2973
2974 #[test]
2975 fn test_screenshot_content_near_uniform_threshold() {
2976 let mut pixels = vec![255u8; 94];
2978 pixels.extend(vec![0u8; 6]);
2979 let content = ScreenshotContent::classify(&pixels);
2980 assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
2981 }
2982
2983 #[test]
2984 fn test_screenshot_content_exactly_at_uniform_threshold() {
2985 let mut pixels = vec![255u8; 96];
2987 pixels.extend(vec![0u8; 4]);
2988 let content = ScreenshotContent::classify(&pixels);
2989 assert!(matches!(
2990 content,
2991 ScreenshotContent::Uniform { fill_value: 255 }
2992 ));
2993 }
2994
2995 #[test]
2996 fn test_screenshot_content_entropy_at_boundary_3() {
2997 let mut pixels = Vec::new();
2999 for _ in 0..125 {
3001 for v in 0u8..8u8 {
3002 pixels.push(v);
3003 }
3004 }
3005 let content = ScreenshotContent::classify(&pixels);
3006 let entropy = content.entropy();
3008 assert!((2.5..=3.5).contains(&entropy));
3009 }
3010
3011 #[test]
3012 fn test_screenshot_content_entropy_at_boundary_6() {
3013 let mut pixels = Vec::new();
3015 for _ in 0..16 {
3017 for v in 0u8..64u8 {
3018 pixels.push(v);
3019 }
3020 }
3021 let content = ScreenshotContent::classify(&pixels);
3022 let entropy = content.entropy();
3023 assert!((5.5..=6.5).contains(&entropy));
3024 }
3025
3026 #[test]
3027 fn test_screenshot_content_maximum_entropy() {
3028 let mut pixels = Vec::new();
3030 for _ in 0..4 {
3031 for v in 0u8..=255u8 {
3032 pixels.push(v);
3033 }
3034 }
3035 let content = ScreenshotContent::classify(&pixels);
3036 assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3037 assert!(content.entropy() > 7.0);
3038 }
3039
3040 #[test]
3041 fn test_validate_multiple_latency_exceeded() {
3042 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
3043
3044 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
3046 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(150)));
3047 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(200)));
3048
3049 let errors = validator.validate_all();
3050 assert_eq!(errors.len(), 3);
3051 for err in errors {
3052 assert!(matches!(
3053 err,
3054 StreamingValidationError::LatencyExceeded { .. }
3055 ));
3056 }
3057 }
3058
3059 #[test]
3060 fn test_streaming_validation_result_debug() {
3061 let result = StreamingValidationResult {
3062 buffer_underruns: 2,
3063 dropped_frames: 5,
3064 average_fps: 30.0,
3065 max_latency_recorded: Duration::from_millis(100),
3066 total_frames: 1000,
3067 };
3068 let debug = format!("{:?}", result);
3069 assert!(debug.contains("StreamingValidationResult"));
3070 assert!(debug.contains("buffer_underruns"));
3071 }
3072
3073 #[test]
3074 fn test_streaming_validation_result_clone() {
3075 let result = StreamingValidationResult {
3076 buffer_underruns: 3,
3077 dropped_frames: 7,
3078 average_fps: 60.0,
3079 max_latency_recorded: Duration::from_millis(50),
3080 total_frames: 2000,
3081 };
3082 let cloned = result;
3083 assert_eq!(cloned.buffer_underruns, 3);
3084 assert_eq!(cloned.dropped_frames, 7);
3085 assert!((cloned.average_fps - 60.0).abs() < f64::EPSILON);
3086 assert_eq!(cloned.max_latency_recorded, Duration::from_millis(50));
3087 assert_eq!(cloned.total_frames, 2000);
3088 }
3089
3090 #[test]
3091 fn test_vu_meter_config_smoothing_tolerance() {
3092 let config = VuMeterConfig {
3093 min_level: 0.0,
3094 max_level: 1.0,
3095 update_rate_hz: 30.0,
3096 smoothing_tolerance: 0.2,
3097 max_stale_ms: 100,
3098 };
3099
3100 assert!(config.validate_sample(1.19).is_ok());
3102
3103 assert!(config.validate_sample(1.21).is_err());
3105 }
3106
3107 #[test]
3108 fn test_compression_algorithm_debug() {
3109 let algos = [
3110 CompressionAlgorithm::Lz4,
3111 CompressionAlgorithm::Zstd,
3112 CompressionAlgorithm::Png,
3113 CompressionAlgorithm::Rle,
3114 ];
3115 for algo in algos {
3116 let debug = format!("{:?}", algo);
3117 assert!(!debug.is_empty());
3118 }
3119 }
3120
3121 #[test]
3122 fn test_compression_algorithm_copy() {
3123 let algo = CompressionAlgorithm::Lz4;
3124 let copied = algo;
3125 assert_eq!(algo, copied);
3126 }
3127
3128 #[test]
3129 fn test_test_execution_stats_large_values() {
3130 let mut stats = TestExecutionStats::new();
3131 stats.record_state_capture(u64::MAX / 2, u64::MAX / 4);
3132
3133 let ratio = stats.compression_ratio();
3135 assert!(ratio > 0.0);
3136
3137 let efficiency = stats.efficiency();
3138 assert!(efficiency > 0.0 && efficiency < 1.0);
3139 }
3140
3141 #[test]
3142 fn test_storage_savings_small_values() {
3143 let mut stats = TestExecutionStats::new();
3144 stats.record_state_capture(500_000, 400_000); let savings = stats.storage_savings_mb();
3147 assert!((savings - 0.1).abs() < 0.01);
3148 }
3149
3150 #[test]
3151 fn test_storage_savings_compressed_larger_than_raw() {
3152 let mut stats = TestExecutionStats::new();
3153 stats.bytes_raw = 100;
3155 stats.bytes_compressed = 200;
3156
3157 let savings = stats.storage_savings_mb();
3158 assert!((savings - 0.0).abs() < f64::EPSILON);
3159 }
3160
3161 #[test]
3162 fn test_same_fill_exactly_at_threshold() {
3163 let mut stats = TestExecutionStats::new();
3164 stats.record_state_capture(1000, 100);
3166 assert_eq!(stats.same_fill_pages, 0);
3168
3169 stats.record_state_capture(1000, 99);
3171 assert_eq!(stats.same_fill_pages, 1);
3172 }
3173
3174 #[test]
3175 fn test_streaming_ux_validator_debug() {
3176 let validator = StreamingUxValidator::new();
3177 let debug = format!("{:?}", validator);
3178 assert!(debug.contains("StreamingUxValidator"));
3179 }
3180
3181 #[test]
3182 fn test_streaming_metric_record_debug() {
3183 let record = StreamingMetricRecord {
3184 metric: StreamingMetric::FrameDropped,
3185 timestamp: Instant::now(),
3186 };
3187 let debug = format!("{:?}", record);
3188 assert!(debug.contains("StreamingMetricRecord"));
3189 }
3190
3191 #[test]
3192 fn test_validate_all_empty_metrics() {
3193 let validator = StreamingUxValidator::new();
3194 let errors = validator.validate_all();
3195 assert!(errors.is_empty());
3196 }
3197
3198 #[test]
3199 fn test_validate_with_error_state() {
3200 let mut validator = StreamingUxValidator::new();
3201 validator.error();
3202
3203 let result = validator.validate();
3204 assert!(result.is_err());
3205 assert!(matches!(
3206 result.unwrap_err(),
3207 StreamingValidationError::EndedInError
3208 ));
3209 }
3210
3211 #[test]
3212 fn test_validate_all_multiple_error_types() {
3213 let mut validator = StreamingUxValidator::new()
3214 .with_max_latency(Duration::from_millis(10))
3215 .with_buffer_underrun_threshold(0)
3216 .with_max_dropped_frames(0)
3217 .with_min_fps(100.0);
3218
3219 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3220 validator.record_metric(StreamingMetric::BufferUnderrun);
3221 validator.record_metric(StreamingMetric::FrameDropped);
3222
3223 for i in 0..5 {
3225 validator.record_metric(StreamingMetric::FrameRendered {
3226 timestamp: i * 500, });
3228 }
3229
3230 validator.error();
3231
3232 let errors = validator.validate_all();
3233 assert!(errors.len() >= 4);
3235 }
3236
3237 #[test]
3238 fn test_vu_meter_error_negative_level_value() {
3239 let config = VuMeterConfig::default();
3240 let result = config.validate_sample(-10.5);
3241 match result {
3242 Err(VuMeterError::NegativeLevel(v)) => {
3243 assert!((v - (-10.5)).abs() < f32::EPSILON);
3244 }
3245 _ => panic!("Expected NegativeLevel error"),
3246 }
3247 }
3248
3249 #[test]
3250 fn test_vu_meter_error_clipping_value() {
3251 let config = VuMeterConfig::default().with_max_level(0.5);
3252 let result = config.validate_sample(2.0);
3253 match result {
3254 Err(VuMeterError::Clipping(v)) => {
3255 assert!((v - 2.0).abs() < f32::EPSILON);
3256 }
3257 _ => panic!("Expected Clipping error"),
3258 }
3259 }
3260
3261 #[test]
3262 fn test_streaming_state_hash() {
3263 use std::collections::HashSet;
3264 let mut set = HashSet::new();
3265 set.insert(StreamingState::Idle);
3266 set.insert(StreamingState::Buffering);
3267 set.insert(StreamingState::Streaming);
3268 set.insert(StreamingState::Stalled);
3269 set.insert(StreamingState::Error);
3270 set.insert(StreamingState::Completed);
3271
3272 assert_eq!(set.len(), 6);
3273 assert!(set.contains(&StreamingState::Idle));
3274 }
3275
3276 #[test]
3277 fn test_screenshot_content_clone() {
3278 let contents = vec![
3279 ScreenshotContent::Uniform { fill_value: 128 },
3280 ScreenshotContent::UiDominated { entropy: 2.5 },
3281 ScreenshotContent::GameWorld { entropy: 4.5 },
3282 ScreenshotContent::HighEntropy { entropy: 7.0 },
3283 ];
3284
3285 for content in contents {
3286 let cloned = content.clone();
3287 assert!((cloned.entropy() - content.entropy()).abs() < f32::EPSILON);
3288 }
3289 }
3290
3291 #[test]
3292 fn test_test_execution_stats_all_fields() {
3293 let mut stats = TestExecutionStats::new();
3294 stats.start();
3295 stats.record_state_capture(1000, 100);
3296 stats.record_state_capture(2000, 50); stats.stop();
3298
3299 assert_eq!(stats.states_captured, 2);
3300 assert_eq!(stats.bytes_raw, 3000);
3301 assert_eq!(stats.bytes_compressed, 150);
3302 assert_eq!(stats.same_fill_pages, 1);
3303 }
3304
3305 #[test]
3306 fn test_frame_times_exactly_120() {
3307 let mut validator = StreamingUxValidator::new();
3308 for i in 0..120 {
3310 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3311 }
3312 assert!(validator.average_fps() > 0.0);
3313 }
3314
3315 #[test]
3316 fn test_frame_times_121() {
3317 let mut validator = StreamingUxValidator::new();
3318 for i in 0..121 {
3320 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3321 }
3322 assert!(validator.average_fps() > 0.0);
3324 }
3325
3326 #[test]
3327 fn test_state_transition_fields_access() {
3328 let transition = StateTransition {
3329 from: "State1".to_string(),
3330 to: "State2".to_string(),
3331 timestamp_ms: 12345.67,
3332 duration_ms: 890.12,
3333 };
3334
3335 assert_eq!(transition.from.as_str(), "State1");
3336 assert_eq!(transition.to.as_str(), "State2");
3337 assert!((transition.timestamp_ms - 12345.67).abs() < f64::EPSILON);
3338 assert!((transition.duration_ms - 890.12).abs() < f64::EPSILON);
3339 }
3340
3341 #[test]
3342 fn test_partial_result_fields_access() {
3343 let partial = PartialResult {
3344 timestamp_ms: 999.99,
3345 text: "Hello World".to_string(),
3346 is_final: true,
3347 };
3348
3349 assert!((partial.timestamp_ms - 999.99).abs() < f64::EPSILON);
3350 assert_eq!(partial.text.as_str(), "Hello World");
3351 assert!(partial.is_final);
3352 }
3353
3354 #[test]
3355 fn test_vu_meter_sample_fields_access() {
3356 let sample = VuMeterSample {
3357 timestamp_ms: 1234.5,
3358 level: 0.789,
3359 };
3360
3361 assert!((sample.timestamp_ms - 1234.5).abs() < f64::EPSILON);
3362 assert!((sample.level - 0.789).abs() < f32::EPSILON);
3363 }
3364
3365 #[test]
3366 fn test_streaming_metric_record_fields_access() {
3367 let timestamp = Instant::now();
3368 let record = StreamingMetricRecord {
3369 metric: StreamingMetric::BufferLevel(0.42),
3370 timestamp,
3371 };
3372
3373 assert!(matches!(record.metric, StreamingMetric::BufferLevel(..)));
3374 assert_eq!(record.timestamp, timestamp);
3375 }
3376
3377 #[test]
3378 fn test_validate_returns_first_error() {
3379 let mut validator = StreamingUxValidator::new()
3380 .with_max_latency(Duration::from_millis(10))
3381 .with_buffer_underrun_threshold(0);
3382
3383 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3385 validator.record_metric(StreamingMetric::BufferUnderrun);
3386
3387 let result = validator.validate();
3388 assert!(result.is_err());
3389 assert!(matches!(
3391 result.unwrap_err(),
3392 StreamingValidationError::LatencyExceeded { .. }
3393 ));
3394 }
3395
3396 #[test]
3397 fn test_validate_fps_error_only_when_positive() {
3398 let mut validator = StreamingUxValidator::new().with_min_fps(100.0);
3399
3400 let result = validator.validate();
3402 assert!(result.is_ok());
3403
3404 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 0 });
3406 let result = validator.validate();
3407 assert!(result.is_ok());
3408 }
3409
3410 #[test]
3411 fn test_buffer_level_recovery_threshold() {
3412 let mut validator = StreamingUxValidator::new();
3413 validator.start();
3414 validator.record_metric(StreamingMetric::AudioChunk {
3415 samples: 1024,
3416 sample_rate: 16000,
3417 });
3418 assert_eq!(validator.state(), StreamingState::Streaming);
3419
3420 validator.record_metric(StreamingMetric::BufferLevel(0.05));
3422 assert_eq!(validator.state(), StreamingState::Stalled);
3423
3424 validator.record_metric(StreamingMetric::BufferLevel(0.3));
3426 assert_eq!(validator.state(), StreamingState::Stalled);
3427
3428 validator.record_metric(StreamingMetric::BufferLevel(0.31));
3430 assert_eq!(validator.state(), StreamingState::Streaming);
3431 }
3432
3433 #[test]
3434 fn test_buffer_level_stall_threshold() {
3435 let mut validator = StreamingUxValidator::new();
3436 validator.start();
3437 validator.record_metric(StreamingMetric::AudioChunk {
3438 samples: 1024,
3439 sample_rate: 16000,
3440 });
3441 assert_eq!(validator.state(), StreamingState::Streaming);
3442
3443 validator.record_metric(StreamingMetric::BufferLevel(0.1));
3445 assert_eq!(validator.state(), StreamingState::Streaming);
3446
3447 validator.record_metric(StreamingMetric::BufferLevel(0.09));
3449 assert_eq!(validator.state(), StreamingState::Stalled);
3450 }
3451
3452 #[test]
3457 fn test_vu_meter_config_default_values() {
3458 let config = VuMeterConfig::default();
3459 assert!((config.min_level - 0.0).abs() < f32::EPSILON);
3460 assert!((config.max_level - 1.0).abs() < f32::EPSILON);
3461 assert!((config.update_rate_hz - 30.0).abs() < f32::EPSILON);
3462 assert!((config.smoothing_tolerance - 0.1).abs() < f32::EPSILON);
3463 assert_eq!(config.max_stale_ms, 100);
3464 }
3465
3466 #[test]
3467 fn test_vu_meter_error_display_all_variants() {
3468 let err = VuMeterError::NegativeLevel(-0.25);
3470 let display = format!("{}", err);
3471 assert!(display.contains("-0.25"));
3472 assert!(display.contains("negative"));
3473
3474 let err = VuMeterError::Clipping(1.75);
3476 let display = format!("{}", err);
3477 assert!(display.contains("1.75"));
3478 assert!(display.contains("clipping"));
3479
3480 let err = VuMeterError::Stale {
3482 last_update_ms: 50,
3483 current_ms: 250,
3484 };
3485 let display = format!("{}", err);
3486 assert!(display.contains("200ms"));
3487
3488 let err = VuMeterError::SlowUpdateRate {
3490 measured_hz: 20.0,
3491 expected_hz: 60.0,
3492 };
3493 let display = format!("{}", err);
3494 assert!(display.contains("20.0Hz"));
3495 assert!(display.contains("60.0Hz"));
3496
3497 let err = VuMeterError::NotAnimating {
3499 sample_count: 50,
3500 value: 0.75,
3501 };
3502 let display = format!("{}", err);
3503 assert!(display.contains("50 samples"));
3504 assert!(display.contains("0.75"));
3505 }
3506
3507 #[test]
3508 fn test_streaming_validation_error_display_all_variants() {
3509 let err = StreamingValidationError::LatencyExceeded {
3510 measured: Duration::from_millis(300),
3511 max: Duration::from_millis(100),
3512 };
3513 assert!(err.to_string().contains("300"));
3514
3515 let err = StreamingValidationError::BufferUnderrunThreshold {
3516 count: 15,
3517 threshold: 5,
3518 };
3519 assert!(err.to_string().contains("15"));
3520 assert!(err.to_string().contains('5'));
3521
3522 let err = StreamingValidationError::DroppedFrameThreshold { count: 25, max: 10 };
3523 assert!(err.to_string().contains("25"));
3524 assert!(err.to_string().contains("10"));
3525
3526 let err = StreamingValidationError::FpsBelowMinimum {
3527 measured: 20.5,
3528 min: 60.0,
3529 };
3530 assert!(err.to_string().contains("20.5"));
3531 assert!(err.to_string().contains("60.0"));
3532
3533 let err = StreamingValidationError::TtfbExceeded {
3534 measured: Duration::from_secs(10),
3535 max: Duration::from_secs(3),
3536 };
3537 let display = err.to_string();
3538 assert!(display.contains("first byte"));
3539
3540 let err = StreamingValidationError::InvalidStateTransition {
3541 from: StreamingState::Buffering,
3542 to: StreamingState::Completed,
3543 };
3544 let display = err.to_string();
3545 assert!(display.contains("Buffering"));
3546 assert!(display.contains("Completed"));
3547
3548 let err = StreamingValidationError::EndedInError;
3549 assert!(err.to_string().contains("error state"));
3550 }
3551
3552 #[test]
3553 fn test_streaming_state_display_coverage() {
3554 assert_eq!(format!("{}", StreamingState::Idle), "Idle");
3556 assert_eq!(format!("{}", StreamingState::Buffering), "Buffering");
3557 assert_eq!(format!("{}", StreamingState::Streaming), "Streaming");
3558 assert_eq!(format!("{}", StreamingState::Stalled), "Stalled");
3559 assert_eq!(format!("{}", StreamingState::Error), "Error");
3560 assert_eq!(format!("{}", StreamingState::Completed), "Completed");
3561 }
3562
3563 #[test]
3564 fn test_test_execution_stats_zero_raw_bytes() {
3565 let stats = TestExecutionStats::new();
3566 assert!((stats.efficiency() - 0.0).abs() < f64::EPSILON);
3568 }
3569
3570 #[test]
3571 fn test_test_execution_stats_zero_compressed_bytes() {
3572 let mut stats = TestExecutionStats::new();
3573 stats.bytes_raw = 1000;
3574 stats.bytes_compressed = 0;
3575 assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
3577 }
3578
3579 #[test]
3580 fn test_test_execution_stats_zero_states_captured() {
3581 let stats = TestExecutionStats::new();
3582 assert!((stats.same_fill_ratio() - 0.0).abs() < f64::EPSILON);
3584 }
3585
3586 #[test]
3587 fn test_test_execution_stats_throughput_no_start() {
3588 let mut stats = TestExecutionStats::new();
3589 stats.stop();
3590 stats.record_state_capture(1000, 100);
3591 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3593 }
3594
3595 #[test]
3596 fn test_test_execution_stats_throughput_only_end() {
3597 let mut stats = TestExecutionStats::new();
3598 stats.stop();
3599 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3601 }
3602
3603 #[test]
3604 fn test_test_execution_stats_same_fill_with_zero_raw() {
3605 let mut stats = TestExecutionStats::new();
3606 stats.record_state_capture(0, 0);
3608 assert_eq!(stats.same_fill_pages, 0);
3609 }
3610
3611 #[test]
3612 fn test_screenshot_content_classify_single_pixel() {
3613 let content = ScreenshotContent::classify(&[42]);
3615 assert!(matches!(
3616 content,
3617 ScreenshotContent::Uniform { fill_value: 42 }
3618 ));
3619 }
3620
3621 #[test]
3622 fn test_screenshot_content_all_different_values() {
3623 let pixels: Vec<u8> = (0..=255).collect();
3625 let content = ScreenshotContent::classify(&pixels);
3626 assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3627 assert!(content.entropy() > 7.5);
3629 }
3630
3631 #[test]
3632 fn test_screenshot_content_entropy_method_uniform() {
3633 let content = ScreenshotContent::Uniform { fill_value: 200 };
3634 assert!((content.entropy() - 0.0).abs() < f32::EPSILON);
3635 }
3636
3637 #[test]
3638 fn test_screenshot_content_recommended_algorithm_all() {
3639 assert_eq!(
3640 ScreenshotContent::Uniform { fill_value: 0 }.recommended_algorithm(),
3641 CompressionAlgorithm::Rle
3642 );
3643 assert_eq!(
3644 ScreenshotContent::UiDominated { entropy: 2.0 }.recommended_algorithm(),
3645 CompressionAlgorithm::Png
3646 );
3647 assert_eq!(
3648 ScreenshotContent::GameWorld { entropy: 4.5 }.recommended_algorithm(),
3649 CompressionAlgorithm::Zstd
3650 );
3651 assert_eq!(
3652 ScreenshotContent::HighEntropy { entropy: 7.0 }.recommended_algorithm(),
3653 CompressionAlgorithm::Lz4
3654 );
3655 }
3656
3657 #[test]
3658 fn test_screenshot_content_expected_ratio_hint_all() {
3659 assert!(ScreenshotContent::Uniform { fill_value: 0 }
3660 .expected_ratio_hint()
3661 .contains("excellent"));
3662 assert!(ScreenshotContent::UiDominated { entropy: 2.0 }
3663 .expected_ratio_hint()
3664 .contains("good"));
3665 assert!(ScreenshotContent::GameWorld { entropy: 4.5 }
3666 .expected_ratio_hint()
3667 .contains("moderate"));
3668 assert!(ScreenshotContent::HighEntropy { entropy: 7.0 }
3669 .expected_ratio_hint()
3670 .contains("poor"));
3671 }
3672
3673 #[test]
3674 fn test_streaming_ux_validator_builder_chain() {
3675 let validator = StreamingUxValidator::new()
3676 .with_max_latency(Duration::from_millis(150))
3677 .with_buffer_underrun_threshold(10)
3678 .with_max_dropped_frames(20)
3679 .with_min_fps(45.0)
3680 .with_ttfb_timeout(Duration::from_secs(5));
3681
3682 assert_eq!(validator.max_latency, Duration::from_millis(150));
3683 assert_eq!(validator.buffer_underrun_threshold, 10);
3684 assert_eq!(validator.max_dropped_frames, 20);
3685 assert!((validator.min_fps - 45.0).abs() < f64::EPSILON);
3686 assert_eq!(validator.ttfb_timeout, Duration::from_secs(5));
3687 }
3688
3689 #[test]
3690 fn test_streaming_validator_for_audio_preset() {
3691 let validator = StreamingUxValidator::for_audio();
3692 assert_eq!(validator.max_latency, Duration::from_millis(100));
3693 assert_eq!(validator.buffer_underrun_threshold, 3);
3694 assert_eq!(validator.ttfb_timeout, Duration::from_secs(2));
3695 }
3696
3697 #[test]
3698 fn test_streaming_validator_for_video_preset() {
3699 let validator = StreamingUxValidator::for_video();
3700 assert_eq!(validator.max_latency, Duration::from_millis(500));
3701 assert!((validator.min_fps - 30.0).abs() < f64::EPSILON);
3702 assert_eq!(validator.max_dropped_frames, 5);
3703 }
3704
3705 #[test]
3706 fn test_streaming_validator_average_fps_no_frames() {
3707 let validator = StreamingUxValidator::new();
3708 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3709 }
3710
3711 #[test]
3712 fn test_streaming_validator_average_fps_one_frame() {
3713 let mut validator = StreamingUxValidator::new();
3714 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3715 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3716 }
3717
3718 #[test]
3719 fn test_streaming_validator_average_fps_same_timestamp() {
3720 let mut validator = StreamingUxValidator::new();
3721 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3722 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3723 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3724 assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3726 }
3727
3728 #[test]
3729 fn test_streaming_validator_max_recorded_latency_none() {
3730 let validator = StreamingUxValidator::new();
3731 let result = validator.validate();
3732 assert!(result.is_ok());
3733 let res = result.unwrap();
3734 assert_eq!(res.max_latency_recorded, Duration::ZERO);
3735 }
3736
3737 #[test]
3738 fn test_streaming_validator_validate_no_ttfb() {
3739 let mut validator = StreamingUxValidator::new();
3740 validator.start();
3741 let result = validator.validate();
3743 assert!(result.is_ok());
3744 }
3745
3746 #[test]
3747 fn test_streaming_validator_complete_and_validate() {
3748 let mut validator = StreamingUxValidator::new();
3749 validator.complete();
3750 assert_eq!(validator.state(), StreamingState::Completed);
3751 let result = validator.validate();
3752 assert!(result.is_ok());
3753 }
3754
3755 #[test]
3756 fn test_streaming_validator_error_and_validate() {
3757 let mut validator = StreamingUxValidator::new();
3758 validator.error();
3759 assert_eq!(validator.state(), StreamingState::Error);
3760 let result = validator.validate();
3761 assert!(result.is_err());
3762 }
3763
3764 #[test]
3765 fn test_streaming_validator_state_history_empty() {
3766 let validator = StreamingUxValidator::new();
3767 assert!(validator.state_history().is_empty());
3768 }
3769
3770 #[test]
3771 fn test_streaming_validator_state_history_with_transitions() {
3772 let mut validator = StreamingUxValidator::new();
3773 validator.start();
3774 validator.record_metric(StreamingMetric::AudioChunk {
3775 samples: 1024,
3776 sample_rate: 16000,
3777 });
3778 validator.complete();
3779
3780 let history = validator.state_history();
3781 assert!(history.len() >= 2);
3782 }
3783
3784 #[test]
3785 fn test_streaming_validator_reset_full() {
3786 let mut validator = StreamingUxValidator::new();
3787 validator.start();
3788 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3789 validator.record_metric(StreamingMetric::BufferUnderrun);
3790 validator.record_metric(StreamingMetric::FrameDropped);
3791 validator.record_metric(StreamingMetric::FirstByteReceived);
3792 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
3793
3794 validator.reset();
3795
3796 assert_eq!(validator.state(), StreamingState::Idle);
3797 assert_eq!(validator.buffer_underruns(), 0);
3798 assert_eq!(validator.dropped_frames(), 0);
3799 assert!(validator.state_history().is_empty());
3800 }
3801
3802 #[test]
3803 fn test_streaming_validator_validate_all_with_error_state() {
3804 let mut validator = StreamingUxValidator::new();
3805 validator.error();
3806 let errors = validator.validate_all();
3807 assert!(errors
3808 .iter()
3809 .any(|e| matches!(e, StreamingValidationError::EndedInError)));
3810 }
3811
3812 #[test]
3813 fn test_streaming_validation_result_fields_all() {
3814 let result = StreamingValidationResult {
3815 buffer_underruns: 1,
3816 dropped_frames: 2,
3817 average_fps: 30.0,
3818 max_latency_recorded: Duration::from_millis(50),
3819 total_frames: 100,
3820 };
3821 assert_eq!(result.buffer_underruns, 1);
3822 assert_eq!(result.dropped_frames, 2);
3823 assert!((result.average_fps - 30.0).abs() < f64::EPSILON);
3824 assert_eq!(result.max_latency_recorded, Duration::from_millis(50));
3825 assert_eq!(result.total_frames, 100);
3826 }
3827
3828 #[test]
3829 fn test_state_transition_struct_fields_all() {
3830 let transition = StateTransition {
3831 from: "StateA".to_string(),
3832 to: "StateB".to_string(),
3833 timestamp_ms: 100.0,
3834 duration_ms: 50.0,
3835 };
3836 assert_eq!(&transition.from, "StateA");
3837 assert_eq!(&transition.to, "StateB");
3838 assert!((transition.timestamp_ms - 100.0).abs() < f64::EPSILON);
3839 assert!((transition.duration_ms - 50.0).abs() < f64::EPSILON);
3840 }
3841
3842 #[test]
3843 fn test_partial_result_struct_fields() {
3844 let partial = PartialResult {
3845 timestamp_ms: 200.0,
3846 text: "partial text".to_string(),
3847 is_final: false,
3848 };
3849 assert!((partial.timestamp_ms - 200.0).abs() < f64::EPSILON);
3850 assert_eq!(&partial.text, "partial text");
3851 assert!(!partial.is_final);
3852
3853 let final_result = PartialResult {
3854 timestamp_ms: 300.0,
3855 text: "final text".to_string(),
3856 is_final: true,
3857 };
3858 assert!(final_result.is_final);
3859 }
3860
3861 #[test]
3862 fn test_vu_meter_sample_struct_fields() {
3863 let sample = VuMeterSample {
3864 timestamp_ms: 150.0,
3865 level: 0.65,
3866 };
3867 assert!((sample.timestamp_ms - 150.0).abs() < f64::EPSILON);
3868 assert!((sample.level - 0.65).abs() < f32::EPSILON);
3869 }
3870
3871 #[test]
3872 fn test_streaming_metric_record_struct() {
3873 let now = Instant::now();
3874 let record = StreamingMetricRecord {
3875 metric: StreamingMetric::FrameDropped,
3876 timestamp: now,
3877 };
3878 assert!(matches!(record.metric, StreamingMetric::FrameDropped));
3879 assert_eq!(record.timestamp, now);
3880 }
3881
3882 #[test]
3883 fn test_streaming_metric_latency_variant() {
3884 let metric = StreamingMetric::Latency(Duration::from_millis(123));
3885 if let StreamingMetric::Latency(d) = metric {
3886 assert_eq!(d, Duration::from_millis(123));
3887 } else {
3888 panic!("Expected Latency variant");
3889 }
3890 }
3891
3892 #[test]
3893 fn test_streaming_metric_frame_rendered_variant() {
3894 let metric = StreamingMetric::FrameRendered { timestamp: 999 };
3895 if let StreamingMetric::FrameRendered { timestamp } = metric {
3896 assert_eq!(timestamp, 999);
3897 } else {
3898 panic!("Expected FrameRendered variant");
3899 }
3900 }
3901
3902 #[test]
3903 fn test_streaming_metric_buffer_level_variant() {
3904 let metric = StreamingMetric::BufferLevel(0.42);
3905 if let StreamingMetric::BufferLevel(level) = metric {
3906 assert!((level - 0.42).abs() < f32::EPSILON);
3907 } else {
3908 panic!("Expected BufferLevel variant");
3909 }
3910 }
3911
3912 #[test]
3913 fn test_streaming_metric_audio_chunk_variant() {
3914 let metric = StreamingMetric::AudioChunk {
3915 samples: 2048,
3916 sample_rate: 44100,
3917 };
3918 if let StreamingMetric::AudioChunk {
3919 samples,
3920 sample_rate,
3921 } = metric
3922 {
3923 assert_eq!(samples, 2048);
3924 assert_eq!(sample_rate, 44100);
3925 } else {
3926 panic!("Expected AudioChunk variant");
3927 }
3928 }
3929
3930 #[test]
3931 fn test_compression_algorithm_eq() {
3932 assert_eq!(CompressionAlgorithm::Lz4, CompressionAlgorithm::Lz4);
3933 assert_eq!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Zstd);
3934 assert_eq!(CompressionAlgorithm::Png, CompressionAlgorithm::Png);
3935 assert_eq!(CompressionAlgorithm::Rle, CompressionAlgorithm::Rle);
3936 }
3937
3938 #[test]
3939 fn test_streaming_state_eq() {
3940 assert_eq!(StreamingState::Idle, StreamingState::Idle);
3941 assert_eq!(StreamingState::Buffering, StreamingState::Buffering);
3942 assert_eq!(StreamingState::Streaming, StreamingState::Streaming);
3943 assert_eq!(StreamingState::Stalled, StreamingState::Stalled);
3944 assert_eq!(StreamingState::Error, StreamingState::Error);
3945 assert_eq!(StreamingState::Completed, StreamingState::Completed);
3946 }
3947
3948 #[test]
3949 fn test_streaming_state_ne() {
3950 assert_ne!(StreamingState::Idle, StreamingState::Buffering);
3951 assert_ne!(StreamingState::Streaming, StreamingState::Stalled);
3952 assert_ne!(StreamingState::Error, StreamingState::Completed);
3953 }
3954
3955 #[test]
3956 fn test_vu_meter_error_debug() {
3957 let errors = vec![
3958 VuMeterError::NegativeLevel(-1.0),
3959 VuMeterError::Clipping(2.0),
3960 VuMeterError::Stale {
3961 last_update_ms: 100,
3962 current_ms: 200,
3963 },
3964 VuMeterError::SlowUpdateRate {
3965 measured_hz: 10.0,
3966 expected_hz: 30.0,
3967 },
3968 VuMeterError::NotAnimating {
3969 sample_count: 5,
3970 value: 0.5,
3971 },
3972 ];
3973
3974 for err in errors {
3975 let debug = format!("{:?}", err);
3976 assert!(!debug.is_empty());
3977 }
3978 }
3979
3980 #[test]
3981 fn test_streaming_validation_error_debug() {
3982 let errors: Vec<StreamingValidationError> = vec![
3983 StreamingValidationError::LatencyExceeded {
3984 measured: Duration::from_millis(100),
3985 max: Duration::from_millis(50),
3986 },
3987 StreamingValidationError::BufferUnderrunThreshold {
3988 count: 5,
3989 threshold: 3,
3990 },
3991 StreamingValidationError::DroppedFrameThreshold { count: 10, max: 5 },
3992 StreamingValidationError::FpsBelowMinimum {
3993 measured: 15.0,
3994 min: 30.0,
3995 },
3996 StreamingValidationError::TtfbExceeded {
3997 measured: Duration::from_secs(5),
3998 max: Duration::from_secs(2),
3999 },
4000 StreamingValidationError::InvalidStateTransition {
4001 from: StreamingState::Idle,
4002 to: StreamingState::Error,
4003 },
4004 StreamingValidationError::EndedInError,
4005 ];
4006
4007 for err in errors {
4008 let debug = format!("{:?}", err);
4009 assert!(!debug.is_empty());
4010 }
4011 }
4012
4013 #[test]
4014 fn test_screenshot_content_classify_boundary_uniform() {
4015 let mut pixels = vec![100u8; 96];
4017 pixels.extend(vec![200u8; 4]);
4018 let content = ScreenshotContent::classify(&pixels);
4019 assert!(matches!(
4020 content,
4021 ScreenshotContent::Uniform { fill_value: 100 }
4022 ));
4023 }
4024
4025 #[test]
4026 fn test_screenshot_content_classify_just_under_uniform() {
4027 let mut pixels = vec![100u8; 94];
4029 pixels.extend(vec![200u8; 6]);
4030 let content = ScreenshotContent::classify(&pixels);
4031 assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
4033 }
4034
4035 #[test]
4036 fn test_test_execution_stats_reset_clears_timing() {
4037 let mut stats = TestExecutionStats::new();
4038 stats.start();
4039 stats.record_state_capture(1000, 100);
4040 stats.stop();
4041
4042 assert!(stats.compress_throughput() > 0.0 || stats.bytes_raw > 0);
4044
4045 stats.reset();
4046
4047 assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
4049 assert_eq!(stats.states_captured, 0);
4050 assert_eq!(stats.bytes_raw, 0);
4051 assert_eq!(stats.bytes_compressed, 0);
4052 assert_eq!(stats.same_fill_pages, 0);
4053 }
4054
4055 #[test]
4056 fn test_frame_times_cap_at_120() {
4057 let mut validator = StreamingUxValidator::new();
4058
4059 for i in 0..200 {
4061 validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
4062 }
4063
4064 let fps = validator.average_fps();
4066 assert!(fps > 0.0);
4067 }
4068
4069 #[test]
4070 fn test_latency_metric_triggers_buffering_to_streaming() {
4071 let mut validator =
4072 StreamingUxValidator::new().with_max_latency(Duration::from_millis(200));
4073 validator.start();
4074 assert_eq!(validator.state(), StreamingState::Buffering);
4075
4076 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
4078 assert_eq!(validator.state(), StreamingState::Streaming);
4079 }
4080
4081 #[test]
4082 fn test_latency_metric_high_latency_no_transition() {
4083 let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
4084 validator.start();
4085 assert_eq!(validator.state(), StreamingState::Buffering);
4086
4087 validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
4089 assert_eq!(validator.state(), StreamingState::Buffering);
4090 }
4091
4092 #[test]
4093 fn test_buffer_level_stall_only_when_streaming() {
4094 let mut validator = StreamingUxValidator::new();
4095 validator.record_metric(StreamingMetric::BufferLevel(0.01));
4097 assert_eq!(validator.state(), StreamingState::Idle);
4098
4099 validator.start();
4101 validator.record_metric(StreamingMetric::BufferLevel(0.01));
4102 assert_eq!(validator.state(), StreamingState::Buffering);
4103 }
4104
4105 #[test]
4106 fn test_buffer_level_recovery_only_when_stalled() {
4107 let mut validator = StreamingUxValidator::new();
4108 validator.start();
4109 validator.record_metric(StreamingMetric::AudioChunk {
4110 samples: 1024,
4111 sample_rate: 16000,
4112 });
4113 assert_eq!(validator.state(), StreamingState::Streaming);
4114
4115 validator.record_metric(StreamingMetric::BufferLevel(0.9));
4117 assert_eq!(validator.state(), StreamingState::Streaming);
4118 }
4119
4120 #[test]
4121 fn test_frame_rendered_recovery_from_stalled() {
4122 let mut validator = StreamingUxValidator::new();
4123 validator.start();
4124 validator.record_metric(StreamingMetric::AudioChunk {
4125 samples: 1024,
4126 sample_rate: 16000,
4127 });
4128 validator.record_metric(StreamingMetric::BufferLevel(0.01)); assert_eq!(validator.state(), StreamingState::Stalled);
4131
4132 validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
4134 assert_eq!(validator.state(), StreamingState::Streaming);
4135 }
4136
4137 #[test]
4138 fn test_audio_chunk_only_transitions_from_buffering() {
4139 let mut validator = StreamingUxValidator::new();
4140
4141 validator.record_metric(StreamingMetric::AudioChunk {
4143 samples: 1024,
4144 sample_rate: 16000,
4145 });
4146 assert_eq!(validator.state(), StreamingState::Idle);
4147 }
4148
4149 #[test]
4150 fn test_first_byte_received_only_transitions_from_idle() {
4151 let mut validator = StreamingUxValidator::new();
4152 validator.start(); validator.record_metric(StreamingMetric::FirstByteReceived);
4156 assert_eq!(validator.state(), StreamingState::Buffering);
4157 }
4158
4159 #[test]
4160 fn test_buffer_underrun_only_stalls_when_streaming() {
4161 let mut validator = StreamingUxValidator::new();
4162
4163 validator.record_metric(StreamingMetric::BufferUnderrun);
4165 assert_eq!(validator.state(), StreamingState::Idle);
4166
4167 validator.start();
4169 validator.record_metric(StreamingMetric::BufferUnderrun);
4170 assert_eq!(validator.state(), StreamingState::Buffering);
4171 }
4172
4173 #[test]
4174 fn test_validate_all_fps_error() {
4175 let mut validator = StreamingUxValidator::new().with_min_fps(60.0);
4176
4177 for i in 0..10 {
4179 validator.record_metric(StreamingMetric::FrameRendered {
4180 timestamp: i * 100, });
4182 }
4183
4184 let errors = validator.validate_all();
4185 assert!(errors
4186 .iter()
4187 .any(|e| matches!(e, StreamingValidationError::FpsBelowMinimum { .. })));
4188 }
4189
4190 #[test]
4191 fn test_validate_all_buffer_underrun_error() {
4192 let mut validator = StreamingUxValidator::new().with_buffer_underrun_threshold(1);
4193
4194 validator.record_metric(StreamingMetric::BufferUnderrun);
4195 validator.record_metric(StreamingMetric::BufferUnderrun);
4196
4197 let errors = validator.validate_all();
4198 assert!(errors
4199 .iter()
4200 .any(|e| matches!(e, StreamingValidationError::BufferUnderrunThreshold { .. })));
4201 }
4202
4203 #[test]
4204 fn test_validate_all_dropped_frames_error() {
4205 let mut validator = StreamingUxValidator::new().with_max_dropped_frames(1);
4206
4207 validator.record_metric(StreamingMetric::FrameDropped);
4208 validator.record_metric(StreamingMetric::FrameDropped);
4209
4210 let errors = validator.validate_all();
4211 assert!(errors
4212 .iter()
4213 .any(|e| matches!(e, StreamingValidationError::DroppedFrameThreshold { .. })));
4214 }
4215
4216 #[test]
4217 fn test_streaming_state_copy_clone() {
4218 let state = StreamingState::Streaming;
4219 let copied = state;
4220 let cloned = state;
4221 assert_eq!(copied, cloned);
4222 assert_eq!(state, StreamingState::Streaming);
4223 }
4224
4225 #[test]
4226 fn test_compression_algorithm_copy_clone() {
4227 let algo = CompressionAlgorithm::Zstd;
4228 let copied = algo;
4229 let cloned = algo;
4230 assert_eq!(copied, cloned);
4231 assert_eq!(algo, CompressionAlgorithm::Zstd);
4232 }
4233}