From d117576449b646a86c073fa0ba4f9fab0e60daf8 Mon Sep 17 00:00:00 2001 From: Username Date: Tue, 24 Feb 2026 16:38:03 +0100 Subject: [PATCH] test: refactor audio tests to use public interfaces Replace direct queue/attribute access with capture_callback and get_capture_frame. Add stop-drains-queues test. --- tests/test_audio.py | 61 +++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/tests/test_audio.py b/tests/test_audio.py index 433340a..02ba478 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -7,21 +7,17 @@ from tuimble.audio import FRAME_SIZE, SAMPLE_RATE, AudioPipeline, _apply_gain def test_default_construction(): ap = AudioPipeline() - assert ap._sample_rate == SAMPLE_RATE - assert ap._frame_size == FRAME_SIZE - assert ap._input_device is None - assert ap._output_device is None assert ap.capturing is False assert ap.deafened is False + assert ap.input_gain == 1.0 + assert ap.output_gain == 1.0 def test_custom_construction(): ap = AudioPipeline(sample_rate=24000, frame_size=480, input_device=1, output_device=2) - assert ap._sample_rate == 24000 - assert ap._frame_size == 480 - assert ap._input_device == 1 - assert ap._output_device == 2 + # Verify via public behavior: get_capture_frame returns None + assert ap.get_capture_frame() is None def test_capturing_toggle(): @@ -38,10 +34,13 @@ def test_get_capture_frame_empty(): assert ap.get_capture_frame() is None -def test_get_capture_frame_returns_queued(): +def test_capture_and_retrieve(): + """Capture callback queues frames; get_capture_frame retrieves them.""" ap = AudioPipeline() - ap._capture_queue.put(b"\x01\x02\x03") - assert ap.get_capture_frame() == b"\x01\x02\x03" + ap.capturing = True + pcm = b"\x01\x02\x03\x04" + ap._capture_callback(pcm, 2, None, None) + assert ap.get_capture_frame() == pcm assert ap.get_capture_frame() is None @@ -84,12 +83,20 @@ def test_playback_callback_short_pcm_pads_silence(): def test_queue_playback_overflow_drops(): """Full queue drops new data silently.""" ap = AudioPipeline(frame_size=FRAME_SIZE) - # Fill the queue - for i in range(ap._playback_queue.maxsize): - ap.queue_playback(b"\x00") - # This should not raise - ap.queue_playback(b"\xff") - assert ap._playback_queue.qsize() == ap._playback_queue.maxsize + # Use non-zero PCM so we can distinguish from silence + frame = b"\x42\x42" + for _ in range(50): # maxsize=50 + ap.queue_playback(frame) + # This should not raise (dropped because queue is full) + ap.queue_playback(b"\xff\xff") + # Drain and count -- frames with our marker byte + count = 0 + for _ in range(60): # more than queue size + outdata = bytearray(2) + ap._playback_callback(outdata, 1, None, None) + if outdata != bytearray(2): + count += 1 + assert count == 50 def test_deafened_toggle(): @@ -106,14 +113,16 @@ def test_queue_playback_discards_when_deafened(): ap = AudioPipeline() ap.deafened = True ap.queue_playback(b"\x42" * 100) - assert ap._playback_queue.qsize() == 0 + # Nothing to play back + outdata = bytearray(200) + ap._playback_callback(outdata, 100, None, None) + assert outdata == bytearray(200) # silence def test_playback_callback_silence_when_deafened(): """Playback callback writes silence when deafened, even with queued data.""" ap = AudioPipeline() frame_bytes = FRAME_SIZE * 2 - # Queue data before deafening pcm = b"\x42" * frame_bytes ap.queue_playback(pcm) ap.deafened = True @@ -129,6 +138,20 @@ def test_stop_without_start(): ap.stop() +def test_stop_drains_queues(): + """Queues are empty after stop().""" + ap = AudioPipeline() + ap.capturing = True + ap._capture_callback(b"\x00\x00", 1, None, None) + ap.queue_playback(b"\x00\x00") + ap.stop() + assert ap.get_capture_frame() is None + # Playback queue also drained -- callback produces silence + outdata = bytearray(2) + ap._playback_callback(outdata, 1, None, None) + assert outdata == bytearray(2) + + # -- _apply_gain tests -------------------------------------------------------