* Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend
Adds Supertonic-3 as a 7th opt-in TTS engine on the Phase 2
SubprocessBackend primitive. Closes TTS-01..06 (REQUIREMENTS.md):
* TTS-01 — _REGISTRY["supertonic3"] resolves to Supertonic3Backend,
a SubprocessBackend subclass.
* TTS-02 — `supertonic==1.3.1` lives under [project.optional-dependencies];
default `uv sync --no-dev` does NOT install it. Exactly one
`onnxruntime` row in `uv pip list` after `--extra supertonic`.
* TTS-03 — Model revision pinned by 40-char commit SHA
(724fb5abbf5502583fb520898d45929e62f02c0b — the "Initial
Supertonic 3 release" SHA, same as the SDK's own pin).
Resolver script for intentional bumps:
scripts/resolve_supertonic3_sha.py.
* TTS-04 — Honest CPU-only reporting. `is_available()` message says
"ready (CPU-only via onnxruntime)" and never mentions
"cuda" or "mps". `gpu_compat = ("cpu",)`.
* TTS-05 — License gate via settings_store helpers
(get/set_license_accepted) + Loopback-only
/api/settings/license endpoint + SupertonicLicenseDialog
frontend modal showing MIT (code) and OpenRAIL-M (model).
Wired into EngineCompatibilityMatrix as an "Accept license"
button on rows whose `reason` mentions "license not
accepted".
* TTS-06 — 3 langs (en/ja/ru) × 3 sec smoke test in
tests/test_supertonic3.py::test_smoke_3langs_3sec
(OMNIVOICE_SMOKE-gated; asserts no onnxruntime-gpu row
post-synthesize).
Package legitimacy gate (Task 1 in plan): supertonic on PyPI verified
to be published by Supertone Inc. (ato@supertone.ai), repo
github.com/supertone-inc/supertonic, wheel is pure-Python with no
postinstall scripts. Same publisher ships supertonic-js on npm under
the same maintainer email.
Test results:
* tests/test_supertonic3.py — 10 passed, 3 skipped (network-gated).
* tests/smoke/ — 4 passed.
* tests/ (full, --ignore=tests/manual) — 412 passed, 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(tests): uv sync --all-extras so optional-engine tests can import their package
Phase 3 added `supertonic` as an optional dependency. The CI Tests job
runs `uv sync` (no extras), so `test_cpu_only_honest` and `test_license_gate`
in tests/test_supertonic3.py hit the "supertonic package not installed"
fallback instead of the real import path, and fail.
Bare `uv sync` is the right default for users (engines are opt-in), but
the test environment should exercise the full surface. `--all-extras`
keeps the smoke job lean (still bare `uv sync`) while letting Tests
verify the integrated behavior of every optional engine.
Future-proofs against the same failure mode in Phase 4 (GGUF) and any
later optional engines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.8 KiB
Python
48 lines
1.8 KiB
Python
import os
|
|
import sys
|
|
|
|
# Backend runs with `--app-dir backend`, so tests must do the same.
|
|
_BACKEND = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "backend"))
|
|
if _BACKEND not in sys.path:
|
|
sys.path.insert(0, _BACKEND)
|
|
|
|
|
|
# ── Test fixtures ──────────────────────────────────────────────────────────
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_settings_store(monkeypatch):
|
|
"""In-memory replacement for ``services.settings_store`` license helpers.
|
|
|
|
Phase 3 Plan 03-01 / Wave 0 gap: the real settings_store talks to
|
|
SQLite via ``core.db.db_conn()``; that opens the project SQLite
|
|
file as a side effect of the import. Tests that exercise
|
|
``Supertonic3Backend.is_available()`` shouldn't need the SQLite
|
|
plumbing online ‑‑ they just need a controllable
|
|
``get_license_accepted`` / ``set_license_accepted`` pair.
|
|
|
|
Yields a dict ``{engine_id: bool}`` so tests can pre-seed
|
|
acceptance state or assert on what got written. The dict is
|
|
re-bound to the monkeypatched helpers on every read/write so a
|
|
test can mutate it directly to simulate "user clicked Accept".
|
|
"""
|
|
state: dict[str, bool] = {}
|
|
|
|
def fake_get(engine_id: str) -> bool:
|
|
return bool(state.get(engine_id, False))
|
|
|
|
def fake_set(engine_id: str, accepted: bool) -> None:
|
|
state[engine_id] = bool(accepted)
|
|
|
|
# Patch the canonical module so any importer (Supertonic3Backend,
|
|
# api.routers.settings, etc.) sees the fakes. Using setattr+
|
|
# monkeypatch lets pytest restore the originals between tests.
|
|
from services import settings_store as _ss
|
|
|
|
monkeypatch.setattr(_ss, "get_license_accepted", fake_get)
|
|
monkeypatch.setattr(_ss, "set_license_accepted", fake_set)
|
|
return state
|