lesson-agent-dev / libs /echocoach /tests /test_recording.py
MSG
Feat/voice stuff (#7)
1719c2a
Raw
History Blame Contribute Delete
3.92 kB
from __future__ import annotations
from pathlib import Path
import pytest
from echocoach.recording import (
ServerRecordingError,
recording_level_warning,
select_capture_backend,
start_server_recording,
stop_server_recording,
)
class _FakeProcess:
def __init__(self, out_path: Path) -> None:
self._running = True
self.returncode: int | None = None
self.stderr = None
self._out_path = out_path
def poll(self) -> int | None:
return None if self._running else self.returncode
def send_signal(self, sig: int) -> None:
self._running = False
self.returncode = 1 # pw-record exits 1 on SIGINT
self._out_path.write_bytes(b"RIFF" + b"x" * 100)
def wait(self, timeout: float | None = None) -> int:
return 0
def test_select_capture_backend_prefers_pw_record(monkeypatch):
monkeypatch.setattr("echocoach.recording._pw_record_available", lambda: True)
monkeypatch.setattr("echocoach.recording._sounddevice_available", lambda: True)
monkeypatch.setattr("echocoach.recording._arecord_available", lambda: True)
assert select_capture_backend() == "pw-record"
class _FakeTimer:
def __init__(self, *_args, **_kwargs) -> None:
self.daemon = False
def start(self) -> None:
return None
def cancel(self) -> None:
return None
def test_start_stop_session(monkeypatch, tmp_path):
import echocoach.recording as rec
rec._session = None
def fake_spawn(backend, path: Path):
return _FakeProcess(path)
monkeypatch.setattr("echocoach.recording.select_capture_backend", lambda: "pw-record")
monkeypatch.setattr("echocoach.recording.outputs_dir", lambda: tmp_path)
monkeypatch.setattr("echocoach.recording._spawn_capture_process", fake_spawn)
monkeypatch.setattr("echocoach.recording.threading.Timer", _FakeTimer)
start_server_recording(10)
path = stop_server_recording()
assert path.is_file()
rec._session = None
def test_start_while_recording_raises(monkeypatch, tmp_path):
import echocoach.recording as rec
rec._session = None
def fake_spawn(backend, path: Path):
return _FakeProcess(path)
monkeypatch.setattr("echocoach.recording.select_capture_backend", lambda: "pw-record")
monkeypatch.setattr("echocoach.recording.outputs_dir", lambda: tmp_path)
monkeypatch.setattr("echocoach.recording._spawn_capture_process", fake_spawn)
monkeypatch.setattr("echocoach.recording.threading.Timer", _FakeTimer)
start_server_recording(10)
with pytest.raises(ServerRecordingError, match="Already recording"):
start_server_recording(10)
stop_server_recording()
rec._session = None
def test_stop_without_start_raises():
import echocoach.recording as rec
rec._session = None
rec._last_recording_path = None
with pytest.raises(ServerRecordingError, match="Not recording"):
stop_server_recording()
def test_recording_level_warning_detects_silence(tmp_path):
import numpy as np
import soundfile as sf
silent = tmp_path / "silent.wav"
sf.write(silent, np.zeros(16_000, dtype=np.float32), 16_000)
assert "silent" in (recording_level_warning(silent) or "").lower()
def test_finalize_accepts_pw_record_exit_code_one(tmp_path, monkeypatch):
import echocoach.recording as rec
rec._session = None
rec._last_recording_path = None
out_path = tmp_path / "recordings" / "server_pw.wav"
out_path.parent.mkdir(parents=True)
fake_proc = _FakeProcess(out_path)
session = rec._RecordingSession(
process=fake_proc,
out_path=out_path,
backend="pw-record",
max_seconds=10,
started_at=0.0,
watchdog=None,
)
rec._session = session
path = rec.stop_server_recording()
assert path == out_path
assert path.stat().st_size > 44
rec._session = None