mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
- Normalize Pipeline public API: create_pipeline(plugin_id, *, lang_code, device) - EngineConfig: add lang_code field per Architecture Amendment #1 - Kokoro plugin reads config.lang_code (fixes functional regression) - Static voice catalog in PluginManifest.voices (None = dynamic/VoiceLister) - get_voices() reads from manifest without creating Engine - Remove dead kwargs (sample_rate, auto_download, total_steps) from SuperTonic - Clean up unused imports and dead code in engine implementations - Fix test expectations for VoiceLister (mock overrides) - Add clear_preview_pipelines() for resource management
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
"""Base contract tests for Engine implementations.
|
|
|
|
Any new TTS plugin must inherit from these classes to verify
|
|
it satisfies the Engine/EngineSession protocol.
|
|
|
|
Usage:
|
|
from tests.contracts.engine_contract import EngineContractMixin
|
|
|
|
class TestMyEngine(EngineContractMixin):
|
|
@pytest.fixture
|
|
def engine(self):
|
|
return create_my_engine()
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from abogen.tts_plugin.engine import Engine, EngineSession
|
|
from abogen.tts_plugin.errors import EngineError
|
|
from abogen.tts_plugin.types import (
|
|
AudioFormat,
|
|
ParameterValues,
|
|
SynthesisRequest,
|
|
SynthesizedAudio,
|
|
VoiceSelection,
|
|
)
|
|
|
|
|
|
class EngineContractMixin:
|
|
"""Base contract tests for Engine implementations.
|
|
|
|
Subclasses must define a module-level ``engine`` fixture returning
|
|
a fully initialized Engine instance. The tests below will use it
|
|
via pytest's standard fixture resolution.
|
|
"""
|
|
|
|
def _req(self, text: str = "Hello", voice: str | None = None) -> SynthesisRequest:
|
|
return SynthesisRequest(
|
|
text=text,
|
|
voice=VoiceSelection(source="builtin", key=voice or "default"),
|
|
parameters=ParameterValues(values={}),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
|
|
# ── Engine protocol ──────────────────────────────────────
|
|
|
|
def test_engine_satisfies_protocol(self, engine: Engine) -> None:
|
|
assert isinstance(engine, Engine)
|
|
|
|
def test_create_session_returns_session(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
assert isinstance(session, EngineSession)
|
|
session.dispose()
|
|
|
|
def test_create_session_returns_new_instances(self, engine: Engine) -> None:
|
|
s1 = engine.createSession()
|
|
s2 = engine.createSession()
|
|
assert s1 is not s2
|
|
s1.dispose()
|
|
s2.dispose()
|
|
|
|
def test_dispose_is_idempotent(self, engine: Engine) -> None:
|
|
engine.dispose()
|
|
engine.dispose()
|
|
|
|
def test_create_session_after_dispose_raises(self, engine: Engine) -> None:
|
|
engine.dispose()
|
|
with pytest.raises(EngineError):
|
|
engine.createSession()
|
|
|
|
# ── Session protocol ─────────────────────────────────────
|
|
|
|
def test_session_satisfies_protocol(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
assert isinstance(session, EngineSession)
|
|
session.dispose()
|
|
engine.dispose()
|
|
|
|
def test_session_synthesize_returns_audio(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
result = session.synthesize(self._req())
|
|
assert isinstance(result, SynthesizedAudio)
|
|
assert isinstance(result.data, bytes)
|
|
assert len(result.data) > 0
|
|
session.dispose()
|
|
engine.dispose()
|
|
|
|
def test_session_dispose_is_idempotent(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
session.dispose()
|
|
session.dispose()
|
|
engine.dispose()
|
|
|
|
def test_session_synthesize_after_dispose_raises(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
session.dispose()
|
|
with pytest.raises(EngineError):
|
|
session.synthesize(self._req())
|
|
engine.dispose()
|
|
|
|
def test_session_multiple_synthesize(self, engine: Engine) -> None:
|
|
session = engine.createSession()
|
|
r1 = session.synthesize(self._req())
|
|
r2 = session.synthesize(self._req())
|
|
assert isinstance(r1.data, bytes)
|
|
assert isinstance(r2.data, bytes)
|
|
session.dispose()
|
|
engine.dispose()
|
|
|
|
# ── Lifecycle ────────────────────────────────────────────
|
|
|
|
def test_full_lifecycle(self, engine: Engine) -> None:
|
|
s1 = engine.createSession()
|
|
s2 = engine.createSession()
|
|
s1.synthesize(self._req())
|
|
s2.synthesize(self._req())
|
|
s1.dispose()
|
|
s2.dispose()
|
|
engine.dispose()
|