import importlib.util import sys import tempfile import unittest from pathlib import Path SCRIPT = Path(__file__).parents[1] / "scripts" / "enrich_metadata.py" def load_script(): spec = importlib.util.spec_from_file_location("tsl_enrich_metadata", SCRIPT) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module class SourceCatalogTest(unittest.TestCase): def setUp(self): self.module = load_script() self.temp_dir = tempfile.TemporaryDirectory() self.docs = Path(self.temp_dir.name) def tearDown(self): self.temp_dir.cleanup() def write_corpus(self, corpus, rows, descriptions): root = self.docs / corpus pages = root / "pages" pages.mkdir(parents=True) manifest = [ "id\ttitle\turl\tpath\tsource_bytes\tutf8_bytes\tsha256\tstatus" ] for page_id, title in rows: manifest.append( f"{page_id}\t{title}\thttp://example/{page_id}\t" f"pages/{page_id}.html\t1\t1\tsha\tok" ) description = descriptions[page_id] (pages / f"{page_id}.html").write_text( "

" + title + "

简述
" + f"
{description}
", encoding="utf-8", ) (root / "manifest.tsv").write_text( "\n".join(manifest) + "\n", encoding="utf-8" ) def test_exact_matching_preserves_underscores_and_prefers_scope_corpus(self): self.write_corpus( "tsl_base", [("1", "alpha_test")], {"1": "基础解释器说明"}, ) self.write_corpus( "net_function", [("2", "alpha_test"), ("3", "alphaTest")], {"2": "NET 说明", "3": "无下划线的另一个函数"}, ) catalog = self.module.SourceCatalog.from_docs_root(self.docs) builtin = self.module.EntryContext( name="alpha_test", signature="alpha_test()", kind="function", scope="builtin", page="builtin/demo.md", summary="原说明", ) dotnet = self.module.EntryContext( name="alpha_test", signature="alpha_test()", kind="function", scope="dotnet", page="dotnet/demo.md", summary="原说明", ) self.assertEqual("tsl_base", catalog.match(builtin)[0].corpus) self.assertEqual("net_function", catalog.match(dotnet)[0].corpus) self.assertNotIn( "alphaTest", {record.title for record in catalog.match(dotnet)} ) def test_duplicate_titles_remain_visible_for_audit(self): self.write_corpus( "net_function", [("1", "same_name"), ("2", "same_name")], {"1": "说明一", "2": "说明二"}, ) catalog = self.module.SourceCatalog.from_docs_root(self.docs) entry = self.module.EntryContext( name="same_name", signature="same_name()", kind="function", scope="dotnet", page="dotnet/demo.md", summary="原说明", ) self.assertEqual(["1", "2"], [item.page_id for item in catalog.match(entry)]) class DescriptionQualityTest(unittest.TestCase): def setUp(self): self.module = load_script() def entry(self, **overrides): values = { "name": "profitRatio", "signature": "profitRatio(report_date)", "kind": "function", "scope": "dotnet", "page": "dotnet/financial/profitability.md", "summary": "总资产收益率(%)。", } values.update(overrides) return self.module.EntryContext(**values) def test_removes_only_terminal_sentence_punctuation(self): entry = self.entry(summary="总资产收益率(%)。") self.assertEqual( "返回总资产收益率(%)", self.module.improve_description(entry, []), ) def test_preserves_formula_and_identifiers(self): entry = self.entry( summary="总资产报酬率(%)=利润总额/平均资产总额*100。" ) self.assertEqual( "计算总资产报酬率(%),公式为利润总额/平均资产总额*100", self.module.improve_description(entry, []), ) def test_rejects_source_process_prose(self): entry = self.entry(summary="返回指定值") source = self.module.SourceRecord( corpus="net_function", page_id="1", title="profitRatio", page=Path("1.html"), description="Windows 已验证通过,Linux 返回 not found", ) self.assertEqual( "返回指定值", self.module.improve_description(entry, [source]) ) def test_current_environment_can_be_real_api_semantics(self): self.assertEqual( "获取当前环境时间系统参数", self.module.safe_source_description("获取当前环境时间系统参数。"), ) def test_property_access_is_stated_explicitly(self): entry = self.entry( name="Host", signature="Host", kind="property", scope="builtin", page="builtin/object/ftp.md", summary="远程服务器地址。", access="read/write", owner="FTP", ) self.assertEqual( "获取或设置远程服务器地址", self.module.improve_description(entry, []), ) def test_boolean_property_description_is_idempotent(self): entry = self.entry( name="UseTLS", signature="UseTLS", kind="property", scope="builtin", page="builtin/object/imap.md", summary="是否采用 SSL 连接", access="read/write", owner="IMAP", ) first = self.module.improve_description(entry, []) repeated = self.module.improve_description( self.module.EntryContext( **{**entry.__dict__, "summary": first} ), [], ) self.assertEqual("控制是否采用 SSL 连接", first) self.assertEqual(first, repeated) def test_low_confidence_sentence_is_kept_except_terminal_punctuation(self): entry = self.entry( name="opaqueApi", page="dotnet.md", summary="根据调用上下文处理结果,具体规则取决于输入。", ) self.assertEqual( "根据调用上下文处理结果,具体规则取决于输入", self.module.improve_description(entry, []), ) def test_short_description_is_not_replaced_by_ambiguous_source_text(self): entry = self.entry( name="clRed", page="builtin/color.md", summary="红色", ) sources = [ self.module.SourceRecord( corpus="net_function", page_id="1", title="clRed", page=Path("1.html"), description="定义", ) ] self.assertEqual("红色", self.module.improve_description(entry, sources)) def test_existing_action_word_is_normalized_without_duplicate_verb(self): getter = self.entry( name="getCValue", page="builtin/color.md", summary="得到颜色的 CMYK 模式 C 值。", ) writer = self.entry( name="write", page="builtin/cgi.md", summary="输出字符串。", ) self.assertEqual( "获取颜色的 CMYK 模式 C 值", self.module.improve_description(getter, []), ) self.assertEqual("输出字符串", self.module.improve_description(writer, [])) def test_equation_inside_algorithm_sentence_is_not_rewritten_as_formula(self): entry = self.entry( name="se_Gauss", page="builtin/optimization.md", summary="用高斯消去法求解线性方程组 AX = B", ) self.assertEqual( "用高斯消去法求解线性方程组 AX = B", self.module.improve_description(entry, []), ) def test_builtin_class_gets_a_purpose_description(self): entry = self.entry( name="TStringList", signature="TStringList", kind="class", scope="builtin", page="builtin/object/tstringlist.md", summary="TStringList 内置对象", ) self.assertEqual( "提供字符串集合存储、查找、排序和名称值管理能力的内置对象", self.module.improve_description(entry, []), ) def test_long_property_explanation_is_not_wrapped_in_access_boilerplate(self): entry = self.entry( name="CommaTextW", signature="CommaTextW", kind="property", scope="builtin", page="builtin/object/tstringlist.md", summary=( "功能同 CommaText,区别是在读取时返回宽字节字符串," "而 CommaText 返回多字节字符串" ), access="read/write", owner="TStringList", ) self.assertEqual( entry.summary, self.module.improve_description(entry, []) ) def test_short_read_and_load_phrases_are_normalized(self): read_entry = self.entry( name="read", page="builtin/object/tstream.md", summary="读出内容", ) load_entry = self.entry( name="loadFromFile", page="builtin/object/tstringlist.md", summary="从指定的文件中装载内容", ) self.assertEqual( "读取内容", self.module.improve_description(read_entry, []) ) self.assertEqual( "从指定的文件中加载内容", self.module.improve_description(load_entry, []), ) def test_embedded_read_out_is_normalized_without_duplicate_prefix(self): entry = self.entry( name="readExcelSheets", page="dotnet.md", summary="从Excel文件中读出Sheets列表。", ) self.assertEqual( "从Excel文件中读取Sheets列表", self.module.improve_description(entry, []), ) def test_existing_decomposition_verb_is_not_prefixed(self): entry = self.entry( name="decodeGraphGroup", page="builtin/graph.md", summary="分解图形组合并写入输出参数", ) self.assertEqual( entry.summary, self.module.improve_description(entry, []) ) def test_comparison_expression_is_not_rewritten_as_formula(self): entry = self.entry( name="stockStepAmount", page="dotnet/financial/stock-capital-flow.md", summary="分档区间 V1<=Value