From 9d278ded5a68803c9b0b5cf3d06b1317ae161ecc Mon Sep 17 00:00:00 2001 From: Mollusk Date: Tue, 2 Jun 2026 16:33:55 -0400 Subject: [PATCH] test(jitter): state-transition edge cases for JitterBuffer Covers the remaining state-transition edges: re-prime after an underrun goes idle (must re-accumulate TARGET_DELAY_FRAMES, not resume on one packet), duplicate-insert overwrite (no buffer growth), is_idle across fresh/buffering/ underrun, and overflow-resync when next_seq is already Some (playout head snaps to the new front). Jitter tests 6 -> 10; test-only, no prod change. Implemented by Gemini per next-task.md; left uncommitted per the operating- agreement default, reviewed against the real diff and re-verified (build + clippy --all-targets + test all green) by the senior. Co-Authored-By: Claude Opus 4.8 --- src/core/jitter.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/core/jitter.rs b/src/core/jitter.rs index 2e3672e..95817f3 100644 --- a/src/core/jitter.rs +++ b/src/core/jitter.rs @@ -222,5 +222,94 @@ mod tests { assert!(!jb.packets.contains_key(&0)); assert_eq!(jb.next_seq, Some(1)); } + + #[test] + fn reprimes_after_underrun_idle() { + let mut enc = OpusEncoder::new(48000, Channels::Mono, Application::Voip).unwrap(); + let mut jb = JitterBuffer::new().unwrap(); + + // Prime with seq 0, 1, 2 + jb.insert(0, frame(&mut enc, 1000)); + jb.insert(1, frame(&mut enc, 1000)); + jb.insert(2, frame(&mut enc, 1000)); + + // pop_frame() 3x -> 3 Some + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_some()); + + // A 4th pop_frame() -> None, and jb.is_idle() is true + assert!(jb.pop_frame().is_none()); + assert!(jb.is_idle()); + + // Insert ONE new frame (seq 3): pop_frame() must still be None (must re-accumulate TARGET_DELAY_FRAMES) + jb.insert(3, frame(&mut enc, 1000)); + assert!(jb.pop_frame().is_none()); + assert!(!jb.is_idle()); + + // Insert seq 4 and 5 (now 3 buffered) -> pop_frame() yields Some (re-primed) + jb.insert(4, frame(&mut enc, 1000)); + jb.insert(5, frame(&mut enc, 1000)); + assert!(jb.pop_frame().is_some()); + } + + #[test] + fn duplicate_insert_does_not_grow_buffer() { + let mut jb = JitterBuffer::new().unwrap(); + jb.insert(0, vec![0u8]); + jb.insert(0, vec![1u8]); + assert_eq!(jb.packets.len(), 1); + } + + #[test] + fn is_idle_reflects_buffer_state() { + let mut enc = OpusEncoder::new(48000, Channels::Mono, Application::Voip).unwrap(); + let mut jb = JitterBuffer::new().unwrap(); + + // Fresh buffer + assert!(jb.is_idle()); + + // After a single insert + jb.insert(0, frame(&mut enc, 1000)); + assert!(!jb.is_idle()); + + // Prime (3 frames) + jb.insert(1, frame(&mut enc, 1000)); + jb.insert(2, frame(&mut enc, 1000)); + + // Drain past the end so it underruns + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_none()); + + assert!(jb.is_idle()); + } + + #[test] + fn overflow_resync_while_playing() { + let mut enc = OpusEncoder::new(48000, Channels::Mono, Application::Voip).unwrap(); + let mut jb = JitterBuffer::new().unwrap(); + + // Prime with seq 0, 1, 2 + jb.insert(0, frame(&mut enc, 1000)); + jb.insert(1, frame(&mut enc, 1000)); + jb.insert(2, frame(&mut enc, 1000)); + + // pop_frame() twice (now next_seq == Some(2)) + assert!(jb.pop_frame().is_some()); + assert!(jb.pop_frame().is_some()); + assert_eq!(jb.next_seq, Some(2)); + + // Insert a contiguous run of higher sequences to exceed MAX_BUFFERED_FRAMES + let start = 3; + let end = 3 + MAX_BUFFERED_FRAMES + 2; + for seq in start..end { + jb.insert(seq as u32, frame(&mut enc, 1000)); + } + + assert_eq!(jb.packets.len(), MAX_BUFFERED_FRAMES); + assert_eq!(jb.next_seq, jb.packets.keys().next().copied()); + } }