The remote had one commit this branch did not (03f8a85), which adds the
Apache-2.0 LICENSE, the NOTICE and SPDX headers to every .py and .sh
file. The merge was clean - the headers sit at the top of each file and
my changes are further down.
The new files this branch added (languages.py, translations.py, the
cluster package, the new tests and scripts) were written after the
merge base, so the licence commit never saw them. They now carry the
same header, so the convention holds across the whole tree.
Nothing else changed. 883 tests passed / 8 failed - the same 8
pre-existing failures.
105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
# Copyright 2026 Umbra Tools (umbratools.com)
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Tests for the language registry (spec 31 section 5).
|
|
|
|
The registry is the single source of truth for what this system can speak, so
|
|
these tests pin the two things that would silently mis-voice audio: the
|
|
voiceable/gap split, and the exact strings the TTS model expects.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from umbraaudio import languages as lang
|
|
|
|
REPO = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_voiceable_count_is_ten() -> None:
|
|
assert len(lang.QWEN_LANGUAGES) == 10
|
|
assert sum(1 for x in lang.QWEN_LANGUAGES if x.tts_name) == 10
|
|
|
|
|
|
def test_gap_languages_are_not_voiceable() -> None:
|
|
assert len(lang.GAP_LANGUAGES) == 5
|
|
for gap in lang.GAP_LANGUAGES:
|
|
assert gap.tts_name is None
|
|
assert not lang.is_voiceable(gap.code)
|
|
assert lang.is_known(gap.code), "gap languages are known, just not speakable"
|
|
|
|
|
|
def test_gap_set_is_exactly_the_five_unsupported() -> None:
|
|
# spec 30 section 8 named these five; a sixth appearing here silently would
|
|
# change what the product claims to support.
|
|
assert {g.code for g in lang.GAP_LANGUAGES} == {"nl", "no", "pl", "ar", "hi"}
|
|
|
|
|
|
def test_no_duplicate_codes_or_names() -> None:
|
|
codes = [x.code for x in lang.ALL_LANGUAGES]
|
|
names = [x.name for x in lang.ALL_LANGUAGES]
|
|
assert len(codes) == len(set(codes))
|
|
assert len(names) == len(set(names))
|
|
|
|
|
|
def test_codes_are_lowercase_iso() -> None:
|
|
for x in lang.ALL_LANGUAGES:
|
|
assert re.fullmatch(r"[a-z]{2}", x.code), x
|
|
|
|
|
|
def test_normalize_accepts_code_name_and_model_string() -> None:
|
|
for x in lang.ALL_LANGUAGES:
|
|
assert lang.normalize(x.code) == x.code
|
|
assert lang.normalize(x.code.upper()) == x.code
|
|
assert lang.normalize(x.name) == x.code
|
|
assert lang.normalize(f" {x.name.lower()} ") == x.code
|
|
|
|
|
|
def test_normalize_rejects_unknown_without_raising() -> None:
|
|
# spec 31 section 8: an unrecognised language is a finding, not a crash.
|
|
for bad in (None, "", " ", "klingon", "zz"):
|
|
assert lang.normalize(bad) is None
|
|
|
|
|
|
def test_tts_name_is_the_model_string() -> None:
|
|
assert lang.tts_name("es") == "spanish"
|
|
assert lang.tts_name("Spanish") == "spanish"
|
|
assert lang.tts_name("zh") == "chinese"
|
|
assert lang.tts_name("nl") is None
|
|
|
|
|
|
def test_language_name_falls_back_gracefully() -> None:
|
|
assert lang.language_name("fr") == "French"
|
|
assert lang.language_name("xx") == "xx"
|
|
assert lang.language_name(None) == "unknown"
|
|
|
|
|
|
def test_refusal_reason_is_explicit() -> None:
|
|
assert lang.refusal_reason("en") is None
|
|
assert lang.refusal_reason(None) is None
|
|
nl = lang.refusal_reason("nl")
|
|
assert nl is not None and "'nl'" in nl and "Dutch" in nl
|
|
xx = lang.refusal_reason("xx")
|
|
assert xx is not None and "unrecognised" in xx
|
|
|
|
|
|
def test_origins_cover_the_three_sources() -> None:
|
|
assert set(lang.ORIGINS) == {"unknown", "umbratrans", "operator"}
|
|
|
|
|
|
def test_matches_the_working_reference_map() -> None:
|
|
"""The renderer that actually produces 10 languages is the authority.
|
|
|
|
If the registry and render_lang_test.py disagree, one of them is wrong and
|
|
the audio is mis-voiced. This is the test that catches it.
|
|
"""
|
|
src = (REPO / "scripts" / "render_lang_test.py").read_text(encoding="utf-8")
|
|
block = re.search(r"LANG_CODES = \{(.*?)\}", src, re.S)
|
|
assert block is not None, "LANG_CODES not found - did the renderer change?"
|
|
pairs = dict(re.findall(r'"([A-Z]{2})": "(\w+)"', block.group(1)))
|
|
assert len(pairs) == 10
|
|
for code, model_name in pairs.items():
|
|
assert lang.tts_name(code.lower()) == model_name, (code, model_name)
|