test: add test suite for player, api and widgets
This commit is contained in:
1 parent
237de2efe9
commit
8eb22ea52c
11 files changed
+1069
No files matched your search
@@ -0,0 +1,168 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import queue
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from somafm_tui.player import Player
|
||||
|
||||
FAKE_MPV = '''#!/usr/bin/env python3
|
||||
import json, os, socket, sys, time
|
||||
|
||||
sock_path = None
|
||||
volume = 70
|
||||
for arg in sys.argv[1:]:
|
||||
if arg.startswith("--input-ipc-server="):
|
||||
sock_path = arg.split("=", 1)[1]
|
||||
elif arg.startswith("--volume="):
|
||||
volume = int(arg.split("=", 1)[1])
|
||||
|
||||
log_path = os.environ.get("FAKE_MPV_LOG")
|
||||
def log(line):
|
||||
if log_path:
|
||||
with open(log_path, "a") as handle:
|
||||
handle.write(line + "\\n")
|
||||
|
||||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
os.unlink(sock_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
server.bind(sock_path)
|
||||
server.listen(1)
|
||||
conn, _ = server.accept()
|
||||
stream = conn.makefile("rwb", buffering=0)
|
||||
|
||||
def send(payload):
|
||||
stream.write((json.dumps(payload) + "\\n").encode())
|
||||
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
break
|
||||
try:
|
||||
message = json.loads(line)
|
||||
except ValueError:
|
||||
continue
|
||||
command = message.get("command") or []
|
||||
if not command:
|
||||
continue
|
||||
name = command[0]
|
||||
if name == "observe_property":
|
||||
prop = command[2]
|
||||
if prop == "volume":
|
||||
send({"event": "property-change", "id": command[1], "name": "volume", "data": volume})
|
||||
elif prop == "pause":
|
||||
send({"event": "property-change", "id": command[1], "name": "pause", "data": False})
|
||||
elif name == "loadfile":
|
||||
url = command[1]
|
||||
log(url)
|
||||
send({"event": "start-file"})
|
||||
if "bad" in url:
|
||||
time.sleep(0.05)
|
||||
send({"event": "end-file", "reason": "error"})
|
||||
else:
|
||||
time.sleep(0.05)
|
||||
send({"event": "file-loaded"})
|
||||
send({"event": "property-change", "id": 1, "name": "pause", "data": False})
|
||||
elif name == "set_property":
|
||||
prop, value = command[1], command[2]
|
||||
if prop == "pause":
|
||||
send({"event": "property-change", "id": 1, "name": "pause", "data": bool(value)})
|
||||
elif prop == "volume":
|
||||
volume = int(value)
|
||||
send({"event": "property-change", "id": 2, "name": "volume", "data": volume})
|
||||
elif name == "stop":
|
||||
send({"event": "end-file", "reason": "stop"})
|
||||
elif name == "quit":
|
||||
break
|
||||
|
||||
try:
|
||||
stream.close()
|
||||
except Exception:
|
||||
pass
|
||||
conn.close()
|
||||
server.close()
|
||||
'''
|
||||
|
||||
|
||||
def wait_for(player: Player, predicate, timeout: float = 8.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
seen = []
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
event = player.events.get(timeout=0.1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
seen.append(event)
|
||||
if predicate(event):
|
||||
return event, seen
|
||||
raise AssertionError(f"timed out; last events: {seen[-8:]}")
|
||||
|
||||
|
||||
def install_fake_mpv(tmp_path: Path, monkeypatch, log_path: Path) -> None:
|
||||
script = tmp_path / "mpv"
|
||||
script.write_text(FAKE_MPV)
|
||||
script.chmod(0o755)
|
||||
monkeypatch.setenv("PATH", f"{tmp_path}{os.pathsep}{os.environ.get('PATH', '')}")
|
||||
monkeypatch.setenv("FAKE_MPV_LOG", str(log_path))
|
||||
|
||||
|
||||
def test_mpv_ipc_play_pause_volume_retry_stop(tmp_path, monkeypatch) -> None:
|
||||
log_path = tmp_path / "mpv.log"
|
||||
install_fake_mpv(tmp_path, monkeypatch, log_path)
|
||||
|
||||
player = Player()
|
||||
player.start()
|
||||
assert player.backend == "mpv"
|
||||
player.set_volume(55)
|
||||
player.play(
|
||||
[
|
||||
"https://example.invalid/bad-stream",
|
||||
"https://example.invalid/good-stream",
|
||||
]
|
||||
)
|
||||
try:
|
||||
event, _ = wait_for(
|
||||
player,
|
||||
lambda ev: ev.kind == "state"
|
||||
and ev.state == "playing"
|
||||
and "good-stream" in ev.url,
|
||||
timeout=10,
|
||||
)
|
||||
assert event.state == "playing"
|
||||
assert "bad-stream" in log_path.read_text()
|
||||
assert "good-stream" in log_path.read_text()
|
||||
|
||||
player.pause()
|
||||
wait_for(player, lambda ev: ev.kind == "state" and ev.state == "paused")
|
||||
|
||||
player.resume()
|
||||
wait_for(player, lambda ev: ev.kind == "state" and ev.state == "playing")
|
||||
|
||||
player.set_volume(42)
|
||||
volume_event, _ = wait_for(
|
||||
player, lambda ev: ev.kind == "volume" and ev.volume == 42
|
||||
)
|
||||
assert volume_event.volume == 42
|
||||
|
||||
player.stop()
|
||||
wait_for(player, lambda ev: ev.kind == "state" and ev.state == "stopped")
|
||||
finally:
|
||||
player.shutdown()
|
||||
if player._thread is not None:
|
||||
player._thread.join(timeout=2)
|
||||
assert player._thread is None or not player._thread.is_alive()
|
||||
|
||||
|
||||
def test_backend_detection_without_tools(tmp_path, monkeypatch) -> None:
|
||||
empty = tmp_path / "empty"
|
||||
empty.mkdir()
|
||||
monkeypatch.setenv("PATH", str(empty))
|
||||
player = Player()
|
||||
player.start()
|
||||
assert player.backend is None
|
||||
event, _ = wait_for(player, lambda ev: ev.kind == "state", timeout=1)
|
||||
assert event.state == "error"
|
||||
player.shutdown()
|
||||
Reference in new issue
Block a user