mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
- Extract compile_pronunciation_rules, compile_heteronym_sentence_rules - Extract apply_pronunciation_rules, apply_heteronym_sentence_rules - Extract merge_pronunciation_overrides - Add tests/test_pronunciation.py (31 tests) - All tests pass
335 lines
12 KiB
Python
335 lines
12 KiB
Python
"""Tests for abogen.domain.pronunciation — compile/apply pronunciation rules."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import pytest
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# We import the domain functions. The module must be created first.
|
|
# For now the tests are written against the expected public API so they can
|
|
# serve as the contract during extraction.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCompilePronunciationRules:
|
|
"""compile_pronunciation_rules turns override dicts into regex-based rules."""
|
|
|
|
def test_empty_input(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
assert compile_pronunciation_rules(None) == []
|
|
assert compile_pronunciation_rules([]) == []
|
|
|
|
def test_single_entry(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [{"token": "albeit", "pronunciation": "all be it"}]
|
|
rules = compile_pronunciation_rules(overrides)
|
|
assert len(rules) == 1
|
|
assert rules[0]["replacement"] == "all be it"
|
|
assert rules[0]["pattern"].search("albeit")
|
|
|
|
def test_skips_entries_without_pronunciation(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [{"token": "hello"}]
|
|
assert compile_pronunciation_rules(overrides) == []
|
|
|
|
def test_skips_entries_without_token(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [{"pronunciation": "foo"}]
|
|
assert compile_pronunciation_rules(overrides) == []
|
|
|
|
def test_deduplication_by_casefold(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [
|
|
{"token": "Albeit", "pronunciation": "all be it"},
|
|
{"token": "ALBEIT", "pronunciation": "all be it"},
|
|
]
|
|
rules = compile_pronunciation_rules(overrides)
|
|
assert len(rules) == 1
|
|
|
|
def test_longer_token_sorted_first(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [
|
|
{"token": "ice cream", "pronunciation": "ice cream"},
|
|
{"token": "ice", "pronunciation": "ais"},
|
|
]
|
|
rules = compile_pronunciation_rules(overrides)
|
|
assert len(rules) == 2
|
|
assert len(rules[0]["token"]) >= len(rules[1]["token"])
|
|
|
|
def test_normalized_fallback_to_entity_token(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [{"normalized": "USA", "pronunciation": "you ess ay"}]
|
|
rules = compile_pronunciation_rules(overrides)
|
|
assert len(rules) == 1
|
|
|
|
def test_pattern_is_case_insensitive(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = [{"token": "hello", "pronunciation": "hi"}]
|
|
rules = compile_pronunciation_rules(overrides)
|
|
assert rules[0]["pattern"].search("Hello")
|
|
assert rules[0]["pattern"].search("HELLO")
|
|
|
|
def test_non_mapping_items_skipped(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules
|
|
|
|
overrides = ["bad", None, 42]
|
|
assert compile_pronunciation_rules(overrides) == []
|
|
|
|
|
|
class TestCompileHeteronymSentenceRules:
|
|
"""compile_heteronym_sentence_rules builds sentence-level replacements."""
|
|
|
|
def test_empty_input(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
assert compile_heteronym_sentence_rules(None) == []
|
|
assert compile_heteronym_sentence_rules([]) == []
|
|
|
|
def test_basic_replacement(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
overrides = [
|
|
{
|
|
"sentence": "I read the book",
|
|
"choice": "past",
|
|
"options": [
|
|
{"key": "present", "replacement_sentence": "I read the book"},
|
|
{"key": "past", "replacement_sentence": "I read the book"},
|
|
],
|
|
}
|
|
]
|
|
rules = compile_heteronym_sentence_rules(overrides)
|
|
assert len(rules) == 1
|
|
assert rules[0]["replacement"] == "I read the book"
|
|
|
|
def test_skips_without_sentence(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
overrides = [{"choice": "a", "options": []}]
|
|
assert compile_heteronym_sentence_rules(overrides) == []
|
|
|
|
def test_skips_without_choice(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
overrides = [{"sentence": "hello", "options": []}]
|
|
assert compile_heteronym_sentence_rules(overrides) == []
|
|
|
|
def test_skips_when_no_matching_option(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
overrides = [
|
|
{
|
|
"sentence": "I read the book",
|
|
"choice": "past",
|
|
"options": [{"key": "present", "replacement_sentence": "I read the book"}],
|
|
}
|
|
]
|
|
assert compile_heteronym_sentence_rules(overrides) == []
|
|
|
|
def test_deduplication(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
entry = {
|
|
"sentence": "I read the book",
|
|
"choice": "past",
|
|
"options": [{"key": "past", "replacement_sentence": "I red the book"}],
|
|
}
|
|
rules = compile_heteronym_sentence_rules([entry, entry])
|
|
assert len(rules) == 1
|
|
|
|
def test_longer_sentence_sorted_first(self):
|
|
from abogen.domain.pronunciation import compile_heteronym_sentence_rules
|
|
|
|
overrides = [
|
|
{
|
|
"sentence": "short",
|
|
"choice": "a",
|
|
"options": [{"key": "a", "replacement_sentence": "s"}],
|
|
},
|
|
{
|
|
"sentence": "a longer sentence here",
|
|
"choice": "b",
|
|
"options": [{"key": "b", "replacement_sentence": "l"}],
|
|
},
|
|
]
|
|
rules = compile_heteronym_sentence_rules(overrides)
|
|
assert len(rules[0]["pattern"].pattern) >= len(rules[1]["pattern"].pattern)
|
|
|
|
|
|
class TestApplyPronunciationRules:
|
|
"""apply_pronunciation_rules applies compiled token-level rules."""
|
|
|
|
def test_empty_text(self):
|
|
from abogen.domain.pronunciation import apply_pronunciation_rules
|
|
|
|
assert apply_pronunciation_rules("", []) == ""
|
|
|
|
def test_no_rules(self):
|
|
from abogen.domain.pronunciation import apply_pronunciation_rules
|
|
|
|
assert apply_pronunciation_rules("hello", []) == "hello"
|
|
|
|
def test_basic_replacement(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules, apply_pronunciation_rules
|
|
|
|
rules = compile_pronunciation_rules([{"token": "albeit", "pronunciation": "all be it"}])
|
|
result = apply_pronunciation_rules("albeit it was raining", rules)
|
|
assert result == "all be it it was raining"
|
|
|
|
def test_possessive_preserved(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules, apply_pronunciation_rules
|
|
|
|
rules = compile_pronunciation_rules([{"token": "dog", "pronunciation": "dawg"}])
|
|
result = apply_pronunciation_rules("the dog's bone", rules)
|
|
assert result == "the dawg's bone"
|
|
|
|
def test_usage_counter_increments(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules, apply_pronunciation_rules
|
|
|
|
rules = compile_pronunciation_rules([{"token": "hello", "pronunciation": "hi"}])
|
|
counter: dict[str, int] = {}
|
|
apply_pronunciation_rules("hello hello", rules, usage_counter=counter)
|
|
assert counter.get("hello", 0) == 2
|
|
|
|
def test_case_insensitive_match(self):
|
|
from abogen.domain.pronunciation import compile_pronunciation_rules, apply_pronunciation_rules
|
|
|
|
rules = compile_pronunciation_rules([{"token": "test", "pronunciation": "tst"}])
|
|
result = apply_pronunciation_rules("This is a Test", rules)
|
|
assert "tst" in result.lower()
|
|
|
|
|
|
class TestApplyHeteronymSentenceRules:
|
|
"""apply_heteronym_sentence_rules applies sentence-level replacements."""
|
|
|
|
def test_empty_text(self):
|
|
from abogen.domain.pronunciation import apply_heteronym_sentence_rules
|
|
|
|
assert apply_heteronym_sentence_rules("", []) == ""
|
|
|
|
def test_no_rules(self):
|
|
from abogen.domain.pronunciation import apply_heteronym_sentence_rules
|
|
|
|
assert apply_heteronym_sentence_rules("hello", []) == "hello"
|
|
|
|
def test_basic_replacement(self):
|
|
from abogen.domain.pronunciation import (
|
|
compile_heteronym_sentence_rules,
|
|
apply_heteronym_sentence_rules,
|
|
)
|
|
|
|
rules = compile_heteronym_sentence_rules(
|
|
[
|
|
{
|
|
"sentence": "I read the book",
|
|
"choice": "past",
|
|
"options": [{"key": "past", "replacement_sentence": "I read the book"}],
|
|
}
|
|
]
|
|
)
|
|
result = apply_heteronym_sentence_rules("I read the book.", rules)
|
|
assert result == "I read the book."
|
|
|
|
def test_no_match_left_unchanged(self):
|
|
from abogen.domain.pronunciation import (
|
|
compile_heteronym_sentence_rules,
|
|
apply_heteronym_sentence_rules,
|
|
)
|
|
|
|
rules = compile_heteronym_sentence_rules(
|
|
[
|
|
{
|
|
"sentence": "I read the book",
|
|
"choice": "past",
|
|
"options": [{"key": "past", "replacement_sentence": "I red the book"}],
|
|
}
|
|
]
|
|
)
|
|
result = apply_heteronym_sentence_rules("something else entirely", rules)
|
|
assert result == "something else entirely"
|
|
|
|
|
|
class TestMergePronunciationOverrides:
|
|
"""merge_pronunciation_overrides consolidates override sources."""
|
|
|
|
def test_empty_job(self):
|
|
from abogen.domain.pronunciation import merge_pronunciation_overrides
|
|
|
|
class FakeJob:
|
|
pronunciation_overrides = None
|
|
speakers = None
|
|
manual_overrides = None
|
|
language = "en"
|
|
|
|
result = merge_pronunciation_overrides(FakeJob())
|
|
assert result == []
|
|
|
|
def test_pronunciation_overrides_included(self):
|
|
from abogen.domain.pronunciation import merge_pronunciation_overrides
|
|
|
|
class FakeJob:
|
|
pronunciation_overrides = [
|
|
{"token": "hello", "pronunciation": "hi", "normalized": "hello"}
|
|
]
|
|
speakers = None
|
|
manual_overrides = None
|
|
language = "en"
|
|
|
|
result = merge_pronunciation_overrides(FakeJob())
|
|
assert len(result) == 1
|
|
assert result[0]["token"] == "hello"
|
|
assert result[0]["source"] == "pronunciation"
|
|
|
|
def test_manual_overrides_win(self):
|
|
from abogen.domain.pronunciation import merge_pronunciation_overrides
|
|
|
|
class FakeJob:
|
|
pronunciation_overrides = [
|
|
{"token": "hello", "pronunciation": "hi old", "normalized": "hello"}
|
|
]
|
|
speakers = None
|
|
manual_overrides = [
|
|
{"token": "hello", "pronunciation": "hi new", "normalized": "hello"}
|
|
]
|
|
language = "en"
|
|
|
|
result = merge_pronunciation_overrides(FakeJob())
|
|
assert len(result) == 1
|
|
assert result[0]["pronunciation"] == "hi new"
|
|
assert result[0]["source"] == "manual"
|
|
|
|
def test_speaker_entries_included(self):
|
|
from abogen.domain.pronunciation import merge_pronunciation_overrides
|
|
|
|
class FakeJob:
|
|
pronunciation_overrides = None
|
|
speakers = {"narrator": {"token": "war", "pronunciation": "wɔːr"}}
|
|
manual_overrides = None
|
|
language = "en"
|
|
|
|
result = merge_pronunciation_overrides(FakeJob())
|
|
assert len(result) == 1
|
|
assert result[0]["source"] == "speaker"
|
|
|
|
def test_skips_empty_tokens(self):
|
|
from abogen.domain.pronunciation import merge_pronunciation_overrides
|
|
|
|
class FakeJob:
|
|
pronunciation_overrides = [{"token": "", "pronunciation": "foo"}]
|
|
speakers = None
|
|
manual_overrides = None
|
|
language = "en"
|
|
|
|
result = merge_pronunciation_overrides(FakeJob())
|
|
assert result == []
|