mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
- Create tests/plugins/ with auto-discovery fixtures and generic tests - Add conftest.py with plugin_ids, loaded_plugin, host_context fixtures - Add test_all_plugins.py with 3 test classes: - TestAllPluginsManifest: validates manifest structure - TestAllPluginsEngine: validates engine lifecycle contract - TestAllPluginsCapabilities: validates capability implementation - Update docs/testing.md with auto-discovery documentation - Plugin-specific tests remain in tests/test_*_plugin.py for integration New plugins in plugins/ are now automatically tested without manual test creation.
169 lines
4.8 KiB
Python
169 lines
4.8 KiB
Python
"""Fixtures for auto-discovery plugin tests.
|
|
|
|
This module provides shared fixtures for testing all plugins:
|
|
- plugin_ids: List of all plugin IDs from plugins/ directory
|
|
- plugin_dir: Path to a specific plugin directory (parametrized)
|
|
- loaded_plugin: Loaded plugin data (manifest, model_requirements, create_engine)
|
|
- host_context: Test HostContext with fake HTTP client
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from abogen.tts_plugin.host_context import HostContext
|
|
from abogen.tts_plugin.loader import load_plugin_from_dir
|
|
|
|
|
|
def _discover_plugin_ids() -> list[str]:
|
|
"""Discover all plugin IDs from the plugins/ directory.
|
|
|
|
Returns:
|
|
List of plugin IDs (directory names with __init__.py).
|
|
"""
|
|
plugins_dir = Path(__file__).parent.parent.parent / "plugins"
|
|
plugin_ids = []
|
|
if plugins_dir.exists():
|
|
for item in sorted(plugins_dir.iterdir()):
|
|
if item.is_dir() and not item.name.startswith("."):
|
|
init_file = item / "__init__.py"
|
|
if init_file.exists():
|
|
plugin_ids.append(item.name)
|
|
return plugin_ids
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def plugins_dir() -> Path:
|
|
"""Return the path to the plugins directory."""
|
|
return Path(__file__).parent.parent.parent / "plugins"
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def plugin_ids(plugins_dir: Path) -> list[str]:
|
|
"""Return a list of all plugin IDs from the plugins/ directory.
|
|
|
|
This fixture discovers all subdirectories in plugins/ that contain
|
|
an __init__.py file (i.e., valid plugin directories).
|
|
|
|
Returns:
|
|
List of plugin IDs (directory names).
|
|
"""
|
|
return _discover_plugin_ids()
|
|
|
|
|
|
@pytest.fixture(params=_discover_plugin_ids())
|
|
def plugin_id(request: pytest.FixtureRequest) -> str:
|
|
"""Parametrized fixture returning each plugin ID.
|
|
|
|
This fixture is parametrized to run tests for each plugin.
|
|
"""
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture
|
|
def plugin_dir(plugins_dir: Path, plugin_id: str) -> Path:
|
|
"""Return the path to a specific plugin directory.
|
|
|
|
Args:
|
|
plugins_dir: Base plugins directory.
|
|
plugin_id: The plugin ID (directory name).
|
|
|
|
Returns:
|
|
Path to the plugin directory.
|
|
"""
|
|
return plugins_dir / plugin_id
|
|
|
|
|
|
@pytest.fixture
|
|
def loaded_plugin(plugin_dir: Path):
|
|
"""Load a plugin and return its load result.
|
|
|
|
This fixture loads the plugin using the loader, providing access to:
|
|
- manifest: PluginManifest
|
|
- model_requirements: tuple[ModelManifest, ...]
|
|
- create_engine: Callable
|
|
- module: The loaded module
|
|
|
|
Args:
|
|
plugin_dir: Path to the plugin directory.
|
|
|
|
Returns:
|
|
PluginLoadResult from load_plugin_from_dir.
|
|
|
|
Raises:
|
|
pytest.skip: If plugin fails to load (should not happen for valid plugins).
|
|
"""
|
|
from abogen.tts_plugin.loader import PluginLoadResult
|
|
|
|
result = load_plugin_from_dir(plugin_dir)
|
|
if not result.success:
|
|
pytest.fail(
|
|
f"Plugin {plugin_dir.name} failed to load: "
|
|
f"{result.error.errors if result.error else 'Unknown error'}"
|
|
)
|
|
return result
|
|
|
|
|
|
@pytest.fixture
|
|
def host_context(tmp_path: Path) -> HostContext:
|
|
"""Create a test HostContext with fake HTTP client.
|
|
|
|
Args:
|
|
tmp_path: Pytest tmp_path fixture for config directory.
|
|
|
|
Returns:
|
|
HostContext with fake HTTP client and test logger.
|
|
"""
|
|
class FakeHttpClient:
|
|
"""Fake HTTP client for testing."""
|
|
|
|
def get(self, url: str, **kwargs: object) -> object:
|
|
"""Fake GET request."""
|
|
return None
|
|
|
|
def post(self, url: str, **kwargs: object) -> object:
|
|
"""Fake POST request."""
|
|
return None
|
|
|
|
return HostContext(
|
|
config_dir=tmp_path,
|
|
logger=logging.getLogger("test"),
|
|
http_client=FakeHttpClient(),
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def engine_config() -> Any:
|
|
"""Return a default EngineConfig for testing.
|
|
|
|
Returns:
|
|
EngineConfig with device="cpu" for testing.
|
|
"""
|
|
from abogen.tts_plugin.types import EngineConfig
|
|
return EngineConfig(device="cpu")
|
|
|
|
|
|
@pytest.fixture
|
|
def create_engine(loaded_plugin, host_context, engine_config):
|
|
"""Create an engine instance from a loaded plugin.
|
|
|
|
This fixture creates an engine and ensures proper cleanup via dispose.
|
|
|
|
Args:
|
|
loaded_plugin: Loaded plugin result.
|
|
host_context: Test HostContext.
|
|
engine_config: EngineConfig for engine creation.
|
|
|
|
Yields:
|
|
Engine instance.
|
|
"""
|
|
from abogen.tts_plugin.engine import Engine
|
|
|
|
engine = loaded_plugin.create_engine(host_context, None, engine_config)
|
|
assert isinstance(engine, Engine)
|
|
yield engine
|
|
engine.dispose() |