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