Skip to main content

jugar_probar/
validators.rs

1//! Streaming UX Validators (PROBAR-SPEC-011)
2//!
3//! Validate streaming interface patterns per specification Section 2.
4//!
5//! ## Toyota Way Application:
6//! - **Jidoka**: Real-time quality monitoring during streaming tests
7//! - **Poka-Yoke**: State machine validation prevents invalid transitions
8//! - **Andon**: Clear alerts when streaming metrics degrade
9//!
10//! ## References:
11//! - [10] Sewell et al. (2010) State machine verification
12//! - [11] Blott & Korn (2015) Low-latency patterns
13//! - [12] Sohn et al. (2015) VAD state machine testing
14
15use std::collections::VecDeque;
16use std::fmt;
17use std::time::{Duration, Instant};
18
19// =============================================================================
20// VU Meter Configuration (Section 2.4)
21// =============================================================================
22
23/// VU meter validation parameters
24///
25/// # Example
26/// ```
27/// use jugar_probar::validators::VuMeterConfig;
28///
29/// let config = VuMeterConfig::default()
30///     .with_min_level(0.1)
31///     .with_update_rate_hz(30.0);
32/// ```
33#[derive(Debug, Clone)]
34pub struct VuMeterConfig {
35    /// Minimum expected level (0.0-1.0)
36    pub min_level: f32,
37    /// Maximum expected level (0.0-1.0)
38    pub max_level: f32,
39    /// Update frequency (Hz)
40    pub update_rate_hz: f32,
41    /// Smoothing factor validation tolerance
42    pub smoothing_tolerance: f32,
43    /// Maximum time without updates (staleness)
44    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    /// Set minimum expected level
61    #[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    /// Set maximum expected level
68    #[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    /// Set expected update rate in Hz
75    #[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    /// Set staleness threshold
82    #[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    /// Validate a VU meter sample
89    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/// VU meter validation error
101#[derive(Debug, Clone)]
102pub enum VuMeterError {
103    /// Level is negative
104    NegativeLevel(f32),
105    /// Level exceeds maximum (clipping)
106    Clipping(f32),
107    /// Updates are stale
108    Stale {
109        /// Last update timestamp
110        last_update_ms: u64,
111        /// Current timestamp
112        current_ms: u64,
113    },
114    /// Update rate too slow
115    SlowUpdateRate {
116        /// Measured rate
117        measured_hz: f32,
118        /// Expected rate
119        expected_hz: f32,
120    },
121    /// Constant value detected (not animating)
122    NotAnimating {
123        /// Number of samples
124        sample_count: usize,
125        /// Constant value
126        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// =============================================================================
170// State Transition Tracking (Section 2.4)
171// =============================================================================
172
173/// State transition event with timing
174#[derive(Debug, Clone)]
175pub struct StateTransition {
176    /// Previous state
177    pub from: String,
178    /// New state
179    pub to: String,
180    /// Timestamp of transition (ms since start)
181    pub timestamp_ms: f64,
182    /// Duration in previous state (ms)
183    pub duration_ms: f64,
184}
185
186/// Partial transcription result
187#[derive(Debug, Clone)]
188pub struct PartialResult {
189    /// Timestamp (ms since start)
190    pub timestamp_ms: f64,
191    /// Partial text content
192    pub text: String,
193    /// Whether this is the final result
194    pub is_final: bool,
195}
196
197/// VU meter sample
198#[derive(Debug, Clone)]
199pub struct VuMeterSample {
200    /// Timestamp (ms since start)
201    pub timestamp_ms: f64,
202    /// Level (0.0-1.0)
203    pub level: f32,
204}
205
206// =============================================================================
207// Test Execution Stats (Section 5.1 - trueno-zram integration)
208// =============================================================================
209
210/// Test execution statistics with compression metrics
211///
212/// Tracks game state capture efficiency during test runs.
213/// Based on trueno-zram compression statistics patterns.
214///
215/// # Example
216/// ```
217/// use jugar_probar::validators::TestExecutionStats;
218///
219/// let mut stats = TestExecutionStats::new();
220/// stats.record_state_capture(4096, 1024);
221/// stats.record_state_capture(4096, 512); // Same-fill page
222///
223/// assert!(stats.efficiency() > 0.5);
224/// ```
225#[derive(Debug, Clone, Default)]
226pub struct TestExecutionStats {
227    /// Total game states captured
228    pub states_captured: u64,
229    /// Bytes before compression
230    pub bytes_raw: u64,
231    /// Bytes after compression
232    pub bytes_compressed: u64,
233    /// Same-fill pages detected (high compression)
234    pub same_fill_pages: u64,
235    /// Start time (for throughput calculation)
236    start_time: Option<Instant>,
237    /// End time (for throughput calculation)
238    end_time: Option<Instant>,
239}
240
241impl TestExecutionStats {
242    /// Create new stats tracker
243    #[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    /// Start tracking
256    pub fn start(&mut self) {
257        self.start_time = Some(Instant::now());
258    }
259
260    /// Stop tracking
261    pub fn stop(&mut self) {
262        self.end_time = Some(Instant::now());
263    }
264
265    /// Record a state capture
266    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        // Detect same-fill pages (>90% compression = likely uniform data)
272        if raw_bytes > 0 && (compressed_bytes as f64 / raw_bytes as f64) < 0.1 {
273            self.same_fill_pages += 1;
274        }
275    }
276
277    /// Calculate compression ratio (raw / compressed)
278    #[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    /// Calculate compression efficiency (1 - compressed/raw)
287    #[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    /// Estimate storage savings in MB
296    #[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    /// Calculate compression throughput (bytes/sec)
302    #[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    /// Get same-fill page ratio
318    #[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    /// Reset statistics
327    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// =============================================================================
338// Screenshot Content Classifier (Section 5.2)
339// =============================================================================
340
341/// Compression algorithm recommendation
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum CompressionAlgorithm {
344    /// LZ4 for speed (UI-heavy content)
345    Lz4,
346    /// Zstd for balanced speed/ratio
347    Zstd,
348    /// PNG for lossless images
349    Png,
350    /// RLE for uniform content
351    Rle,
352}
353
354/// Screenshot content classification for optimal compression
355///
356/// Based on entropy analysis to determine best compression strategy.
357///
358/// # Example
359/// ```
360/// use jugar_probar::validators::ScreenshotContent;
361///
362/// // Simulate UI-heavy screenshot (low entropy)
363/// let pixels: Vec<u8> = vec![255; 1000]; // Uniform white
364/// let content = ScreenshotContent::classify(&pixels);
365///
366/// assert!(matches!(content, ScreenshotContent::Uniform { .. }));
367/// ```
368#[derive(Debug, Clone)]
369pub enum ScreenshotContent {
370    /// UI-heavy (text, buttons) - high compressibility
371    UiDominated {
372        /// Shannon entropy (0.0-8.0 for bytes)
373        entropy: f32,
374    },
375    /// Physics/game world - medium compressibility
376    GameWorld {
377        /// Shannon entropy
378        entropy: f32,
379    },
380    /// Random/noise - low compressibility
381    HighEntropy {
382        /// Shannon entropy
383        entropy: f32,
384    },
385    /// Mostly uniform - very high compressibility (same-fill)
386    Uniform {
387        /// Dominant fill value
388        fill_value: u8,
389    },
390}
391
392impl ScreenshotContent {
393    /// Classify screenshot by entropy analysis
394    ///
395    /// Uses Shannon entropy to determine content type.
396    #[must_use]
397    pub fn classify(pixels: &[u8]) -> Self {
398        if pixels.is_empty() {
399            return Self::Uniform { fill_value: 0 };
400        }
401
402        // Count byte frequencies
403        let mut frequencies = [0u64; 256];
404        for &byte in pixels {
405            frequencies[byte as usize] += 1;
406        }
407
408        // Check for uniform content (>95% same value)
409        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        // Calculate Shannon entropy
424        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        // Classify based on entropy thresholds
434        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    /// Get entropy value
444    #[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    /// Recommended compression algorithm based on content type
455    #[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    /// Expected compression ratio hint
466    #[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/// Streaming UX validator for real-time audio/video interfaces
478///
479/// # Example
480/// ```
481/// use std::time::Duration;
482/// use jugar_probar::validators::{StreamingUxValidator, StreamingMetric};
483///
484/// let mut validator = StreamingUxValidator::new()
485///     .with_max_latency(Duration::from_millis(100))
486///     .with_buffer_underrun_threshold(3);
487///
488/// // Simulate streaming metrics
489/// validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
490/// validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
491///
492/// assert!(validator.validate().is_ok());
493/// ```
494#[derive(Debug, Clone)]
495pub struct StreamingUxValidator {
496    /// Maximum acceptable latency
497    max_latency: Duration,
498    /// Maximum buffer underrun count threshold
499    buffer_underrun_threshold: usize,
500    /// Maximum consecutive dropped frames
501    max_dropped_frames: usize,
502    /// Minimum frames per second
503    min_fps: f64,
504    /// Time-to-first-byte timeout
505    ttfb_timeout: Duration,
506    /// Recorded metrics
507    metrics: Vec<StreamingMetricRecord>,
508    /// Buffer underrun count
509    buffer_underruns: usize,
510    /// Dropped frame count
511    dropped_frames: usize,
512    /// Frame timestamps for FPS calculation
513    frame_times: VecDeque<u64>,
514    /// First byte received time
515    first_byte_time: Option<Instant>,
516    /// Start time
517    start_time: Option<Instant>,
518    /// State machine state
519    state: StreamingState,
520    /// State transition history
521    state_history: Vec<(StreamingState, Instant)>,
522}
523
524/// Streaming metric record with timestamp
525#[derive(Debug, Clone)]
526pub struct StreamingMetricRecord {
527    /// The metric
528    pub metric: StreamingMetric,
529    /// When recorded
530    pub timestamp: Instant,
531}
532
533/// Streaming metrics to record
534#[derive(Debug, Clone)]
535pub enum StreamingMetric {
536    /// Latency measurement
537    Latency(Duration),
538    /// Frame rendered with timestamp
539    FrameRendered {
540        /// Frame timestamp in milliseconds
541        timestamp: u64,
542    },
543    /// Frame dropped
544    FrameDropped,
545    /// Buffer underrun occurred
546    BufferUnderrun,
547    /// First byte received
548    FirstByteReceived,
549    /// Buffer level percentage
550    BufferLevel(f32),
551    /// Audio chunk processed
552    AudioChunk {
553        /// Sample count
554        samples: usize,
555        /// Sample rate
556        sample_rate: u32,
557    },
558}
559
560/// Streaming state machine states
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
562pub enum StreamingState {
563    /// Initial state
564    Idle,
565    /// Loading/buffering
566    Buffering,
567    /// Actively streaming
568    Streaming,
569    /// Stalled (buffer underrun)
570    Stalled,
571    /// Error state
572    Error,
573    /// Completed
574    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/// Streaming validation error
597#[derive(Debug, Clone)]
598pub enum StreamingValidationError {
599    /// Latency exceeded threshold
600    LatencyExceeded {
601        /// Measured latency
602        measured: Duration,
603        /// Maximum allowed
604        max: Duration,
605    },
606    /// Too many buffer underruns
607    BufferUnderrunThreshold {
608        /// Number of underruns
609        count: usize,
610        /// Threshold
611        threshold: usize,
612    },
613    /// Too many dropped frames
614    DroppedFrameThreshold {
615        /// Number of dropped frames
616        count: usize,
617        /// Maximum allowed
618        max: usize,
619    },
620    /// FPS below minimum
621    FpsBelowMinimum {
622        /// Measured FPS
623        measured: f64,
624        /// Minimum required
625        min: f64,
626    },
627    /// Time to first byte exceeded
628    TtfbExceeded {
629        /// Measured TTFB
630        measured: Duration,
631        /// Maximum allowed
632        max: Duration,
633    },
634    /// Invalid state transition
635    InvalidStateTransition {
636        /// From state
637        from: StreamingState,
638        /// To state
639        to: StreamingState,
640    },
641    /// State machine ended in error state
642    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    /// Create a new streaming UX validator with sensible defaults
687    #[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    /// Create validator for real-time audio streaming
707    #[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    /// Create validator for video streaming
716    #[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    /// Set maximum acceptable latency
725    #[must_use]
726    pub fn with_max_latency(mut self, latency: Duration) -> Self {
727        self.max_latency = latency;
728        self
729    }
730
731    /// Set buffer underrun threshold
732    #[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    /// Set maximum dropped frames
739    #[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    /// Set minimum FPS
746    #[must_use]
747    pub fn with_min_fps(mut self, fps: f64) -> Self {
748        self.min_fps = fps;
749        self
750    }
751
752    /// Set time-to-first-byte timeout
753    #[must_use]
754    pub fn with_ttfb_timeout(mut self, timeout: Duration) -> Self {
755        self.ttfb_timeout = timeout;
756        self
757    }
758
759    /// Start streaming validation
760    pub fn start(&mut self) {
761        self.start_time = Some(Instant::now());
762        self.transition_to(StreamingState::Buffering);
763    }
764
765    /// Record a streaming metric
766    pub fn record_metric(&mut self, metric: StreamingMetric) {
767        let now = Instant::now();
768
769        match &metric {
770            StreamingMetric::Latency(latency) => {
771                // Latency recorded - check if streaming
772                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                // Keep only last 120 frames (4 seconds at 30fps)
779                while self.frame_times.len() > 120 {
780                    self.frame_times.pop_front();
781                }
782                // If streaming, we're healthy
783                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                // Low buffer level indicates potential stall
804                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                // Audio chunk received - mark streaming active
812                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    /// Transition to a new state
825    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    /// Mark streaming as completed
833    pub fn complete(&mut self) {
834        self.transition_to(StreamingState::Completed);
835    }
836
837    /// Mark streaming as errored
838    pub fn error(&mut self) {
839        self.transition_to(StreamingState::Error);
840    }
841
842    /// Get current state
843    #[must_use]
844    pub fn state(&self) -> StreamingState {
845        self.state
846    }
847
848    /// Get buffer underrun count
849    #[must_use]
850    pub fn buffer_underruns(&self) -> usize {
851        self.buffer_underruns
852    }
853
854    /// Get dropped frame count
855    #[must_use]
856    pub fn dropped_frames(&self) -> usize {
857        self.dropped_frames
858    }
859
860    /// Calculate average FPS from recorded frames
861    #[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    /// Validate all recorded metrics
880    ///
881    /// # Errors
882    /// Returns validation error if any threshold is exceeded.
883    pub fn validate(&self) -> Result<StreamingValidationResult, StreamingValidationError> {
884        let mut errors = Vec::new();
885
886        // Check latency metrics
887        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        // Check buffer underruns
899        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        // Check dropped frames
907        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        // Check FPS
915        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        // Check TTFB
924        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        // Check final state
935        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    /// Get all validation errors (for comprehensive reporting)
953    #[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    /// Get maximum recorded latency
998    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    /// Get state history for debugging
1013    #[must_use]
1014    pub fn state_history(&self) -> &[(StreamingState, Instant)] {
1015        &self.state_history
1016    }
1017
1018    /// Reset validator for reuse
1019    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/// Successful validation result
1032#[derive(Debug, Clone)]
1033pub struct StreamingValidationResult {
1034    /// Number of buffer underruns
1035    pub buffer_underruns: usize,
1036    /// Number of dropped frames
1037    pub dropped_frames: usize,
1038    /// Average FPS
1039    pub average_fps: f64,
1040    /// Maximum latency recorded
1041    pub max_latency_recorded: Duration,
1042    /// Total frames processed
1043    pub total_frames: usize,
1044}
1045
1046// CDP-based streaming UX tracking (PROBAR-SPEC-011)
1047#[cfg(feature = "browser")]
1048impl StreamingUxValidator {
1049    /// Track state changes on element via CDP
1050    ///
1051    /// Sets up a MutationObserver to watch for text content changes
1052    /// on the specified element and records state transitions.
1053    ///
1054    /// # Errors
1055    /// Returns error if CDP injection fails or element not found.
1056    pub async fn track_state_cdp(
1057        page: &chromiumoxide::Page,
1058        selector: &str,
1059    ) -> Result<Self, StreamingValidationError> {
1060        // Inject MutationObserver to track text changes
1061        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    /// Track VU meter levels via CDP
1117    ///
1118    /// Sets up an animation frame loop to sample VU meter values
1119    /// from the specified element's width, height, or data attribute.
1120    ///
1121    /// # Errors
1122    /// Returns error if CDP injection fails.
1123    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    /// Track partial transcription results via CDP
1174    ///
1175    /// Watches for text content changes on the specified element
1176    /// and records partial results with timestamps.
1177    ///
1178    /// # Errors
1179    /// Returns error if CDP injection fails.
1180    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    /// Collect state history from browser
1230    ///
1231    /// # Errors
1232    /// Returns error if CDP call fails.
1233    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    /// Collect VU meter samples from browser
1268    ///
1269    /// # Errors
1270    /// Returns error if CDP call fails.
1271    pub async fn collect_vu_samples_cdp(
1272        &self,
1273        page: &chromiumoxide::Page,
1274    ) -> Result<Vec<VuMeterSample>, StreamingValidationError> {
1275        // Stop sampling
1276        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    /// Collect partial results from browser
1301    ///
1302    /// # Errors
1303    /// Returns error if CDP call fails.
1304    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        // Mark the last one as final if present
1330        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    /// Assert state sequence occurred in order
1342    ///
1343    /// # Errors
1344    /// Returns error if sequence was not observed.
1345    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    /// Assert VU meter was active during period
1375    ///
1376    /// # Errors
1377    /// Returns error if VU meter was not active for required duration.
1378    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    /// Assert no UI jank (state updates within threshold)
1409    ///
1410    /// # Errors
1411    /// Returns error if gap between updates exceeds threshold.
1412    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    /// Assert partial results appeared before final result
1432    ///
1433    /// # Errors
1434    /// Returns error if no partials appeared before final.
1435    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        // Need at least 2 results (one partial, one final)
1442        if partials.len() < 2 {
1443            return Err(StreamingValidationError::EndedInError);
1444        }
1445
1446        // Verify there's at least one non-final result before the final
1447        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    /// Get average state transition time in milliseconds
1456    ///
1457    /// # Errors
1458    /// Returns error if CDP call fails.
1459    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    // ========================================================================
1480    // H7: Streaming latency monitoring is accurate - Falsification tests
1481    // ========================================================================
1482
1483    #[test]
1484    fn f029_latency_exceeded() {
1485        // Falsification: Latency above threshold should fail validation
1486        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        // Falsification: Latency below threshold should pass
1503        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        // Falsification: Too many buffer underruns should fail
1514        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        // Falsification: Too many dropped frames should fail
1531        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    // ========================================================================
1546    // H8: State machine transitions are valid - Falsification tests
1547    // ========================================================================
1548
1549    #[test]
1550    fn f033_state_idle_to_buffering() {
1551        // Falsification: FirstByteReceived should transition Idle -> Buffering
1552        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        // Falsification: Audio chunk should transition Buffering -> Streaming
1562        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        // Falsification: Buffer underrun should transition Streaming -> Stalled
1576        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        // Falsification: Frame rendered should recover Stalled -> Streaming
1591        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    // ========================================================================
1605    // H9: FPS calculation is accurate - Falsification tests
1606    // ========================================================================
1607
1608    #[test]
1609    fn f037_fps_calculation() {
1610        // Falsification: FPS should be calculated correctly
1611        let mut validator = StreamingUxValidator::new();
1612
1613        // Simulate 30fps for 1 second
1614        for i in 0..31 {
1615            validator.record_metric(StreamingMetric::FrameRendered {
1616                timestamp: i * 33, // ~30fps
1617            });
1618        }
1619
1620        let fps = validator.average_fps();
1621        // Should be approximately 30fps
1622        assert!((fps - 30.0).abs() < 1.0, "FPS was {fps}, expected ~30");
1623    }
1624
1625    #[test]
1626    fn f038_fps_below_minimum() {
1627        // Falsification: Low FPS should fail validation
1628        let mut validator = StreamingUxValidator::new().with_min_fps(30.0);
1629
1630        // Simulate 15fps for 1 second
1631        for i in 0..16 {
1632            validator.record_metric(StreamingMetric::FrameRendered {
1633                timestamp: i * 66, // ~15fps
1634            });
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    // ========================================================================
1646    // Unit tests for core functionality
1647    // ========================================================================
1648
1649    #[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        // Low buffer level should stall
1738        validator.record_metric(StreamingMetric::BufferLevel(0.05));
1739        assert_eq!(validator.state(), StreamingState::Stalled);
1740
1741        // Buffer recovery should resume streaming
1742        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    // ========================================================================
1754    // H10: VU Meter validation is accurate - Falsification tests
1755    // ========================================================================
1756
1757    #[test]
1758    fn f039_vu_meter_negative_level_rejected() {
1759        // Falsification: Negative levels should be rejected
1760        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        // Falsification: Level above max (with tolerance) should be clipping
1772        let config = VuMeterConfig::default().with_max_level(1.0);
1773        // Level 1.5 exceeds 1.0 + 0.1 tolerance
1774        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        // Falsification: Valid level should pass
1782        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        // Falsification: Builder methods should work correctly
1791        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        // Falsification: Out-of-range levels should be clamped in config
1806        let config = VuMeterConfig::default()
1807            .with_min_level(-5.0)
1808            .with_max_level(10.0);
1809
1810        // Clamped to 0.0-1.0 range
1811        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        // Falsification: Update rate should have minimum of 1.0 Hz
1818        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        // Falsification: Error messages should be informative
1826        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        // Falsification: State transitions should be properly structured
1854        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        // Falsification: Partial results should track interim transcriptions
1870        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        // Falsification: VU meter samples should track level over time
1891        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        // Calculate average level
1911        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        // Check time span
1915        let duration = samples.last().unwrap().timestamp_ms - samples.first().unwrap().timestamp_ms;
1916        assert!((duration - 100.0).abs() < f64::EPSILON);
1917    }
1918
1919    // ========================================================================
1920    // H11: Test Execution Stats are accurate - Falsification tests (Section 5.1)
1921    // ========================================================================
1922
1923    #[test]
1924    fn f049_test_execution_stats_creation() {
1925        // Falsification: New stats should be zero-initialized
1926        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        // Falsification: Stats should correctly record captures
1936        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        // Falsification: Compression ratio should be raw/compressed
1948        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        // Falsification: Efficiency should be 1 - (compressed/raw)
1958        let mut stats = TestExecutionStats::new();
1959        stats.record_state_capture(1000, 250); // 75% efficiency
1960
1961        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        // Falsification: Storage savings should be in MB
1968        let mut stats = TestExecutionStats::new();
1969        stats.record_state_capture(5_000_000, 1_000_000); // 4MB saved
1970
1971        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        // Falsification: >90% compression should be detected as same-fill
1978        let mut stats = TestExecutionStats::new();
1979        stats.record_state_capture(4096, 100); // 97.5% compression - same-fill
1980        stats.record_state_capture(4096, 1024); // 75% compression - not same-fill
1981
1982        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        // Falsification: Reset should clear all stats
1989        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        // Falsification: Edge cases should not panic
2001        let mut stats = TestExecutionStats::new();
2002
2003        // Zero bytes
2004        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        // Record with zero compressed
2009        stats.record_state_capture(1000, 0);
2010        assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON); // Avoid division by zero
2011    }
2012
2013    // ========================================================================
2014    // H12: Screenshot content classification is accurate - Falsification tests (Section 5.2)
2015    // ========================================================================
2016
2017    #[test]
2018    fn f057_screenshot_content_uniform_detection() {
2019        // Falsification: >95% same value should be classified as Uniform
2020        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        // Falsification: Low entropy (<3.0) should be UI-dominated
2033        // Simulate mostly uniform with some variation (like UI text)
2034        let mut pixels = vec![255u8; 900]; // 90% white
2035        pixels.extend(vec![0u8; 50]); // 5% black
2036        pixels.extend(vec![128u8; 50]); // 5% gray
2037
2038        let content = ScreenshotContent::classify(&pixels);
2039        // This should not be Uniform since it's <95% same value
2040        // And should have low entropy
2041        assert!(matches!(
2042            content,
2043            ScreenshotContent::UiDominated { .. } | ScreenshotContent::Uniform { .. }
2044        ));
2045    }
2046
2047    #[test]
2048    fn f059_screenshot_content_high_entropy() {
2049        // Falsification: Random data should be classified as HighEntropy
2050        // Create pseudo-random looking data
2051        let pixels: Vec<u8> = (0..1000).map(|i| ((i * 127 + 37) % 256) as u8).collect();
2052        let content = ScreenshotContent::classify(&pixels);
2053
2054        // Should be GameWorld or HighEntropy depending on actual entropy
2055        assert!(matches!(
2056            content,
2057            ScreenshotContent::GameWorld { .. } | ScreenshotContent::HighEntropy { .. }
2058        ));
2059    }
2060
2061    #[test]
2062    fn f060_screenshot_content_compression_algorithm() {
2063        // Falsification: Compression algorithm should match content type
2064        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        // Falsification: Ratio hints should describe compression expectations
2080        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        // Falsification: Empty input should be handled gracefully
2096        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        // Falsification: Entropy should be extractable from all variants
2106        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); // Uniform has 0 entropy
2118    }
2119
2120    // ========================================================================
2121    // Additional coverage tests for validators.rs
2122    // ========================================================================
2123
2124    #[test]
2125    fn test_execution_stats_start_stop_throughput() {
2126        // Test start/stop timing and throughput calculation
2127        let mut stats = TestExecutionStats::new();
2128        stats.start();
2129
2130        // Record some captures
2131        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        // Throughput should be > 0 after recording data
2137        let throughput = stats.compress_throughput();
2138        // May be 0 if test runs too fast, but shouldn't panic
2139        assert!(throughput >= 0.0);
2140    }
2141
2142    #[test]
2143    fn test_execution_stats_throughput_no_timing() {
2144        // Test throughput without start/stop
2145        let mut stats = TestExecutionStats::new();
2146        stats.record_state_capture(1000, 100);
2147
2148        // Should return 0 when no timing is set
2149        assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
2150    }
2151
2152    #[test]
2153    fn test_execution_stats_throughput_start_only() {
2154        // Test throughput with only start (no stop)
2155        let mut stats = TestExecutionStats::new();
2156        stats.start();
2157        stats.record_state_capture(1000, 100);
2158
2159        // Should return 0 when end time not set
2160        assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
2161    }
2162
2163    #[test]
2164    fn test_streaming_validation_error_display() {
2165        // Test Display implementations for all error variants
2166        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        // Start and wait for first byte
2240        validator.start();
2241
2242        // Simulate waiting too long before first byte
2243        std::thread::sleep(Duration::from_millis(150));
2244
2245        // Record first byte
2246        validator.record_metric(StreamingMetric::FirstByteReceived);
2247
2248        let result = validator.validate();
2249        // TTFB should be exceeded
2250        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        // Start and immediately receive first byte
2261        validator.start();
2262        validator.record_metric(StreamingMetric::FirstByteReceived);
2263
2264        // Other metrics to make it valid
2265        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        // Ensure all variants are distinct
2277        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        // Test std::error::Error implementation
2346        let err = StreamingValidationError::EndedInError;
2347        let _: &dyn std::error::Error = &err;
2348    }
2349
2350    #[test]
2351    fn test_vu_meter_error_as_error() {
2352        // Test std::error::Error implementation
2353        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        // Ensure all variants can be created
2360        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    // Additional coverage tests
2415
2416    #[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        // Create medium entropy data
2452        let mut pixels = Vec::with_capacity(1000);
2453        for i in 0..1000 {
2454            pixels.push((i % 64) as u8); // Moderate variation
2455        }
2456        let content = ScreenshotContent::classify(&pixels);
2457        // With 64 unique values, entropy should be ~6 bits
2458        match content {
2459            ScreenshotContent::GameWorld { entropy } => {
2460                assert!((3.0..6.0).contains(&entropy));
2461            }
2462            ScreenshotContent::HighEntropy { entropy } => {
2463                // Also acceptable for this pattern
2464                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        // Record good latency - should transition to Streaming
2489        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        // Buffer level should be recorded
2499        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        // Add more than 120 frames to test the overflow handling
2513        for i in 0..150 {
2514            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 33 });
2515        }
2516
2517        // Should have capped at 120 frames
2518        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        // Cover all metric variants
2528        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        // Access the validate method which uses max_recorded_latency
2551        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        // Add some slow frames
2562        for i in 0..10 {
2563            validator.record_metric(StreamingMetric::FrameRendered {
2564                timestamp: i * 100, // 10fps
2565            });
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        // Test UI-dominated (entropy < 3.0)
2576        let mut pixels = Vec::with_capacity(1000);
2577        for i in 0..1000 {
2578            pixels.push((i % 4) as u8); // Only 4 unique values = low entropy
2579        }
2580        let content = ScreenshotContent::classify(&pixels);
2581        // Other classifications possible
2582        if let ScreenshotContent::UiDominated { entropy } = content {
2583            assert!(entropy < 3.0);
2584        }
2585    }
2586
2587    #[test]
2588    fn test_test_execution_stats_default() {
2589        let stats: TestExecutionStats = Default::default();
2590        assert_eq!(stats.states_captured, 0);
2591    }
2592
2593    #[test]
2594    fn test_streaming_validation_result_success() {
2595        let mut validator = StreamingUxValidator::new();
2596        validator.start();
2597        validator.record_metric(StreamingMetric::FirstByteReceived);
2598        validator.record_metric(StreamingMetric::AudioChunk {
2599            samples: 1024,
2600            sample_rate: 16000,
2601        });
2602
2603        // Add enough frames for good FPS
2604        for i in 0..60 {
2605            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 33 });
2606        }
2607
2608        validator.complete();
2609
2610        let result = validator.validate();
2611        assert!(result.is_ok());
2612        if let Ok(result) = result {
2613            assert!(result.max_latency_recorded >= Duration::ZERO);
2614            assert!(result.average_fps >= 0.0);
2615        }
2616    }
2617
2618    #[test]
2619    fn test_partial_result_clone() {
2620        let result = PartialResult {
2621            timestamp_ms: 100.0,
2622            text: "test".to_string(),
2623            is_final: false,
2624        };
2625        let cloned = result;
2626        assert_eq!(cloned.text, "test");
2627    }
2628
2629    #[test]
2630    fn test_vu_meter_sample_clone() {
2631        let sample = VuMeterSample {
2632            timestamp_ms: 100.0,
2633            level: 0.5,
2634        };
2635        let cloned = sample;
2636        assert!((cloned.level - 0.5).abs() < f32::EPSILON);
2637    }
2638
2639    #[test]
2640    fn test_streaming_metric_record_clone() {
2641        let record = StreamingMetricRecord {
2642            metric: StreamingMetric::BufferLevel(0.5),
2643            timestamp: Instant::now(),
2644        };
2645        let cloned = record;
2646        assert!(matches!(cloned.metric, StreamingMetric::BufferLevel(..)));
2647    }
2648
2649    #[test]
2650    fn test_streaming_validation_error_clone() {
2651        let err = StreamingValidationError::LatencyExceeded {
2652            measured: Duration::from_millis(150),
2653            max: Duration::from_millis(100),
2654        };
2655        let cloned = err;
2656        assert!(matches!(
2657            cloned,
2658            StreamingValidationError::LatencyExceeded { .. }
2659        ));
2660    }
2661
2662    #[test]
2663    fn test_vu_meter_error_clone() {
2664        let err = VuMeterError::Clipping(1.5);
2665        let cloned = err;
2666        assert!(matches!(cloned, VuMeterError::Clipping(..)));
2667    }
2668
2669    // ========================================================================
2670    // Additional comprehensive tests for 95%+ coverage
2671    // ========================================================================
2672
2673    #[test]
2674    fn test_average_fps_with_zero_duration() {
2675        let mut validator = StreamingUxValidator::new();
2676        // Add frames with same timestamp - zero duration
2677        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2678        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2679
2680        // Should return 0.0 when duration is 0
2681        assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2682    }
2683
2684    #[test]
2685    fn test_average_fps_single_frame() {
2686        let mut validator = StreamingUxValidator::new();
2687        // Only one frame - not enough to calculate FPS
2688        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2689
2690        assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
2691    }
2692
2693    #[test]
2694    fn test_streaming_validation_result_fields() {
2695        let mut validator = StreamingUxValidator::new()
2696            .with_max_latency(Duration::from_secs(10))
2697            .with_buffer_underrun_threshold(100)
2698            .with_max_dropped_frames(100)
2699            .with_min_fps(1.0);
2700
2701        validator.start();
2702        validator.record_metric(StreamingMetric::FirstByteReceived);
2703        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
2704
2705        // Add frames for FPS
2706        for i in 0..60 {
2707            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
2708        }
2709
2710        let result = validator.validate().unwrap();
2711        assert_eq!(result.buffer_underruns, 0);
2712        assert_eq!(result.dropped_frames, 0);
2713        assert!(result.average_fps > 0.0);
2714        assert!(result.total_frames > 0);
2715        assert!(result.max_latency_recorded >= Duration::ZERO);
2716    }
2717
2718    #[test]
2719    fn test_max_recorded_latency_empty() {
2720        let validator = StreamingUxValidator::new();
2721        // No latency metrics recorded - should use max_recorded_latency internally
2722        let result = validator.validate();
2723        assert!(result.is_ok());
2724    }
2725
2726    #[test]
2727    fn test_compression_ratio_with_zero_raw() {
2728        let mut stats = TestExecutionStats::new();
2729        // Record with 0 raw bytes
2730        stats.bytes_raw = 0;
2731        stats.bytes_compressed = 100;
2732        // compression_ratio should handle this edge case
2733        assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
2734    }
2735
2736    #[test]
2737    fn test_throughput_with_zero_duration() {
2738        let mut stats = TestExecutionStats::new();
2739        stats.start();
2740        stats.record_state_capture(1000, 100);
2741        // Stop immediately - very short duration
2742        stats.stop();
2743
2744        // Should not panic even with very small duration
2745        let throughput = stats.compress_throughput();
2746        assert!(throughput >= 0.0);
2747    }
2748
2749    #[test]
2750    fn test_vu_meter_error_stale_clone() {
2751        let err = VuMeterError::Stale {
2752            last_update_ms: 100,
2753            current_ms: 200,
2754        };
2755        let cloned = err;
2756        assert!(matches!(
2757            cloned,
2758            VuMeterError::Stale {
2759                last_update_ms: 100,
2760                current_ms: 200,
2761            }
2762        ));
2763    }
2764
2765    #[test]
2766    fn test_vu_meter_error_slow_update_rate_clone() {
2767        let err = VuMeterError::SlowUpdateRate {
2768            measured_hz: 15.0,
2769            expected_hz: 30.0,
2770        };
2771        let cloned = err;
2772        match cloned {
2773            VuMeterError::SlowUpdateRate {
2774                measured_hz,
2775                expected_hz,
2776            } => {
2777                assert!((measured_hz - 15.0).abs() < f32::EPSILON);
2778                assert!((expected_hz - 30.0).abs() < f32::EPSILON);
2779            }
2780            _ => panic!("Expected SlowUpdateRate"),
2781        }
2782    }
2783
2784    #[test]
2785    fn test_vu_meter_error_not_animating_clone() {
2786        let err = VuMeterError::NotAnimating {
2787            sample_count: 10,
2788            value: 0.5,
2789        };
2790        let cloned = err;
2791        match cloned {
2792            VuMeterError::NotAnimating {
2793                sample_count,
2794                value,
2795            } => {
2796                assert_eq!(sample_count, 10);
2797                assert!((value - 0.5).abs() < f32::EPSILON);
2798            }
2799            _ => panic!("Expected NotAnimating"),
2800        }
2801    }
2802
2803    #[test]
2804    fn test_streaming_validation_error_all_clone_variants() {
2805        // Test all error variants clone correctly
2806        let errors: Vec<StreamingValidationError> = vec![
2807            StreamingValidationError::LatencyExceeded {
2808                measured: Duration::from_millis(200),
2809                max: Duration::from_millis(100),
2810            },
2811            StreamingValidationError::BufferUnderrunThreshold {
2812                count: 10,
2813                threshold: 5,
2814            },
2815            StreamingValidationError::DroppedFrameThreshold { count: 20, max: 10 },
2816            StreamingValidationError::FpsBelowMinimum {
2817                measured: 15.0,
2818                min: 30.0,
2819            },
2820            StreamingValidationError::TtfbExceeded {
2821                measured: Duration::from_secs(5),
2822                max: Duration::from_secs(2),
2823            },
2824            StreamingValidationError::InvalidStateTransition {
2825                from: StreamingState::Idle,
2826                to: StreamingState::Completed,
2827            },
2828            StreamingValidationError::EndedInError,
2829        ];
2830
2831        for err in errors {
2832            let cloned = err.clone();
2833            // Verify toString works on cloned
2834            let _ = cloned.to_string();
2835        }
2836    }
2837
2838    #[test]
2839    fn test_streaming_metric_clone_all_variants() {
2840        let metrics = vec![
2841            StreamingMetric::Latency(Duration::from_millis(100)),
2842            StreamingMetric::FrameRendered { timestamp: 1000 },
2843            StreamingMetric::FrameDropped,
2844            StreamingMetric::BufferUnderrun,
2845            StreamingMetric::FirstByteReceived,
2846            StreamingMetric::BufferLevel(0.75),
2847            StreamingMetric::AudioChunk {
2848                samples: 2048,
2849                sample_rate: 44100,
2850            },
2851        ];
2852
2853        for metric in metrics {
2854            let cloned = metric.clone();
2855            let _ = format!("{:?}", cloned);
2856        }
2857    }
2858
2859    #[test]
2860    fn test_buffer_level_no_transition_when_not_streaming() {
2861        let mut validator = StreamingUxValidator::new();
2862        // Not started, not streaming
2863        validator.record_metric(StreamingMetric::BufferLevel(0.05));
2864        assert_eq!(validator.state(), StreamingState::Idle);
2865
2866        // Buffer recovery when not stalled
2867        validator.record_metric(StreamingMetric::BufferLevel(0.5));
2868        assert_eq!(validator.state(), StreamingState::Idle);
2869    }
2870
2871    #[test]
2872    fn test_latency_no_transition_when_exceeds_max() {
2873        let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
2874        validator.start();
2875        assert_eq!(validator.state(), StreamingState::Buffering);
2876
2877        // High latency should not transition to streaming
2878        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
2879        assert_eq!(validator.state(), StreamingState::Buffering);
2880    }
2881
2882    #[test]
2883    fn test_frame_rendered_no_transition_when_not_stalled() {
2884        let mut validator = StreamingUxValidator::new();
2885        // In Idle state
2886        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
2887        assert_eq!(validator.state(), StreamingState::Idle);
2888
2889        // In Buffering state
2890        validator.start();
2891        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 200 });
2892        assert_eq!(validator.state(), StreamingState::Buffering);
2893    }
2894
2895    #[test]
2896    fn test_audio_chunk_no_transition_when_not_buffering() {
2897        let mut validator = StreamingUxValidator::new();
2898        // In Idle state - should not transition
2899        validator.record_metric(StreamingMetric::AudioChunk {
2900            samples: 1024,
2901            sample_rate: 16000,
2902        });
2903        assert_eq!(validator.state(), StreamingState::Idle);
2904
2905        // In Streaming state - should stay streaming
2906        validator.start();
2907        validator.record_metric(StreamingMetric::AudioChunk {
2908            samples: 1024,
2909            sample_rate: 16000,
2910        });
2911        assert_eq!(validator.state(), StreamingState::Streaming);
2912        validator.record_metric(StreamingMetric::AudioChunk {
2913            samples: 1024,
2914            sample_rate: 16000,
2915        });
2916        assert_eq!(validator.state(), StreamingState::Streaming);
2917    }
2918
2919    #[test]
2920    fn test_first_byte_no_transition_when_not_idle() {
2921        let mut validator = StreamingUxValidator::new();
2922        validator.start(); // Now in Buffering
2923        validator.record_metric(StreamingMetric::FirstByteReceived);
2924        assert_eq!(validator.state(), StreamingState::Buffering);
2925    }
2926
2927    #[test]
2928    fn test_buffer_underrun_no_transition_when_not_streaming() {
2929        let mut validator = StreamingUxValidator::new();
2930        // In Idle state
2931        validator.record_metric(StreamingMetric::BufferUnderrun);
2932        assert_eq!(validator.state(), StreamingState::Idle);
2933        assert_eq!(validator.buffer_underruns(), 1);
2934
2935        // In Buffering state
2936        validator.start();
2937        validator.record_metric(StreamingMetric::BufferUnderrun);
2938        assert_eq!(validator.state(), StreamingState::Buffering);
2939        assert_eq!(validator.buffer_underruns(), 2);
2940    }
2941
2942    #[test]
2943    fn test_transition_to_same_state() {
2944        let mut validator = StreamingUxValidator::new();
2945        validator.start();
2946        let history_len = validator.state_history().len();
2947
2948        // Try to transition to current state - should not add to history
2949        validator.record_metric(StreamingMetric::BufferLevel(0.5)); // Does nothing in Buffering
2950        assert_eq!(validator.state_history().len(), history_len);
2951    }
2952
2953    #[test]
2954    fn test_screenshot_content_single_byte() {
2955        // Edge case: single byte input
2956        let content = ScreenshotContent::classify(&[128]);
2957        assert!(matches!(
2958            content,
2959            ScreenshotContent::Uniform { fill_value: 128 }
2960        ));
2961    }
2962
2963    #[test]
2964    fn test_screenshot_content_two_bytes_same() {
2965        let content = ScreenshotContent::classify(&[42, 42]);
2966        assert!(matches!(
2967            content,
2968            ScreenshotContent::Uniform { fill_value: 42 }
2969        ));
2970    }
2971
2972    #[test]
2973    fn test_screenshot_content_near_uniform_threshold() {
2974        // 94% same value - should NOT be uniform (threshold is 95%)
2975        let mut pixels = vec![255u8; 94];
2976        pixels.extend(vec![0u8; 6]);
2977        let content = ScreenshotContent::classify(&pixels);
2978        assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
2979    }
2980
2981    #[test]
2982    fn test_screenshot_content_exactly_at_uniform_threshold() {
2983        // 96% same value - should be uniform (> 95%)
2984        let mut pixels = vec![255u8; 96];
2985        pixels.extend(vec![0u8; 4]);
2986        let content = ScreenshotContent::classify(&pixels);
2987        assert!(matches!(
2988            content,
2989            ScreenshotContent::Uniform { fill_value: 255 }
2990        ));
2991    }
2992
2993    #[test]
2994    fn test_screenshot_content_entropy_at_boundary_3() {
2995        // Create data with entropy around 3.0 (UI vs GameWorld boundary)
2996        let mut pixels = Vec::new();
2997        // 8 unique values, equally distributed = log2(8) = 3 bits entropy
2998        for _ in 0..125 {
2999            for v in 0u8..8u8 {
3000                pixels.push(v);
3001            }
3002        }
3003        let content = ScreenshotContent::classify(&pixels);
3004        // Could be either UI or GameWorld depending on exact calculation
3005        let entropy = content.entropy();
3006        assert!((2.5..=3.5).contains(&entropy));
3007    }
3008
3009    #[test]
3010    fn test_screenshot_content_entropy_at_boundary_6() {
3011        // Create data with entropy around 6.0 (GameWorld vs HighEntropy boundary)
3012        let mut pixels = Vec::new();
3013        // 64 unique values = log2(64) = 6 bits entropy
3014        for _ in 0..16 {
3015            for v in 0u8..64u8 {
3016                pixels.push(v);
3017            }
3018        }
3019        let content = ScreenshotContent::classify(&pixels);
3020        let entropy = content.entropy();
3021        assert!((5.5..=6.5).contains(&entropy));
3022    }
3023
3024    #[test]
3025    fn test_screenshot_content_maximum_entropy() {
3026        // Create data with maximum entropy - all 256 values equally distributed
3027        let mut pixels = Vec::new();
3028        for _ in 0..4 {
3029            for v in 0u8..=255u8 {
3030                pixels.push(v);
3031            }
3032        }
3033        let content = ScreenshotContent::classify(&pixels);
3034        assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3035        assert!(content.entropy() > 7.0);
3036    }
3037
3038    #[test]
3039    fn test_validate_multiple_latency_exceeded() {
3040        let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
3041
3042        // Multiple latency violations
3043        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
3044        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(150)));
3045        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(200)));
3046
3047        let errors = validator.validate_all();
3048        assert_eq!(errors.len(), 3);
3049        for err in errors {
3050            assert!(matches!(
3051                err,
3052                StreamingValidationError::LatencyExceeded { .. }
3053            ));
3054        }
3055    }
3056
3057    #[test]
3058    fn test_streaming_validation_result_debug() {
3059        let result = StreamingValidationResult {
3060            buffer_underruns: 2,
3061            dropped_frames: 5,
3062            average_fps: 30.0,
3063            max_latency_recorded: Duration::from_millis(100),
3064            total_frames: 1000,
3065        };
3066        let debug = format!("{:?}", result);
3067        assert!(debug.contains("StreamingValidationResult"));
3068        assert!(debug.contains("buffer_underruns"));
3069    }
3070
3071    #[test]
3072    fn test_streaming_validation_result_clone() {
3073        let result = StreamingValidationResult {
3074            buffer_underruns: 3,
3075            dropped_frames: 7,
3076            average_fps: 60.0,
3077            max_latency_recorded: Duration::from_millis(50),
3078            total_frames: 2000,
3079        };
3080        let cloned = result;
3081        assert_eq!(cloned.buffer_underruns, 3);
3082        assert_eq!(cloned.dropped_frames, 7);
3083        assert!((cloned.average_fps - 60.0).abs() < f64::EPSILON);
3084        assert_eq!(cloned.max_latency_recorded, Duration::from_millis(50));
3085        assert_eq!(cloned.total_frames, 2000);
3086    }
3087
3088    #[test]
3089    fn test_vu_meter_config_smoothing_tolerance() {
3090        let config = VuMeterConfig {
3091            min_level: 0.0,
3092            max_level: 1.0,
3093            update_rate_hz: 30.0,
3094            smoothing_tolerance: 0.2,
3095            max_stale_ms: 100,
3096        };
3097
3098        // Level at max + tolerance should pass
3099        assert!(config.validate_sample(1.19).is_ok());
3100
3101        // Level beyond max + tolerance should fail
3102        assert!(config.validate_sample(1.21).is_err());
3103    }
3104
3105    #[test]
3106    fn test_compression_algorithm_debug() {
3107        let algos = [
3108            CompressionAlgorithm::Lz4,
3109            CompressionAlgorithm::Zstd,
3110            CompressionAlgorithm::Png,
3111            CompressionAlgorithm::Rle,
3112        ];
3113        for algo in algos {
3114            let debug = format!("{:?}", algo);
3115            assert!(!debug.is_empty());
3116        }
3117    }
3118
3119    #[test]
3120    fn test_compression_algorithm_copy() {
3121        let algo = CompressionAlgorithm::Lz4;
3122        let copied = algo;
3123        assert_eq!(algo, copied);
3124    }
3125
3126    #[test]
3127    fn test_test_execution_stats_large_values() {
3128        let mut stats = TestExecutionStats::new();
3129        stats.record_state_capture(u64::MAX / 2, u64::MAX / 4);
3130
3131        // Should handle large values without overflow
3132        let ratio = stats.compression_ratio();
3133        assert!(ratio > 0.0);
3134
3135        let efficiency = stats.efficiency();
3136        assert!(efficiency > 0.0 && efficiency < 1.0);
3137    }
3138
3139    #[test]
3140    fn test_storage_savings_small_values() {
3141        let mut stats = TestExecutionStats::new();
3142        stats.record_state_capture(500_000, 400_000); // 0.1 MB saved
3143
3144        let savings = stats.storage_savings_mb();
3145        assert!((savings - 0.1).abs() < 0.01);
3146    }
3147
3148    #[test]
3149    fn test_storage_savings_compressed_larger_than_raw() {
3150        let mut stats = TestExecutionStats::new();
3151        // Edge case: compressed somehow larger than raw (saturating_sub handles this)
3152        stats.bytes_raw = 100;
3153        stats.bytes_compressed = 200;
3154
3155        let savings = stats.storage_savings_mb();
3156        assert!((savings - 0.0).abs() < f64::EPSILON);
3157    }
3158
3159    #[test]
3160    fn test_same_fill_exactly_at_threshold() {
3161        let mut stats = TestExecutionStats::new();
3162        // Exactly 10% compression ratio - boundary case
3163        stats.record_state_capture(1000, 100);
3164        // 10% is not < 10%, so should not count as same-fill
3165        assert_eq!(stats.same_fill_pages, 0);
3166
3167        // Just under 10%
3168        stats.record_state_capture(1000, 99);
3169        assert_eq!(stats.same_fill_pages, 1);
3170    }
3171
3172    #[test]
3173    fn test_streaming_ux_validator_debug() {
3174        let validator = StreamingUxValidator::new();
3175        let debug = format!("{:?}", validator);
3176        assert!(debug.contains("StreamingUxValidator"));
3177    }
3178
3179    #[test]
3180    fn test_streaming_metric_record_debug() {
3181        let record = StreamingMetricRecord {
3182            metric: StreamingMetric::FrameDropped,
3183            timestamp: Instant::now(),
3184        };
3185        let debug = format!("{:?}", record);
3186        assert!(debug.contains("StreamingMetricRecord"));
3187    }
3188
3189    #[test]
3190    fn test_validate_all_empty_metrics() {
3191        let validator = StreamingUxValidator::new();
3192        let errors = validator.validate_all();
3193        assert!(errors.is_empty());
3194    }
3195
3196    #[test]
3197    fn test_validate_with_error_state() {
3198        let mut validator = StreamingUxValidator::new();
3199        validator.error();
3200
3201        let result = validator.validate();
3202        assert!(result.is_err());
3203        assert!(matches!(
3204            result.unwrap_err(),
3205            StreamingValidationError::EndedInError
3206        ));
3207    }
3208
3209    #[test]
3210    fn test_validate_all_multiple_error_types() {
3211        let mut validator = StreamingUxValidator::new()
3212            .with_max_latency(Duration::from_millis(10))
3213            .with_buffer_underrun_threshold(0)
3214            .with_max_dropped_frames(0)
3215            .with_min_fps(100.0);
3216
3217        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3218        validator.record_metric(StreamingMetric::BufferUnderrun);
3219        validator.record_metric(StreamingMetric::FrameDropped);
3220
3221        // Add slow frames for FPS error
3222        for i in 0..5 {
3223            validator.record_metric(StreamingMetric::FrameRendered {
3224                timestamp: i * 500, // 2 fps
3225            });
3226        }
3227
3228        validator.error();
3229
3230        let errors = validator.validate_all();
3231        // Should have: latency, underrun, dropped frames, fps, ended in error
3232        assert!(errors.len() >= 4);
3233    }
3234
3235    #[test]
3236    fn test_vu_meter_error_negative_level_value() {
3237        let config = VuMeterConfig::default();
3238        let result = config.validate_sample(-10.5);
3239        match result {
3240            Err(VuMeterError::NegativeLevel(v)) => {
3241                assert!((v - (-10.5)).abs() < f32::EPSILON);
3242            }
3243            _ => panic!("Expected NegativeLevel error"),
3244        }
3245    }
3246
3247    #[test]
3248    fn test_vu_meter_error_clipping_value() {
3249        let config = VuMeterConfig::default().with_max_level(0.5);
3250        let result = config.validate_sample(2.0);
3251        match result {
3252            Err(VuMeterError::Clipping(v)) => {
3253                assert!((v - 2.0).abs() < f32::EPSILON);
3254            }
3255            _ => panic!("Expected Clipping error"),
3256        }
3257    }
3258
3259    #[test]
3260    fn test_streaming_state_hash() {
3261        use std::collections::HashSet;
3262        let mut set = HashSet::new();
3263        set.insert(StreamingState::Idle);
3264        set.insert(StreamingState::Buffering);
3265        set.insert(StreamingState::Streaming);
3266        set.insert(StreamingState::Stalled);
3267        set.insert(StreamingState::Error);
3268        set.insert(StreamingState::Completed);
3269
3270        assert_eq!(set.len(), 6);
3271        assert!(set.contains(&StreamingState::Idle));
3272    }
3273
3274    #[test]
3275    fn test_screenshot_content_clone() {
3276        let contents = vec![
3277            ScreenshotContent::Uniform { fill_value: 128 },
3278            ScreenshotContent::UiDominated { entropy: 2.5 },
3279            ScreenshotContent::GameWorld { entropy: 4.5 },
3280            ScreenshotContent::HighEntropy { entropy: 7.0 },
3281        ];
3282
3283        for content in contents {
3284            let cloned = content.clone();
3285            assert!((cloned.entropy() - content.entropy()).abs() < f32::EPSILON);
3286        }
3287    }
3288
3289    #[test]
3290    fn test_test_execution_stats_all_fields() {
3291        let mut stats = TestExecutionStats::new();
3292        stats.start();
3293        stats.record_state_capture(1000, 100);
3294        stats.record_state_capture(2000, 50); // same-fill
3295        stats.stop();
3296
3297        assert_eq!(stats.states_captured, 2);
3298        assert_eq!(stats.bytes_raw, 3000);
3299        assert_eq!(stats.bytes_compressed, 150);
3300        assert_eq!(stats.same_fill_pages, 1);
3301    }
3302
3303    #[test]
3304    fn test_frame_times_exactly_120() {
3305        let mut validator = StreamingUxValidator::new();
3306        // Add exactly 120 frames
3307        for i in 0..120 {
3308            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3309        }
3310        assert!(validator.average_fps() > 0.0);
3311    }
3312
3313    #[test]
3314    fn test_frame_times_121() {
3315        let mut validator = StreamingUxValidator::new();
3316        // Add 121 frames - should cap at 120
3317        for i in 0..121 {
3318            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
3319        }
3320        // The oldest frame should be removed
3321        assert!(validator.average_fps() > 0.0);
3322    }
3323
3324    #[test]
3325    fn test_state_transition_fields_access() {
3326        let transition = StateTransition {
3327            from: "State1".to_string(),
3328            to: "State2".to_string(),
3329            timestamp_ms: 12345.67,
3330            duration_ms: 890.12,
3331        };
3332
3333        assert_eq!(transition.from.as_str(), "State1");
3334        assert_eq!(transition.to.as_str(), "State2");
3335        assert!((transition.timestamp_ms - 12345.67).abs() < f64::EPSILON);
3336        assert!((transition.duration_ms - 890.12).abs() < f64::EPSILON);
3337    }
3338
3339    #[test]
3340    fn test_partial_result_fields_access() {
3341        let partial = PartialResult {
3342            timestamp_ms: 999.99,
3343            text: "Hello World".to_string(),
3344            is_final: true,
3345        };
3346
3347        assert!((partial.timestamp_ms - 999.99).abs() < f64::EPSILON);
3348        assert_eq!(partial.text.as_str(), "Hello World");
3349        assert!(partial.is_final);
3350    }
3351
3352    #[test]
3353    fn test_vu_meter_sample_fields_access() {
3354        let sample = VuMeterSample {
3355            timestamp_ms: 1234.5,
3356            level: 0.789,
3357        };
3358
3359        assert!((sample.timestamp_ms - 1234.5).abs() < f64::EPSILON);
3360        assert!((sample.level - 0.789).abs() < f32::EPSILON);
3361    }
3362
3363    #[test]
3364    fn test_streaming_metric_record_fields_access() {
3365        let timestamp = Instant::now();
3366        let record = StreamingMetricRecord {
3367            metric: StreamingMetric::BufferLevel(0.42),
3368            timestamp,
3369        };
3370
3371        assert!(matches!(record.metric, StreamingMetric::BufferLevel(..)));
3372        assert_eq!(record.timestamp, timestamp);
3373    }
3374
3375    #[test]
3376    fn test_validate_returns_first_error() {
3377        let mut validator = StreamingUxValidator::new()
3378            .with_max_latency(Duration::from_millis(10))
3379            .with_buffer_underrun_threshold(0);
3380
3381        // Record latency error first (in order)
3382        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3383        validator.record_metric(StreamingMetric::BufferUnderrun);
3384
3385        let result = validator.validate();
3386        assert!(result.is_err());
3387        // Should return latency error (first in check order)
3388        assert!(matches!(
3389            result.unwrap_err(),
3390            StreamingValidationError::LatencyExceeded { .. }
3391        ));
3392    }
3393
3394    #[test]
3395    fn test_validate_fps_error_only_when_positive() {
3396        let mut validator = StreamingUxValidator::new().with_min_fps(100.0);
3397
3398        // No frames at all - fps is 0, should not trigger fps error
3399        let result = validator.validate();
3400        assert!(result.is_ok());
3401
3402        // Add one frame - fps is still 0
3403        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 0 });
3404        let result = validator.validate();
3405        assert!(result.is_ok());
3406    }
3407
3408    #[test]
3409    fn test_buffer_level_recovery_threshold() {
3410        let mut validator = StreamingUxValidator::new();
3411        validator.start();
3412        validator.record_metric(StreamingMetric::AudioChunk {
3413            samples: 1024,
3414            sample_rate: 16000,
3415        });
3416        assert_eq!(validator.state(), StreamingState::Streaming);
3417
3418        // Low buffer - should stall
3419        validator.record_metric(StreamingMetric::BufferLevel(0.05));
3420        assert_eq!(validator.state(), StreamingState::Stalled);
3421
3422        // Buffer at exactly 0.3 - should NOT recover (threshold is > 0.3)
3423        validator.record_metric(StreamingMetric::BufferLevel(0.3));
3424        assert_eq!(validator.state(), StreamingState::Stalled);
3425
3426        // Buffer above 0.3 - should recover
3427        validator.record_metric(StreamingMetric::BufferLevel(0.31));
3428        assert_eq!(validator.state(), StreamingState::Streaming);
3429    }
3430
3431    #[test]
3432    fn test_buffer_level_stall_threshold() {
3433        let mut validator = StreamingUxValidator::new();
3434        validator.start();
3435        validator.record_metric(StreamingMetric::AudioChunk {
3436            samples: 1024,
3437            sample_rate: 16000,
3438        });
3439        assert_eq!(validator.state(), StreamingState::Streaming);
3440
3441        // Buffer at exactly 0.1 - should NOT stall (threshold is < 0.1)
3442        validator.record_metric(StreamingMetric::BufferLevel(0.1));
3443        assert_eq!(validator.state(), StreamingState::Streaming);
3444
3445        // Buffer below 0.1 - should stall
3446        validator.record_metric(StreamingMetric::BufferLevel(0.09));
3447        assert_eq!(validator.state(), StreamingState::Stalled);
3448    }
3449
3450    // ========================================================================
3451    // Additional tests for 95%+ coverage - Edge cases and branches
3452    // ========================================================================
3453
3454    #[test]
3455    fn test_vu_meter_config_default_values() {
3456        let config = VuMeterConfig::default();
3457        assert!((config.min_level - 0.0).abs() < f32::EPSILON);
3458        assert!((config.max_level - 1.0).abs() < f32::EPSILON);
3459        assert!((config.update_rate_hz - 30.0).abs() < f32::EPSILON);
3460        assert!((config.smoothing_tolerance - 0.1).abs() < f32::EPSILON);
3461        assert_eq!(config.max_stale_ms, 100);
3462    }
3463
3464    #[test]
3465    fn test_vu_meter_error_display_all_variants() {
3466        // NegativeLevel
3467        let err = VuMeterError::NegativeLevel(-0.25);
3468        let display = format!("{}", err);
3469        assert!(display.contains("-0.25"));
3470        assert!(display.contains("negative"));
3471
3472        // Clipping
3473        let err = VuMeterError::Clipping(1.75);
3474        let display = format!("{}", err);
3475        assert!(display.contains("1.75"));
3476        assert!(display.contains("clipping"));
3477
3478        // Stale
3479        let err = VuMeterError::Stale {
3480            last_update_ms: 50,
3481            current_ms: 250,
3482        };
3483        let display = format!("{}", err);
3484        assert!(display.contains("200ms"));
3485
3486        // SlowUpdateRate
3487        let err = VuMeterError::SlowUpdateRate {
3488            measured_hz: 20.0,
3489            expected_hz: 60.0,
3490        };
3491        let display = format!("{}", err);
3492        assert!(display.contains("20.0Hz"));
3493        assert!(display.contains("60.0Hz"));
3494
3495        // NotAnimating
3496        let err = VuMeterError::NotAnimating {
3497            sample_count: 50,
3498            value: 0.75,
3499        };
3500        let display = format!("{}", err);
3501        assert!(display.contains("50 samples"));
3502        assert!(display.contains("0.75"));
3503    }
3504
3505    #[test]
3506    fn test_streaming_validation_error_display_all_variants() {
3507        let err = StreamingValidationError::LatencyExceeded {
3508            measured: Duration::from_millis(300),
3509            max: Duration::from_millis(100),
3510        };
3511        assert!(err.to_string().contains("300"));
3512
3513        let err = StreamingValidationError::BufferUnderrunThreshold {
3514            count: 15,
3515            threshold: 5,
3516        };
3517        assert!(err.to_string().contains("15"));
3518        assert!(err.to_string().contains('5'));
3519
3520        let err = StreamingValidationError::DroppedFrameThreshold { count: 25, max: 10 };
3521        assert!(err.to_string().contains("25"));
3522        assert!(err.to_string().contains("10"));
3523
3524        let err = StreamingValidationError::FpsBelowMinimum {
3525            measured: 20.5,
3526            min: 60.0,
3527        };
3528        assert!(err.to_string().contains("20.5"));
3529        assert!(err.to_string().contains("60.0"));
3530
3531        let err = StreamingValidationError::TtfbExceeded {
3532            measured: Duration::from_secs(10),
3533            max: Duration::from_secs(3),
3534        };
3535        let display = err.to_string();
3536        assert!(display.contains("first byte"));
3537
3538        let err = StreamingValidationError::InvalidStateTransition {
3539            from: StreamingState::Buffering,
3540            to: StreamingState::Completed,
3541        };
3542        let display = err.to_string();
3543        assert!(display.contains("Buffering"));
3544        assert!(display.contains("Completed"));
3545
3546        let err = StreamingValidationError::EndedInError;
3547        assert!(err.to_string().contains("error state"));
3548    }
3549
3550    #[test]
3551    fn test_streaming_state_display_coverage() {
3552        // Test all StreamingState Display implementations
3553        assert_eq!(format!("{}", StreamingState::Idle), "Idle");
3554        assert_eq!(format!("{}", StreamingState::Buffering), "Buffering");
3555        assert_eq!(format!("{}", StreamingState::Streaming), "Streaming");
3556        assert_eq!(format!("{}", StreamingState::Stalled), "Stalled");
3557        assert_eq!(format!("{}", StreamingState::Error), "Error");
3558        assert_eq!(format!("{}", StreamingState::Completed), "Completed");
3559    }
3560
3561    #[test]
3562    fn test_test_execution_stats_zero_raw_bytes() {
3563        let stats = TestExecutionStats::new();
3564        // Zero raw bytes should not panic and return 0 efficiency
3565        assert!((stats.efficiency() - 0.0).abs() < f64::EPSILON);
3566    }
3567
3568    #[test]
3569    fn test_test_execution_stats_zero_compressed_bytes() {
3570        let mut stats = TestExecutionStats::new();
3571        stats.bytes_raw = 1000;
3572        stats.bytes_compressed = 0;
3573        // Zero compressed bytes should return 0 ratio (avoid div by zero)
3574        assert!((stats.compression_ratio() - 0.0).abs() < f64::EPSILON);
3575    }
3576
3577    #[test]
3578    fn test_test_execution_stats_zero_states_captured() {
3579        let stats = TestExecutionStats::new();
3580        // Zero states should return 0 same_fill_ratio
3581        assert!((stats.same_fill_ratio() - 0.0).abs() < f64::EPSILON);
3582    }
3583
3584    #[test]
3585    fn test_test_execution_stats_throughput_no_start() {
3586        let mut stats = TestExecutionStats::new();
3587        stats.stop();
3588        stats.record_state_capture(1000, 100);
3589        // No start time should return 0 throughput
3590        assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3591    }
3592
3593    #[test]
3594    fn test_test_execution_stats_throughput_only_end() {
3595        let mut stats = TestExecutionStats::new();
3596        stats.stop();
3597        // Only end time, no start - should return 0 throughput
3598        assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
3599    }
3600
3601    #[test]
3602    fn test_test_execution_stats_same_fill_with_zero_raw() {
3603        let mut stats = TestExecutionStats::new();
3604        // Edge case: raw_bytes is 0, should not count as same-fill
3605        stats.record_state_capture(0, 0);
3606        assert_eq!(stats.same_fill_pages, 0);
3607    }
3608
3609    #[test]
3610    fn test_screenshot_content_classify_single_pixel() {
3611        // Edge case: single pixel
3612        let content = ScreenshotContent::classify(&[42]);
3613        assert!(matches!(
3614            content,
3615            ScreenshotContent::Uniform { fill_value: 42 }
3616        ));
3617    }
3618
3619    #[test]
3620    fn test_screenshot_content_all_different_values() {
3621        // All 256 different values - maximum entropy
3622        let pixels: Vec<u8> = (0..=255).collect();
3623        let content = ScreenshotContent::classify(&pixels);
3624        assert!(matches!(content, ScreenshotContent::HighEntropy { .. }));
3625        // Should have 8 bits of entropy
3626        assert!(content.entropy() > 7.5);
3627    }
3628
3629    #[test]
3630    fn test_screenshot_content_entropy_method_uniform() {
3631        let content = ScreenshotContent::Uniform { fill_value: 200 };
3632        assert!((content.entropy() - 0.0).abs() < f32::EPSILON);
3633    }
3634
3635    #[test]
3636    fn test_screenshot_content_recommended_algorithm_all() {
3637        assert_eq!(
3638            ScreenshotContent::Uniform { fill_value: 0 }.recommended_algorithm(),
3639            CompressionAlgorithm::Rle
3640        );
3641        assert_eq!(
3642            ScreenshotContent::UiDominated { entropy: 2.0 }.recommended_algorithm(),
3643            CompressionAlgorithm::Png
3644        );
3645        assert_eq!(
3646            ScreenshotContent::GameWorld { entropy: 4.5 }.recommended_algorithm(),
3647            CompressionAlgorithm::Zstd
3648        );
3649        assert_eq!(
3650            ScreenshotContent::HighEntropy { entropy: 7.0 }.recommended_algorithm(),
3651            CompressionAlgorithm::Lz4
3652        );
3653    }
3654
3655    #[test]
3656    fn test_screenshot_content_expected_ratio_hint_all() {
3657        assert!(ScreenshotContent::Uniform { fill_value: 0 }
3658            .expected_ratio_hint()
3659            .contains("excellent"));
3660        assert!(ScreenshotContent::UiDominated { entropy: 2.0 }
3661            .expected_ratio_hint()
3662            .contains("good"));
3663        assert!(ScreenshotContent::GameWorld { entropy: 4.5 }
3664            .expected_ratio_hint()
3665            .contains("moderate"));
3666        assert!(ScreenshotContent::HighEntropy { entropy: 7.0 }
3667            .expected_ratio_hint()
3668            .contains("poor"));
3669    }
3670
3671    #[test]
3672    fn test_streaming_ux_validator_builder_chain() {
3673        let validator = StreamingUxValidator::new()
3674            .with_max_latency(Duration::from_millis(150))
3675            .with_buffer_underrun_threshold(10)
3676            .with_max_dropped_frames(20)
3677            .with_min_fps(45.0)
3678            .with_ttfb_timeout(Duration::from_secs(5));
3679
3680        assert_eq!(validator.max_latency, Duration::from_millis(150));
3681        assert_eq!(validator.buffer_underrun_threshold, 10);
3682        assert_eq!(validator.max_dropped_frames, 20);
3683        assert!((validator.min_fps - 45.0).abs() < f64::EPSILON);
3684        assert_eq!(validator.ttfb_timeout, Duration::from_secs(5));
3685    }
3686
3687    #[test]
3688    fn test_streaming_validator_for_audio_preset() {
3689        let validator = StreamingUxValidator::for_audio();
3690        assert_eq!(validator.max_latency, Duration::from_millis(100));
3691        assert_eq!(validator.buffer_underrun_threshold, 3);
3692        assert_eq!(validator.ttfb_timeout, Duration::from_secs(2));
3693    }
3694
3695    #[test]
3696    fn test_streaming_validator_for_video_preset() {
3697        let validator = StreamingUxValidator::for_video();
3698        assert_eq!(validator.max_latency, Duration::from_millis(500));
3699        assert!((validator.min_fps - 30.0).abs() < f64::EPSILON);
3700        assert_eq!(validator.max_dropped_frames, 5);
3701    }
3702
3703    #[test]
3704    fn test_streaming_validator_average_fps_no_frames() {
3705        let validator = StreamingUxValidator::new();
3706        assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3707    }
3708
3709    #[test]
3710    fn test_streaming_validator_average_fps_one_frame() {
3711        let mut validator = StreamingUxValidator::new();
3712        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3713        assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3714    }
3715
3716    #[test]
3717    fn test_streaming_validator_average_fps_same_timestamp() {
3718        let mut validator = StreamingUxValidator::new();
3719        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3720        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3721        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 1000 });
3722        // Zero duration should return 0 fps
3723        assert!((validator.average_fps() - 0.0).abs() < f64::EPSILON);
3724    }
3725
3726    #[test]
3727    fn test_streaming_validator_max_recorded_latency_none() {
3728        let validator = StreamingUxValidator::new();
3729        let result = validator.validate();
3730        assert!(result.is_ok());
3731        let res = result.unwrap();
3732        assert_eq!(res.max_latency_recorded, Duration::ZERO);
3733    }
3734
3735    #[test]
3736    fn test_streaming_validator_validate_no_ttfb() {
3737        let mut validator = StreamingUxValidator::new();
3738        validator.start();
3739        // No first byte received, but should still validate
3740        let result = validator.validate();
3741        assert!(result.is_ok());
3742    }
3743
3744    #[test]
3745    fn test_streaming_validator_complete_and_validate() {
3746        let mut validator = StreamingUxValidator::new();
3747        validator.complete();
3748        assert_eq!(validator.state(), StreamingState::Completed);
3749        let result = validator.validate();
3750        assert!(result.is_ok());
3751    }
3752
3753    #[test]
3754    fn test_streaming_validator_error_and_validate() {
3755        let mut validator = StreamingUxValidator::new();
3756        validator.error();
3757        assert_eq!(validator.state(), StreamingState::Error);
3758        let result = validator.validate();
3759        assert!(result.is_err());
3760    }
3761
3762    #[test]
3763    fn test_streaming_validator_state_history_empty() {
3764        let validator = StreamingUxValidator::new();
3765        assert!(validator.state_history().is_empty());
3766    }
3767
3768    #[test]
3769    fn test_streaming_validator_state_history_with_transitions() {
3770        let mut validator = StreamingUxValidator::new();
3771        validator.start();
3772        validator.record_metric(StreamingMetric::AudioChunk {
3773            samples: 1024,
3774            sample_rate: 16000,
3775        });
3776        validator.complete();
3777
3778        let history = validator.state_history();
3779        assert!(history.len() >= 2);
3780    }
3781
3782    #[test]
3783    fn test_streaming_validator_reset_full() {
3784        let mut validator = StreamingUxValidator::new();
3785        validator.start();
3786        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
3787        validator.record_metric(StreamingMetric::BufferUnderrun);
3788        validator.record_metric(StreamingMetric::FrameDropped);
3789        validator.record_metric(StreamingMetric::FirstByteReceived);
3790        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
3791
3792        validator.reset();
3793
3794        assert_eq!(validator.state(), StreamingState::Idle);
3795        assert_eq!(validator.buffer_underruns(), 0);
3796        assert_eq!(validator.dropped_frames(), 0);
3797        assert!(validator.state_history().is_empty());
3798    }
3799
3800    #[test]
3801    fn test_streaming_validator_validate_all_with_error_state() {
3802        let mut validator = StreamingUxValidator::new();
3803        validator.error();
3804        let errors = validator.validate_all();
3805        assert!(errors
3806            .iter()
3807            .any(|e| matches!(e, StreamingValidationError::EndedInError)));
3808    }
3809
3810    #[test]
3811    fn test_streaming_validation_result_fields_all() {
3812        let result = StreamingValidationResult {
3813            buffer_underruns: 1,
3814            dropped_frames: 2,
3815            average_fps: 30.0,
3816            max_latency_recorded: Duration::from_millis(50),
3817            total_frames: 100,
3818        };
3819        assert_eq!(result.buffer_underruns, 1);
3820        assert_eq!(result.dropped_frames, 2);
3821        assert!((result.average_fps - 30.0).abs() < f64::EPSILON);
3822        assert_eq!(result.max_latency_recorded, Duration::from_millis(50));
3823        assert_eq!(result.total_frames, 100);
3824    }
3825
3826    #[test]
3827    fn test_state_transition_struct_fields_all() {
3828        let transition = StateTransition {
3829            from: "StateA".to_string(),
3830            to: "StateB".to_string(),
3831            timestamp_ms: 100.0,
3832            duration_ms: 50.0,
3833        };
3834        assert_eq!(&transition.from, "StateA");
3835        assert_eq!(&transition.to, "StateB");
3836        assert!((transition.timestamp_ms - 100.0).abs() < f64::EPSILON);
3837        assert!((transition.duration_ms - 50.0).abs() < f64::EPSILON);
3838    }
3839
3840    #[test]
3841    fn test_partial_result_struct_fields() {
3842        let partial = PartialResult {
3843            timestamp_ms: 200.0,
3844            text: "partial text".to_string(),
3845            is_final: false,
3846        };
3847        assert!((partial.timestamp_ms - 200.0).abs() < f64::EPSILON);
3848        assert_eq!(&partial.text, "partial text");
3849        assert!(!partial.is_final);
3850
3851        let final_result = PartialResult {
3852            timestamp_ms: 300.0,
3853            text: "final text".to_string(),
3854            is_final: true,
3855        };
3856        assert!(final_result.is_final);
3857    }
3858
3859    #[test]
3860    fn test_vu_meter_sample_struct_fields() {
3861        let sample = VuMeterSample {
3862            timestamp_ms: 150.0,
3863            level: 0.65,
3864        };
3865        assert!((sample.timestamp_ms - 150.0).abs() < f64::EPSILON);
3866        assert!((sample.level - 0.65).abs() < f32::EPSILON);
3867    }
3868
3869    #[test]
3870    fn test_streaming_metric_record_struct() {
3871        let now = Instant::now();
3872        let record = StreamingMetricRecord {
3873            metric: StreamingMetric::FrameDropped,
3874            timestamp: now,
3875        };
3876        assert!(matches!(record.metric, StreamingMetric::FrameDropped));
3877        assert_eq!(record.timestamp, now);
3878    }
3879
3880    #[test]
3881    fn test_streaming_metric_latency_variant() {
3882        let metric = StreamingMetric::Latency(Duration::from_millis(123));
3883        if let StreamingMetric::Latency(d) = metric {
3884            assert_eq!(d, Duration::from_millis(123));
3885        } else {
3886            panic!("Expected Latency variant");
3887        }
3888    }
3889
3890    #[test]
3891    fn test_streaming_metric_frame_rendered_variant() {
3892        let metric = StreamingMetric::FrameRendered { timestamp: 999 };
3893        if let StreamingMetric::FrameRendered { timestamp } = metric {
3894            assert_eq!(timestamp, 999);
3895        } else {
3896            panic!("Expected FrameRendered variant");
3897        }
3898    }
3899
3900    #[test]
3901    fn test_streaming_metric_buffer_level_variant() {
3902        let metric = StreamingMetric::BufferLevel(0.42);
3903        if let StreamingMetric::BufferLevel(level) = metric {
3904            assert!((level - 0.42).abs() < f32::EPSILON);
3905        } else {
3906            panic!("Expected BufferLevel variant");
3907        }
3908    }
3909
3910    #[test]
3911    fn test_streaming_metric_audio_chunk_variant() {
3912        let metric = StreamingMetric::AudioChunk {
3913            samples: 2048,
3914            sample_rate: 44100,
3915        };
3916        if let StreamingMetric::AudioChunk {
3917            samples,
3918            sample_rate,
3919        } = metric
3920        {
3921            assert_eq!(samples, 2048);
3922            assert_eq!(sample_rate, 44100);
3923        } else {
3924            panic!("Expected AudioChunk variant");
3925        }
3926    }
3927
3928    #[test]
3929    fn test_compression_algorithm_eq() {
3930        assert_eq!(CompressionAlgorithm::Lz4, CompressionAlgorithm::Lz4);
3931        assert_eq!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Zstd);
3932        assert_eq!(CompressionAlgorithm::Png, CompressionAlgorithm::Png);
3933        assert_eq!(CompressionAlgorithm::Rle, CompressionAlgorithm::Rle);
3934    }
3935
3936    #[test]
3937    fn test_streaming_state_eq() {
3938        assert_eq!(StreamingState::Idle, StreamingState::Idle);
3939        assert_eq!(StreamingState::Buffering, StreamingState::Buffering);
3940        assert_eq!(StreamingState::Streaming, StreamingState::Streaming);
3941        assert_eq!(StreamingState::Stalled, StreamingState::Stalled);
3942        assert_eq!(StreamingState::Error, StreamingState::Error);
3943        assert_eq!(StreamingState::Completed, StreamingState::Completed);
3944    }
3945
3946    #[test]
3947    fn test_streaming_state_ne() {
3948        assert_ne!(StreamingState::Idle, StreamingState::Buffering);
3949        assert_ne!(StreamingState::Streaming, StreamingState::Stalled);
3950        assert_ne!(StreamingState::Error, StreamingState::Completed);
3951    }
3952
3953    #[test]
3954    fn test_vu_meter_error_debug() {
3955        let errors = vec![
3956            VuMeterError::NegativeLevel(-1.0),
3957            VuMeterError::Clipping(2.0),
3958            VuMeterError::Stale {
3959                last_update_ms: 100,
3960                current_ms: 200,
3961            },
3962            VuMeterError::SlowUpdateRate {
3963                measured_hz: 10.0,
3964                expected_hz: 30.0,
3965            },
3966            VuMeterError::NotAnimating {
3967                sample_count: 5,
3968                value: 0.5,
3969            },
3970        ];
3971
3972        for err in errors {
3973            let debug = format!("{:?}", err);
3974            assert!(!debug.is_empty());
3975        }
3976    }
3977
3978    #[test]
3979    fn test_streaming_validation_error_debug() {
3980        let errors: Vec<StreamingValidationError> = vec![
3981            StreamingValidationError::LatencyExceeded {
3982                measured: Duration::from_millis(100),
3983                max: Duration::from_millis(50),
3984            },
3985            StreamingValidationError::BufferUnderrunThreshold {
3986                count: 5,
3987                threshold: 3,
3988            },
3989            StreamingValidationError::DroppedFrameThreshold { count: 10, max: 5 },
3990            StreamingValidationError::FpsBelowMinimum {
3991                measured: 15.0,
3992                min: 30.0,
3993            },
3994            StreamingValidationError::TtfbExceeded {
3995                measured: Duration::from_secs(5),
3996                max: Duration::from_secs(2),
3997            },
3998            StreamingValidationError::InvalidStateTransition {
3999                from: StreamingState::Idle,
4000                to: StreamingState::Error,
4001            },
4002            StreamingValidationError::EndedInError,
4003        ];
4004
4005        for err in errors {
4006            let debug = format!("{:?}", err);
4007            assert!(!debug.is_empty());
4008        }
4009    }
4010
4011    #[test]
4012    fn test_screenshot_content_classify_boundary_uniform() {
4013        // Exactly 95% same value should still be Uniform (> 0.95)
4014        let mut pixels = vec![100u8; 96];
4015        pixels.extend(vec![200u8; 4]);
4016        let content = ScreenshotContent::classify(&pixels);
4017        assert!(matches!(
4018            content,
4019            ScreenshotContent::Uniform { fill_value: 100 }
4020        ));
4021    }
4022
4023    #[test]
4024    fn test_screenshot_content_classify_just_under_uniform() {
4025        // 94% same value should NOT be uniform
4026        let mut pixels = vec![100u8; 94];
4027        pixels.extend(vec![200u8; 6]);
4028        let content = ScreenshotContent::classify(&pixels);
4029        // Should not be Uniform
4030        assert!(!matches!(content, ScreenshotContent::Uniform { .. }));
4031    }
4032
4033    #[test]
4034    fn test_test_execution_stats_reset_clears_timing() {
4035        let mut stats = TestExecutionStats::new();
4036        stats.start();
4037        stats.record_state_capture(1000, 100);
4038        stats.stop();
4039
4040        // Verify we have throughput
4041        assert!(stats.compress_throughput() > 0.0 || stats.bytes_raw > 0);
4042
4043        stats.reset();
4044
4045        // After reset, throughput should be 0 (no timing data)
4046        assert!((stats.compress_throughput() - 0.0).abs() < f64::EPSILON);
4047        assert_eq!(stats.states_captured, 0);
4048        assert_eq!(stats.bytes_raw, 0);
4049        assert_eq!(stats.bytes_compressed, 0);
4050        assert_eq!(stats.same_fill_pages, 0);
4051    }
4052
4053    #[test]
4054    fn test_frame_times_cap_at_120() {
4055        let mut validator = StreamingUxValidator::new();
4056
4057        // Add 200 frames
4058        for i in 0..200 {
4059            validator.record_metric(StreamingMetric::FrameRendered { timestamp: i * 16 });
4060        }
4061
4062        // Frame times should be capped (checked via FPS calculation working)
4063        let fps = validator.average_fps();
4064        assert!(fps > 0.0);
4065    }
4066
4067    #[test]
4068    fn test_latency_metric_triggers_buffering_to_streaming() {
4069        let mut validator =
4070            StreamingUxValidator::new().with_max_latency(Duration::from_millis(200));
4071        validator.start();
4072        assert_eq!(validator.state(), StreamingState::Buffering);
4073
4074        // Good latency should transition to Streaming
4075        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(50)));
4076        assert_eq!(validator.state(), StreamingState::Streaming);
4077    }
4078
4079    #[test]
4080    fn test_latency_metric_high_latency_no_transition() {
4081        let mut validator = StreamingUxValidator::new().with_max_latency(Duration::from_millis(50));
4082        validator.start();
4083        assert_eq!(validator.state(), StreamingState::Buffering);
4084
4085        // High latency should NOT transition to Streaming
4086        validator.record_metric(StreamingMetric::Latency(Duration::from_millis(100)));
4087        assert_eq!(validator.state(), StreamingState::Buffering);
4088    }
4089
4090    #[test]
4091    fn test_buffer_level_stall_only_when_streaming() {
4092        let mut validator = StreamingUxValidator::new();
4093        // In Idle state
4094        validator.record_metric(StreamingMetric::BufferLevel(0.01));
4095        assert_eq!(validator.state(), StreamingState::Idle);
4096
4097        // In Buffering state
4098        validator.start();
4099        validator.record_metric(StreamingMetric::BufferLevel(0.01));
4100        assert_eq!(validator.state(), StreamingState::Buffering);
4101    }
4102
4103    #[test]
4104    fn test_buffer_level_recovery_only_when_stalled() {
4105        let mut validator = StreamingUxValidator::new();
4106        validator.start();
4107        validator.record_metric(StreamingMetric::AudioChunk {
4108            samples: 1024,
4109            sample_rate: 16000,
4110        });
4111        assert_eq!(validator.state(), StreamingState::Streaming);
4112
4113        // High buffer level should NOT change state when already Streaming
4114        validator.record_metric(StreamingMetric::BufferLevel(0.9));
4115        assert_eq!(validator.state(), StreamingState::Streaming);
4116    }
4117
4118    #[test]
4119    fn test_frame_rendered_recovery_from_stalled() {
4120        let mut validator = StreamingUxValidator::new();
4121        validator.start();
4122        validator.record_metric(StreamingMetric::AudioChunk {
4123            samples: 1024,
4124            sample_rate: 16000,
4125        });
4126        validator.record_metric(StreamingMetric::BufferLevel(0.01)); // Stall
4127
4128        assert_eq!(validator.state(), StreamingState::Stalled);
4129
4130        // Frame rendered should recover
4131        validator.record_metric(StreamingMetric::FrameRendered { timestamp: 100 });
4132        assert_eq!(validator.state(), StreamingState::Streaming);
4133    }
4134
4135    #[test]
4136    fn test_audio_chunk_only_transitions_from_buffering() {
4137        let mut validator = StreamingUxValidator::new();
4138
4139        // In Idle - should not transition
4140        validator.record_metric(StreamingMetric::AudioChunk {
4141            samples: 1024,
4142            sample_rate: 16000,
4143        });
4144        assert_eq!(validator.state(), StreamingState::Idle);
4145    }
4146
4147    #[test]
4148    fn test_first_byte_received_only_transitions_from_idle() {
4149        let mut validator = StreamingUxValidator::new();
4150        validator.start(); // Now in Buffering
4151
4152        // FirstByte when already buffering should not re-transition
4153        validator.record_metric(StreamingMetric::FirstByteReceived);
4154        assert_eq!(validator.state(), StreamingState::Buffering);
4155    }
4156
4157    #[test]
4158    fn test_buffer_underrun_only_stalls_when_streaming() {
4159        let mut validator = StreamingUxValidator::new();
4160
4161        // In Idle - underrun should not change state
4162        validator.record_metric(StreamingMetric::BufferUnderrun);
4163        assert_eq!(validator.state(), StreamingState::Idle);
4164
4165        // In Buffering - underrun should not change state
4166        validator.start();
4167        validator.record_metric(StreamingMetric::BufferUnderrun);
4168        assert_eq!(validator.state(), StreamingState::Buffering);
4169    }
4170
4171    #[test]
4172    fn test_validate_all_fps_error() {
4173        let mut validator = StreamingUxValidator::new().with_min_fps(60.0);
4174
4175        // Add slow frames
4176        for i in 0..10 {
4177            validator.record_metric(StreamingMetric::FrameRendered {
4178                timestamp: i * 100, // 10 fps
4179            });
4180        }
4181
4182        let errors = validator.validate_all();
4183        assert!(errors
4184            .iter()
4185            .any(|e| matches!(e, StreamingValidationError::FpsBelowMinimum { .. })));
4186    }
4187
4188    #[test]
4189    fn test_validate_all_buffer_underrun_error() {
4190        let mut validator = StreamingUxValidator::new().with_buffer_underrun_threshold(1);
4191
4192        validator.record_metric(StreamingMetric::BufferUnderrun);
4193        validator.record_metric(StreamingMetric::BufferUnderrun);
4194
4195        let errors = validator.validate_all();
4196        assert!(errors
4197            .iter()
4198            .any(|e| matches!(e, StreamingValidationError::BufferUnderrunThreshold { .. })));
4199    }
4200
4201    #[test]
4202    fn test_validate_all_dropped_frames_error() {
4203        let mut validator = StreamingUxValidator::new().with_max_dropped_frames(1);
4204
4205        validator.record_metric(StreamingMetric::FrameDropped);
4206        validator.record_metric(StreamingMetric::FrameDropped);
4207
4208        let errors = validator.validate_all();
4209        assert!(errors
4210            .iter()
4211            .any(|e| matches!(e, StreamingValidationError::DroppedFrameThreshold { .. })));
4212    }
4213
4214    #[test]
4215    fn test_streaming_state_copy_clone() {
4216        let state = StreamingState::Streaming;
4217        let copied = state;
4218        let cloned = state;
4219        assert_eq!(copied, cloned);
4220        assert_eq!(state, StreamingState::Streaming);
4221    }
4222
4223    #[test]
4224    fn test_compression_algorithm_copy_clone() {
4225        let algo = CompressionAlgorithm::Zstd;
4226        let copied = algo;
4227        let cloned = algo;
4228        assert_eq!(copied, cloned);
4229        assert_eq!(algo, CompressionAlgorithm::Zstd);
4230    }
4231}