2024-03-17 15:15:41 -07:00
|
|
|
import io
|
|
|
|
import threading
|
2024-03-26 00:51:16 -07:00
|
|
|
from pathlib import Path
|
2025-08-28 23:07:04 -07:00
|
|
|
from time import sleep
|
2024-03-26 00:51:16 -07:00
|
|
|
from unittest.mock import MagicMock
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
import shout
|
|
|
|
|
2024-03-26 00:51:16 -07:00
|
|
|
from croaker import playlist, streamer
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2024-03-26 00:51:16 -07:00
|
|
|
@pytest.fixture(scope="session")
|
2024-03-17 15:15:41 -07:00
|
|
|
def silence_bytes():
|
2025-08-28 23:07:04 -07:00
|
|
|
# return (Path(streamer.__file__).parent / "silence.mp3").read_bytes()
|
|
|
|
return (Path(__file__).parent / "fixtures" / "transcoded_silence.mp3").read_bytes()
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def output_stream():
|
|
|
|
return io.BytesIO()
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def mock_shout(output_stream, monkeypatch):
|
|
|
|
def handle_send(buf):
|
2025-08-28 23:07:04 -07:00
|
|
|
print(f"buffering {len(buf)} bytes to output_stream.")
|
2024-03-17 15:15:41 -07:00
|
|
|
output_stream.write(buf)
|
2024-03-26 00:51:16 -07:00
|
|
|
|
|
|
|
mm = MagicMock(spec=shout.Shout, **{"return_value.send.side_effect": handle_send})
|
|
|
|
monkeypatch.setattr("shout.Shout", mm)
|
2024-03-17 15:15:41 -07:00
|
|
|
return mm
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2025-08-28 23:07:04 -07:00
|
|
|
def audio_streamer(monkeypatch, mock_shout):
|
|
|
|
return streamer.AudioStreamer()
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
2025-08-28 23:07:04 -07:00
|
|
|
def thread(audio_streamer):
|
|
|
|
thread = threading.Thread(target=audio_streamer.run)
|
|
|
|
thread.daemon = True
|
|
|
|
yield thread
|
|
|
|
audio_streamer.shutdown_requested.set()
|
|
|
|
thread.join()
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def wait_for(condition, timeout=2.0):
|
|
|
|
elapsed = 0.0
|
|
|
|
while not condition() and elapsed < 2.0:
|
|
|
|
elapsed += 0.01
|
|
|
|
sleep(0.01)
|
|
|
|
return elapsed <= timeout
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def wait_for_not(condition, timeout=2.0):
|
|
|
|
return wait_for(lambda: not condition(), timeout=timeout)
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def test_streamer_clear(audio_streamer, thread):
|
|
|
|
# enqueue some tracks
|
2024-03-26 00:51:16 -07:00
|
|
|
pl = playlist.Playlist(name="test_playlist")
|
2024-03-17 15:15:41 -07:00
|
|
|
for track in pl.tracks:
|
2025-08-28 23:07:04 -07:00
|
|
|
audio_streamer.queue.put(bytes(track))
|
|
|
|
assert not audio_streamer.queue.empty()
|
2024-03-17 15:15:41 -07:00
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
# start the server and send it a clear request
|
|
|
|
thread.start()
|
|
|
|
audio_streamer.clear_requested.set()
|
|
|
|
assert wait_for(audio_streamer.queue.empty)
|
|
|
|
assert wait_for_not(audio_streamer.clear_requested.is_set)
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def test_streamer_shutdown(audio_streamer, thread):
|
|
|
|
thread.start()
|
|
|
|
audio_streamer.shutdown_requested.set()
|
|
|
|
assert wait_for_not(audio_streamer.shutdown_requested.is_set)
|
2024-03-17 15:15:41 -07:00
|
|
|
|
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def test_streamer_skip(audio_streamer, thread):
|
|
|
|
thread.start()
|
|
|
|
audio_streamer.skip_requested.set()
|
|
|
|
assert wait_for_not(audio_streamer.skip_requested.is_set)
|
2024-03-26 00:51:16 -07:00
|
|
|
|
2024-03-17 15:15:41 -07:00
|
|
|
|
2025-08-28 23:07:04 -07:00
|
|
|
def test_streamer_defaults_to_silence(audio_streamer, thread, output_stream, silence_bytes):
|
|
|
|
thread.start()
|
|
|
|
thread.join(timeout=1)
|
|
|
|
output_stream.seek(0, 0)
|
|
|
|
out = output_stream.read()
|
|
|
|
assert silence_bytes in out
|