chore: normalize line endings to LF, add .gitattributes

- 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
This commit is contained in:
Artem Akymenko
2026-07-12 16:20:42 +03:00
parent c85ea9d64f
commit d8fcfb1cce
60 changed files with 19745 additions and 19745 deletions
+183 -183
View File
@@ -1,183 +1,183 @@
"""Contract tests for capability interfaces.
These tests verify that capability interfaces satisfy the architectural requirements:
- VoiceLister: lists voices for a source
- PreviewGenerator: generates preview audio
- StreamingSynthesizer: yields audio chunks
- CancelableSession: cancels in-progress synthesis
"""
import pytest
from abogen.tts_plugin.capabilities import (
CancelableSession,
PreviewGenerator,
StreamingSynthesizer,
VoiceLister,
)
from abogen.tts_plugin.errors import CancelledError, EngineError
from abogen.tts_plugin.manifest import VoiceManifest
from abogen.tts_plugin.types import (
AudioFormat,
Duration,
ParameterValues,
SynthesisRequest,
SynthesizedAudio,
VoiceSelection,
)
from .conftest import FakeCancelableSession, FakeStreamingSession, FakeVoiceListerEngine
class TestVoiceListerProtocolContract:
"""Contract tests for VoiceLister protocol."""
def test_voice_lister_is_protocol(self) -> None:
assert hasattr(VoiceLister, "__protocol_attrs__")
def test_voice_lister_satisfied_by_engine(self) -> None:
engine = FakeVoiceListerEngine()
assert isinstance(engine, VoiceLister)
def test_list_voices_returns_list(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
assert isinstance(voices, list)
def test_list_voices_returns_voice_manifests(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
for voice in voices:
assert isinstance(voice, VoiceManifest)
def test_list_voices_has_required_fields(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
for voice in voices:
assert hasattr(voice, "id")
assert hasattr(voice, "name")
assert hasattr(voice, "tags")
class TestPreviewGeneratorProtocolContract:
"""Contract tests for PreviewGenerator protocol."""
def test_preview_generator_is_protocol(self) -> None:
assert hasattr(PreviewGenerator, "__protocol_attrs__")
def test_preview_generator_satisfied_by_engine(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
assert isinstance(engine, PreviewGenerator)
def test_generate_preview_returns_synthesized_audio(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
voice = VoiceSelection(source="builtin", key="af_nova")
result = engine.generatePreview(voice, "Hello")
assert isinstance(result, SynthesizedAudio)
def test_generate_preview_has_valid_data(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
voice = VoiceSelection(source="builtin", key="af_nova")
result = engine.generatePreview(voice, "Hello")
assert isinstance(result.data, bytes)
assert len(result.data) > 0
class TestStreamingSynthesizerProtocolContract:
"""Contract tests for StreamingSynthesizer protocol."""
def test_streaming_synthesizer_is_protocol(self) -> None:
assert hasattr(StreamingSynthesizer, "__protocol_attrs__")
def test_streaming_session_satisfies_protocol(self) -> None:
session = FakeStreamingSession()
assert isinstance(session, StreamingSynthesizer)
def test_synthesize_stream_yields_bytes(self) -> None:
session = FakeStreamingSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
chunks = list(session.synthesizeStream(request))
assert len(chunks) > 0
for chunk in chunks:
assert isinstance(chunk, bytes)
def test_streaming_iterator_exhaustion(self) -> None:
"""Architecture spec: Iterator exhaustion = synthesis complete."""
session = FakeStreamingSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
chunks = list(session.synthesizeStream(request))
assert len(chunks) == 3
def test_streaming_after_dispose_raises(self) -> None:
"""Architecture spec: After dispose(), methods raise EngineError."""
session = FakeStreamingSession()
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):
list(session.synthesizeStream(request))
class TestCancelableSessionProtocolContract:
"""Contract tests for CancelableSession protocol."""
def test_cancelable_session_is_protocol(self) -> None:
assert hasattr(CancelableSession, "__protocol_attrs__")
def test_cancelable_session_satisfies_protocol(self) -> None:
session = FakeCancelableSession()
assert isinstance(session, CancelableSession)
def test_cancel_causes_synthesize_to_raise_cancelled(self) -> None:
"""Architecture spec: cancel() causes synthesize() to raise CancelledError."""
session = FakeCancelableSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
# Cancel
session.cancel()
# synthesize should raise CancelledError
with pytest.raises(CancelledError):
session.synthesize(request)
def test_cancel_after_dispose_raises(self) -> None:
"""Architecture spec: cancel() raises EngineError if called after dispose()."""
session = FakeCancelableSession()
session.dispose()
with pytest.raises(EngineError):
session.cancel()
def test_session_usable_after_cancel(self) -> None:
"""Architecture spec: EngineSession remains usable after cancellation."""
session = FakeCancelableSession()
# Cancel
session.cancel()
# Dispose and create new session for synthesis
session.dispose()
"""Contract tests for capability interfaces.
These tests verify that capability interfaces satisfy the architectural requirements:
- VoiceLister: lists voices for a source
- PreviewGenerator: generates preview audio
- StreamingSynthesizer: yields audio chunks
- CancelableSession: cancels in-progress synthesis
"""
import pytest
from abogen.tts_plugin.capabilities import (
CancelableSession,
PreviewGenerator,
StreamingSynthesizer,
VoiceLister,
)
from abogen.tts_plugin.errors import CancelledError, EngineError
from abogen.tts_plugin.manifest import VoiceManifest
from abogen.tts_plugin.types import (
AudioFormat,
Duration,
ParameterValues,
SynthesisRequest,
SynthesizedAudio,
VoiceSelection,
)
from .conftest import FakeCancelableSession, FakeStreamingSession, FakeVoiceListerEngine
class TestVoiceListerProtocolContract:
"""Contract tests for VoiceLister protocol."""
def test_voice_lister_is_protocol(self) -> None:
assert hasattr(VoiceLister, "__protocol_attrs__")
def test_voice_lister_satisfied_by_engine(self) -> None:
engine = FakeVoiceListerEngine()
assert isinstance(engine, VoiceLister)
def test_list_voices_returns_list(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
assert isinstance(voices, list)
def test_list_voices_returns_voice_manifests(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
for voice in voices:
assert isinstance(voice, VoiceManifest)
def test_list_voices_has_required_fields(self) -> None:
engine = FakeVoiceListerEngine()
voices = engine.listVoices("builtin")
for voice in voices:
assert hasattr(voice, "id")
assert hasattr(voice, "name")
assert hasattr(voice, "tags")
class TestPreviewGeneratorProtocolContract:
"""Contract tests for PreviewGenerator protocol."""
def test_preview_generator_is_protocol(self) -> None:
assert hasattr(PreviewGenerator, "__protocol_attrs__")
def test_preview_generator_satisfied_by_engine(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
assert isinstance(engine, PreviewGenerator)
def test_generate_preview_returns_synthesized_audio(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
voice = VoiceSelection(source="builtin", key="af_nova")
result = engine.generatePreview(voice, "Hello")
assert isinstance(result, SynthesizedAudio)
def test_generate_preview_has_valid_data(self) -> None:
from .conftest import FakePreviewEngine
engine = FakePreviewEngine()
voice = VoiceSelection(source="builtin", key="af_nova")
result = engine.generatePreview(voice, "Hello")
assert isinstance(result.data, bytes)
assert len(result.data) > 0
class TestStreamingSynthesizerProtocolContract:
"""Contract tests for StreamingSynthesizer protocol."""
def test_streaming_synthesizer_is_protocol(self) -> None:
assert hasattr(StreamingSynthesizer, "__protocol_attrs__")
def test_streaming_session_satisfies_protocol(self) -> None:
session = FakeStreamingSession()
assert isinstance(session, StreamingSynthesizer)
def test_synthesize_stream_yields_bytes(self) -> None:
session = FakeStreamingSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
chunks = list(session.synthesizeStream(request))
assert len(chunks) > 0
for chunk in chunks:
assert isinstance(chunk, bytes)
def test_streaming_iterator_exhaustion(self) -> None:
"""Architecture spec: Iterator exhaustion = synthesis complete."""
session = FakeStreamingSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
chunks = list(session.synthesizeStream(request))
assert len(chunks) == 3
def test_streaming_after_dispose_raises(self) -> None:
"""Architecture spec: After dispose(), methods raise EngineError."""
session = FakeStreamingSession()
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):
list(session.synthesizeStream(request))
class TestCancelableSessionProtocolContract:
"""Contract tests for CancelableSession protocol."""
def test_cancelable_session_is_protocol(self) -> None:
assert hasattr(CancelableSession, "__protocol_attrs__")
def test_cancelable_session_satisfies_protocol(self) -> None:
session = FakeCancelableSession()
assert isinstance(session, CancelableSession)
def test_cancel_causes_synthesize_to_raise_cancelled(self) -> None:
"""Architecture spec: cancel() causes synthesize() to raise CancelledError."""
session = FakeCancelableSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
# Cancel
session.cancel()
# synthesize should raise CancelledError
with pytest.raises(CancelledError):
session.synthesize(request)
def test_cancel_after_dispose_raises(self) -> None:
"""Architecture spec: cancel() raises EngineError if called after dispose()."""
session = FakeCancelableSession()
session.dispose()
with pytest.raises(EngineError):
session.cancel()
def test_session_usable_after_cancel(self) -> None:
"""Architecture spec: EngineSession remains usable after cancellation."""
session = FakeCancelableSession()
# Cancel
session.cancel()
# Dispose and create new session for synthesis
session.dispose()