From 172a85846c3092cb0c9d4fcd08c149c205bf30ea Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 9 Jan 2024 10:59:28 -0600 Subject: [PATCH] Working on tests --- requirements_dev.txt | 1 + script/test | 13 +++++ tests/__init__.py | 1 + tests/shared.py | 17 ++++++ tests/test_always_streaming.py | 102 +++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+) create mode 100755 script/test create mode 100644 tests/__init__.py create mode 100644 tests/shared.py create mode 100644 tests/test_always_streaming.py diff --git a/requirements_dev.txt b/requirements_dev.txt index 77190e6..18755a4 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,3 +3,4 @@ flake8==6.0.0 isort==5.11.3 mypy==0.991 pylint==2.15.9 +pytest==7.4.3 diff --git a/script/test b/script/test new file mode 100755 index 0000000..5ad78a8 --- /dev/null +++ b/script/test @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import venv +from pathlib import Path + +_DIR = Path(__file__).parent +_PROGRAM_DIR = _DIR.parent +_VENV_DIR = _PROGRAM_DIR / ".venv" +_TEST_DIR = _PROGRAM_DIR / "tests" + +context = venv.EnvBuilder().ensure_directories(_VENV_DIR) +subprocess.check_call([context.env_exe, "-m", "pytest", _TEST_DIR] + sys.argv[1:]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..fbab3dc --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Wyoming satellite tests.""" diff --git a/tests/shared.py b/tests/shared.py new file mode 100644 index 0000000..aa626f3 --- /dev/null +++ b/tests/shared.py @@ -0,0 +1,17 @@ +"""Shared code for Wyoming satellite tests.""" +from wyoming.info import Info, Attribution, Satellite +from wyoming.audio import AudioChunk + +WYOMING_INFO = Info( + satellite=Satellite( + name="test satellite", + area="test area", + description="test description", + attribution=Attribution(name="", url=""), + installed=True, + ) +) + +AUDIO_CHUNK = AudioChunk( + rate=16000, width=2, channels=1, audio=bytes([255] * 960) # 30ms +) diff --git a/tests/test_always_streaming.py b/tests/test_always_streaming.py new file mode 100644 index 0000000..332c87e --- /dev/null +++ b/tests/test_always_streaming.py @@ -0,0 +1,102 @@ +import asyncio +import logging +import os +from pathlib import Path + +import pytest +from wyoming.audio import AudioChunk +from wyoming.wake import Detection +from wyoming.client import AsyncClient +from wyoming.event import Event, async_write_event +from wyoming.server import AsyncServer, AsyncEventHandler +from wyoming_satellite import ( + AlwaysStreamingSatellite, + EventSettings, + SatelliteSettings, + MicSettings, + WakeSettings, +) + +from .shared import WYOMING_INFO, AUDIO_CHUNK + +_LOGGER = logging.getLogger() + +class MicEventHandler(AsyncEventHandler): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._is_connected = True + self._task = asyncio.create_task(self.stream_audio()) + + async def handle_event(self, event: Event) -> bool: + """Output only.""" + return True + + async def stream_audio(self) -> None: + while self._is_connected: + await self.write_event(AUDIO_CHUNK.event()) + await asyncio.sleep(AUDIO_CHUNK.seconds) + + async def disconnect(self) -> None: + self._is_connected = False + await self._task + + +class WakeEventHandler(AsyncEventHandler): + async def handle_event(self, event: Event) -> bool: + _LOGGER.error(event) + if AudioChunk.is_type(event.type): + await self.write_event(Detection().event()) + + return True + + +@pytest.mark.asyncio +async def test_multiple_wakeups(tmp_path: Path) -> None: + mic_socket = tmp_path / "mic.socket" + os.mkfifo(mic_socket) + mic_uri = f"unix://{mic_socket}" + mic_server = AsyncServer.from_uri(mic_uri) + mic_task = asyncio.create_task(mic_server.run(MicEventHandler)) + + wake_socket = tmp_path / "wake.socket" + os.mkfifo(wake_socket) + wake_uri = f"unix://{wake_socket}" + wake_server = AsyncServer.from_uri(wake_uri) + wake_task = asyncio.create_task(wake_server.run(WakeEventHandler)) + + wake_event = asyncio.Event() + + class EventEventHandler(AsyncEventHandler): + async def handle_event(self, event: Event) -> bool: + if Detection.is_type(event.type): + wake_event.set() + + return True + + event_socket = tmp_path / "event.socket" + os.mkfifo(event_socket) + event_uri = f"unix://{event_socket}" + event_server = AsyncServer.from_uri(event_uri) + event_task = asyncio.create_task(event_server.run(EventEventHandler)) + + satellite = AlwaysStreamingSatellite( + SatelliteSettings( + mic=MicSettings(uri=mic_uri), + wake=WakeSettings(uri=wake_uri), + event=EventSettings(uri=event_uri), + ) + ) + + satellite_task = asyncio.create_task(satellite.run()) + + await asyncio.sleep(0.5) + + # async with asyncio.timeout(1): + # await wake_event.wait() + + # await satellite.stop() + + tasks = [mic_task, wake_task, event_task] + for task in tasks: + task.cancel() + await asyncio.wait(tasks)