mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
- Add .gitattributes with text=auto eol=lf for all text files - Renormalize all files in index to LF line endings - Fixes massive whitespace-only diffs between main and feature branch
136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
"""Contract tests for EngineSession protocol.
|
|
|
|
These tests verify that EngineSession implementations satisfy the architectural requirements:
|
|
- synthesize() returns SynthesizedAudio
|
|
- dispose() is idempotent
|
|
- After dispose(), synthesize() raises EngineError
|
|
- Session remains usable after synthesize() failure
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from abogen.tts_plugin.engine import EngineSession
|
|
from abogen.tts_plugin.errors import EngineError
|
|
from abogen.tts_plugin.types import (
|
|
AudioFormat,
|
|
Duration,
|
|
ParameterValues,
|
|
SynthesisRequest,
|
|
SynthesizedAudio,
|
|
VoiceSelection,
|
|
)
|
|
|
|
from .conftest import FakeEngineSession
|
|
|
|
|
|
class TestEngineSessionProtocolContract:
|
|
"""Contract tests for the EngineSession protocol itself."""
|
|
|
|
def test_engine_session_is_protocol(self) -> None:
|
|
assert hasattr(EngineSession, "__protocol_attrs__")
|
|
|
|
def test_fake_session_satisfies_protocol(self) -> None:
|
|
session = FakeEngineSession()
|
|
assert isinstance(session, EngineSession)
|
|
|
|
|
|
class TestSessionSynthesizeContract:
|
|
"""Contract tests for EngineSession.synthesize()."""
|
|
|
|
def test_synthesize_returns_synthesized_audio(self) -> None:
|
|
session = FakeEngineSession()
|
|
request = SynthesisRequest(
|
|
text="Hello",
|
|
voice=VoiceSelection(source="builtin", key="af_nova"),
|
|
parameters=ParameterValues(),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
result = session.synthesize(request)
|
|
assert isinstance(result, SynthesizedAudio)
|
|
|
|
def test_synthesize_returns_valid_audio_data(self) -> None:
|
|
session = FakeEngineSession()
|
|
request = SynthesisRequest(
|
|
text="Hello",
|
|
voice=VoiceSelection(source="builtin", key="af_nova"),
|
|
parameters=ParameterValues(),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
result = session.synthesize(request)
|
|
assert isinstance(result.data, bytes)
|
|
assert len(result.data) > 0
|
|
assert isinstance(result.format, AudioFormat)
|
|
assert isinstance(result.duration, Duration)
|
|
|
|
|
|
class TestSessionDisposeContract:
|
|
"""Contract tests for EngineSession.dispose()."""
|
|
|
|
def test_dispose_is_idempotent(self) -> None:
|
|
"""Architecture spec: dispose() is idempotent."""
|
|
session = FakeEngineSession()
|
|
session.dispose()
|
|
session.dispose() # Should not raise
|
|
|
|
def test_dispose_never_raises(self) -> None:
|
|
"""Architecture spec: dispose() never raises exceptions."""
|
|
session = FakeEngineSession()
|
|
session.dispose() # Should not raise
|
|
|
|
|
|
class TestSessionAfterDisposeContract:
|
|
"""Contract tests for behavior after dispose()."""
|
|
|
|
def test_synthesize_after_dispose_raises(self) -> None:
|
|
"""Architecture spec: After dispose(), all methods except dispose() raise EngineError."""
|
|
session = FakeEngineSession()
|
|
session.dispose()
|
|
request = SynthesisRequest(
|
|
text="Hello",
|
|
voice=VoiceSelection(source="builtin", key="af_nova"),
|
|
parameters=ParameterValues(),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
with pytest.raises(EngineError):
|
|
session.synthesize(request)
|
|
|
|
|
|
class TestSessionLifecycleContract:
|
|
"""Contract tests for EngineSession lifecycle."""
|
|
|
|
def test_full_lifecycle(self) -> None:
|
|
"""Test complete session lifecycle: create -> synthesize -> dispose."""
|
|
session = FakeEngineSession()
|
|
request = SynthesisRequest(
|
|
text="Hello",
|
|
voice=VoiceSelection(source="builtin", key="af_nova"),
|
|
parameters=ParameterValues(),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
|
|
# Synthesize
|
|
result = session.synthesize(request)
|
|
assert isinstance(result, SynthesizedAudio)
|
|
|
|
# Dispose
|
|
session.dispose()
|
|
|
|
def test_multiple_synthesize_before_dispose(self) -> None:
|
|
"""Architecture spec: Session remains usable after synthesize() failure."""
|
|
session = FakeEngineSession()
|
|
request = SynthesisRequest(
|
|
text="Hello",
|
|
voice=VoiceSelection(source="builtin", key="af_nova"),
|
|
parameters=ParameterValues(),
|
|
format=AudioFormat(mime="audio/wav", extension="wav"),
|
|
)
|
|
|
|
# Multiple synthesize calls
|
|
result1 = session.synthesize(request)
|
|
result2 = session.synthesize(request)
|
|
assert isinstance(result1, SynthesizedAudio)
|
|
assert isinstance(result2, SynthesizedAudio)
|
|
|
|
# Dispose
|
|
session.dispose()
|