Skip to content

Commit

Permalink
Add tests and Github actions
Browse files Browse the repository at this point in the history
  • Loading branch information
synesthesiam committed Jan 29, 2024
1 parent f3e6e1e commit b3a231b
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 11 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: test

on:
workflow_dispatch:
pull_request:

jobs:
test_linux:
name: "test on linux"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: setup
run: |
script/setup --dev
- name: test
run: |
test $(script/run --version) = $(cat wyoming_vosk/VERSION)
script/lint
script/test
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

## 1.4.0

- Add tests and Github actions
- Bump wyoming to 1.5.2

## 1.3.0

- Public release
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
vosk==0.3.45
wyoming==1.2.0
wyoming==1.5.2
2 changes: 2 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ flake8==6.0.0
isort==5.11.3
mypy==0.991
pylint==2.15.9
pytest==7.4.4
pytest-asyncio==0.23.3
7 changes: 5 additions & 2 deletions script/format
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ _DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_MODULE_DIR = _PROGRAM_DIR / "wyoming_vosk"
_TESTS_DIR = _PROGRAM_DIR / "tests"

_FORMAT_DIRS = [_MODULE_DIR, _TESTS_DIR]

context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, "-m", "black", str(_MODULE_DIR)])
subprocess.check_call([context.env_exe, "-m", "isort", str(_MODULE_DIR)])
subprocess.check_call([context.env_exe, "-m", "black"] + _FORMAT_DIRS)
subprocess.check_call([context.env_exe, "-m", "isort"] + _FORMAT_DIRS)
13 changes: 8 additions & 5 deletions script/lint
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ _DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_MODULE_DIR = _PROGRAM_DIR / "wyoming_vosk"
_TESTS_DIR = _PROGRAM_DIR / "tests"

_LINT_DIRS = [_MODULE_DIR, _TESTS_DIR]

context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, "-m", "black", str(_MODULE_DIR), "--check"])
subprocess.check_call([context.env_exe, "-m", "isort", str(_MODULE_DIR), "--check"])
subprocess.check_call([context.env_exe, "-m", "flake8", str(_MODULE_DIR)])
subprocess.check_call([context.env_exe, "-m", "pylint", str(_MODULE_DIR)])
subprocess.check_call([context.env_exe, "-m", "mypy", str(_MODULE_DIR)])
subprocess.check_call([context.env_exe, "-m", "black"] + _LINT_DIRS + ["--check"])
subprocess.check_call([context.env_exe, "-m", "isort"] + _LINT_DIRS + ["--check"])
subprocess.check_call([context.env_exe, "-m", "flake8"] + _LINT_DIRS)
subprocess.check_call([context.env_exe, "-m", "pylint"] + _LINT_DIRS)
subprocess.check_call([context.env_exe, "-m", "mypy"] + _LINT_DIRS)
10 changes: 10 additions & 0 deletions script/setup
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import argparse
import subprocess
import venv
from pathlib import Path
Expand All @@ -7,6 +8,9 @@ _DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"

parser = argparse.ArgumentParser()
parser.add_argument("--dev", action="store_true", help="Install dev requirements")
args = parser.parse_args()

# Create virtual environment
builder = venv.EnvBuilder(with_pip=True)
Expand All @@ -20,3 +24,9 @@ subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])

# Install requirements
subprocess.check_call(pip + ["install", "-r", str(_PROGRAM_DIR / "requirements.txt")])

if args.dev:
# Install dev requirements
subprocess.check_call(
pip + ["install", "-r", str(_PROGRAM_DIR / "requirements_dev.txt")]
)
13 changes: 13 additions & 0 deletions script/test
Original file line number Diff line number Diff line change
@@ -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:])
13 changes: 11 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,26 @@
with open(requirements_path, "r", encoding="utf-8") as requirements_file:
requirements = requirements_file.read().splitlines()

module_name = "wyoming_vosk"
module_dir = this_dir / module_name
data_files = []

version_path = module_dir / "VERSION"
data_files.append(version_path)
version = version_path.read_text(encoding="utf-8").strip()

# -----------------------------------------------------------------------------

