164 lines
6.5 KiB
Python
164 lines
6.5 KiB
Python
import importlib.util
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
_MODULE_PATH = Path(__file__).resolve().parents[1] / "funasr" / "utils" / "postprocess_hotwords.py"
|
|
_SPEC = importlib.util.spec_from_file_location("postprocess_hotwords", _MODULE_PATH)
|
|
postprocess_hotwords = importlib.util.module_from_spec(_SPEC)
|
|
sys.modules["postprocess_hotwords"] = postprocess_hotwords
|
|
assert _SPEC.loader is not None
|
|
_SPEC.loader.exec_module(postprocess_hotwords)
|
|
|
|
HotwordMatch = postprocess_hotwords.HotwordMatch
|
|
PostprocessHotwordMatcher = postprocess_hotwords.PostprocessHotwordMatcher
|
|
apply_postprocess_hotwords_to_results = postprocess_hotwords.apply_postprocess_hotwords_to_results
|
|
build_postprocess_hotword_matcher = postprocess_hotwords.build_postprocess_hotword_matcher
|
|
parse_hotword_file = postprocess_hotwords.parse_hotword_file
|
|
parse_postprocess_hotwords = postprocess_hotwords.parse_postprocess_hotwords
|
|
|
|
|
|
class TestPostprocessHotwordParsing(unittest.TestCase):
|
|
def test_parse_list_and_dict(self):
|
|
explicit, fuzzy = parse_postprocess_hotwords(["科大讯飞", "东方财富"])
|
|
self.assertEqual(explicit, {})
|
|
self.assertEqual(fuzzy, ["科大讯飞", "东方财富"])
|
|
|
|
explicit, fuzzy = parse_postprocess_hotwords({"科大迅飞": "科大讯飞", "东方财富": "东方财富"})
|
|
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
|
|
self.assertEqual(fuzzy, ["东方财富"])
|
|
|
|
def test_parse_inline_mapping(self):
|
|
explicit, fuzzy = parse_postprocess_hotwords(["撒贝你=>撒贝宁", "康辉"])
|
|
self.assertEqual(explicit, {"撒贝你": "撒贝宁"})
|
|
self.assertEqual(fuzzy, ["康辉"])
|
|
|
|
def test_parse_hotword_file(self):
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", delete=False) as f:
|
|
f.write("# comment\n")
|
|
f.write("科大讯飞\n")
|
|
f.write("科大迅飞=>科大讯飞\n")
|
|
path = f.name
|
|
|
|
try:
|
|
explicit, fuzzy = parse_hotword_file(path)
|
|
self.assertEqual(explicit, {"科大迅飞": "科大讯飞"})
|
|
self.assertEqual(fuzzy, ["科大讯飞"])
|
|
finally:
|
|
os.unlink(path)
|
|
|
|
|
|
class TestPostprocessHotwordMatcher(unittest.TestCase):
|
|
def test_explicit_replace(self):
|
|
matcher = PostprocessHotwordMatcher(
|
|
explicit_map={"撒贝你": "撒贝宁"},
|
|
enable_fuzzy=False,
|
|
)
|
|
text, matches = matcher.apply_text("我非常喜欢撒贝你说的新闻")
|
|
self.assertEqual(text, "我非常喜欢撒贝宁说的新闻")
|
|
self.assertEqual(len(matches), 1)
|
|
self.assertEqual(matches[0].replacement, "撒贝宁")
|
|
self.assertEqual(matches[0].score, 1.0)
|
|
|
|
def test_fuzzy_replace_with_optional_deps(self):
|
|
try:
|
|
import pypinyin # noqa: F401
|
|
import rapidfuzz # noqa: F401
|
|
except ImportError:
|
|
self.skipTest("pypinyin and rapidfuzz are required for fuzzy tests")
|
|
|
|
matcher = PostprocessHotwordMatcher(
|
|
fuzzy_targets=["科大讯飞"],
|
|
threshold=0.75,
|
|
)
|
|
text, matches = matcher.apply_text("科大迅飞的语音识别很强")
|
|
self.assertIn("科大讯飞", text)
|
|
self.assertTrue(matches)
|
|
self.assertEqual(matches[0].replacement, "科大讯飞")
|
|
|
|
def test_missing_fuzzy_dependency_raises(self):
|
|
with mock.patch.object(
|
|
postprocess_hotwords,
|
|
"_require_pypinyin",
|
|
side_effect=ImportError("missing pypinyin"),
|
|
):
|
|
with self.assertRaises(ImportError):
|
|
PostprocessHotwordMatcher(
|
|
fuzzy_targets=["科大讯飞"],
|
|
threshold=0.85,
|
|
)
|
|
|
|
def test_invalid_threshold_raises(self):
|
|
with self.assertRaises(ValueError):
|
|
PostprocessHotwordMatcher(explicit_map={"a": "b"}, threshold=1.5)
|
|
|
|
def test_sentence_info_and_timestamp_preserved(self):
|
|
matcher = PostprocessHotwordMatcher(
|
|
explicit_map={"撒贝你": "撒贝宁"},
|
|
enable_fuzzy=False,
|
|
)
|
|
result = {
|
|
"text": "撒贝你主持节目",
|
|
"timestamp": [[0, 100], [100, 200]],
|
|
"sentence_info": [
|
|
{"text": "撒贝你主持", "sentence": "撒贝你主持", "start": 0, "end": 1000},
|
|
{"text": "节目", "sentence": "节目", "start": 1000, "end": 1500},
|
|
],
|
|
}
|
|
matcher.apply_result(result, return_matches=True)
|
|
self.assertEqual(result["text"], "撒贝宁主持节目")
|
|
self.assertEqual(result["sentence_info"][0]["text"], "撒贝宁主持")
|
|
self.assertEqual(result["sentence_info"][0]["sentence"], "撒贝宁主持")
|
|
self.assertEqual(result["timestamp"], [[0, 100], [100, 200]])
|
|
self.assertEqual(result["postprocess_hotword_matches"][0]["replacement"], "撒贝宁")
|
|
|
|
def test_matcher_reused_for_multiple_results(self):
|
|
build_calls = {"count": 0}
|
|
original_build = build_postprocess_hotword_matcher
|
|
|
|
def counting_build(*args, **kwargs):
|
|
build_calls["count"] += 1
|
|
return original_build(*args, **kwargs)
|
|
|
|
results = [
|
|
{"text": "撒贝你主持", "timestamp": [1]},
|
|
{"text": "康灰播报", "timestamp": [2]},
|
|
]
|
|
cfg = {
|
|
"postprocess_hotwords": {"撒贝你": "撒贝宁", "康灰": "康辉"},
|
|
"return_postprocess_hotword_matches": True,
|
|
}
|
|
with mock.patch.object(
|
|
postprocess_hotwords,
|
|
"build_postprocess_hotword_matcher",
|
|
side_effect=counting_build,
|
|
):
|
|
updated = apply_postprocess_hotwords_to_results(results, cfg)
|
|
|
|
self.assertEqual(build_calls["count"], 1)
|
|
self.assertEqual(updated[0]["text"], "撒贝宁主持")
|
|
self.assertEqual(updated[1]["text"], "康辉播报")
|
|
self.assertEqual(len(updated[0]["postprocess_hotword_matches"]), 1)
|
|
|
|
def test_noop_when_not_configured(self):
|
|
results = [{"text": "不变", "timestamp": [1]}]
|
|
updated = apply_postprocess_hotwords_to_results(results, {})
|
|
self.assertIs(updated, results)
|
|
self.assertEqual(updated[0]["text"], "不变")
|
|
|
|
|
|
class TestHotwordMatch(unittest.TestCase):
|
|
def test_as_dict(self):
|
|
match = HotwordMatch("a", "b", 0.9, 1, 2)
|
|
self.assertEqual(
|
|
match.as_dict(),
|
|
{"original": "a", "replacement": "b", "score": 0.9, "start": 1, "end": 2},
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|