setup(
name="wyoming_vosk",
version="1.3.0",
name=module_name,
version=version,
description="Wyoming Server for Vosk",
url="http://github.com/rhasspy/wyoming-vosk",
author="Michael Hansen",
author_email="[email protected]",
license="MIT",
packages=setuptools.find_packages(),
package_data={module_name: [str(p.relative_to(module_dir)) for p in data_files]},
install_requires=requirements,
extras_require={"limited": ["PyYAML>=6,<7", "rapidfuzz==3.3.1", "hassil==1.2.5"]},
classifiers=[
Expand Down
96 changes: 96 additions & 0 deletions tests/test_vosk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Tests for wyoming-vosk"""
import asyncio
import re
import sys
import wave
from asyncio.subprocess import PIPE
from pathlib import Path

import pytest
from wyoming.asr import Transcribe, Transcript
from wyoming.audio import AudioStart, AudioStop, wav_to_chunks
from wyoming.event import async_read_event, async_write_event
from wyoming.info import Describe, Info

_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_LOCAL_DIR = _PROGRAM_DIR / "local"
_SAMPLES_PER_CHUNK = 1024
_MODEL = "vosk-model-small-en-us-0.15"

# Need to give time for the model to download
_TRANSCRIBE_TIMEOUT = 60


@pytest.mark.asyncio
async def test_vosk() -> None:
proc = await asyncio.create_subprocess_exec(
sys.executable,
"-m",
"wyoming_vosk",
"--uri",
"stdio://",
"--data-dir",
str(_LOCAL_DIR),
"--language",
"en",
stdin=PIPE,
stdout=PIPE,
)
assert proc.stdin is not None
assert proc.stdout is not None

# Check info
await async_write_event(Describe().event(), proc.stdin)
while True:
event = await asyncio.wait_for(async_read_event(proc.stdout), timeout=1)
assert event is not None

if not Info.is_type(event.type):
continue

info = Info.from_event(event)
assert len(info.asr) == 1, "Expected one asr service"
asr = info.asr[0]
assert len(asr.models) > 0, "Expected at least one model"
assert any(m.name == _MODEL for m in asr.models), f"Expected {_MODEL} model"
break

# We want to use the whisper model
await async_write_event(Transcribe(name=_MODEL).event(), proc.stdin)

# Test known WAV
with wave.open(str(_DIR / "turn_on_the_living_room_lamp.wav"), "rb") as example_wav:
await async_write_event(
AudioStart(
rate=example_wav.getframerate(),
width=example_wav.getsampwidth(),
channels=example_wav.getnchannels(),
).event(),
proc.stdin,
)
for chunk in wav_to_chunks(example_wav, _SAMPLES_PER_CHUNK):
await async_write_event(chunk.event(), proc.stdin)

await async_write_event(AudioStop().event(), proc.stdin)

while True:
event = await asyncio.wait_for(
async_read_event(proc.stdout), timeout=_TRANSCRIBE_TIMEOUT
)
assert event is not None

if not Transcript.is_type(event.type):
continue

transcript = Transcript.from_event(event)
text = transcript.text.lower().strip()
text = re.sub(r"[^a-z ]", "", text)
assert text == "turn on the living room lamp"
break

# Need to close stdin for graceful termination
proc.stdin.close()
_, stderr = await proc.communicate()

assert proc.returncode == 0, stderr.decode()
Binary file added tests/turn_on_the_living_room_lamp.wav
Binary file not shown.
1 change: 1 addition & 0 deletions wyoming_vosk/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.4.0
9 changes: 9 additions & 0 deletions wyoming_vosk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Wyoming server for vosk."""
from pathlib import Path

_DIR = Path(__file__).parent
_VERSION_PATH = _DIR / "VERSION"

__version__ = _VERSION_PATH.read_text(encoding="utf-8").strip()

__all__ = ["__version__"]
16 changes: 15 additions & 1 deletion wyoming_vosk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from wyoming.info import AsrModel, AsrProgram, Attribution, Describe, Info
from wyoming.server import AsyncEventHandler, AsyncServer

from . import __version__
from .download import CASING_FOR_MODEL, MODELS, UNK_FOR_MODEL, download_model
from .sentences import correct_sentence, load_sentences_for_language

Expand Down Expand Up @@ -148,6 +149,15 @@ async def main() -> None:
)
#
parser.add_argument("--debug", action="store_true", help="Log DEBUG messages")
parser.add_argument(
"--log-format", default=logging.BASIC_FORMAT, help="Format for log messages"
)
parser.add_argument(
"--version",
action="version",
version=__version__,
help="Print version and exit",
)
args = parser.parse_args()

if (args.correct_sentences is not None) or args.limit_sentences:
Expand All @@ -170,7 +180,9 @@ async def main() -> None:
# Convert to dict of language -> casing
args.casing_for_language = dict(args.casing_for_language)

logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
logging.basicConfig(
level=logging.DEBUG if args.debug else logging.INFO, format=args.log_format
)
_LOGGER.debug(args)

if args.debug:
Expand All @@ -187,6 +199,7 @@ async def main() -> None:
url="https://alphacephei.com/vosk/",
),
installed=True,
version=__version__,
models=[
AsrModel(
name=model_name,
Expand All @@ -196,6 +209,7 @@ async def main() -> None:
url="https://alphacephei.com/vosk/models",
),
installed=True,
version=None,
languages=[language],
)
for language, model_names in MODELS.items()
Expand Down

0 comments on commit b3a231b

Please sign in to comment.