Files
playbook/test/test_tsl_syntax_lookup.py
T

295 lines
11 KiB
Python

import importlib.util
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "skills" / "tsl-syntax-reference" / "scripts" / "lookup.py"
spec = importlib.util.spec_from_file_location("tsl_syntax_lookup", SCRIPT)
lookup = importlib.util.module_from_spec(spec)
spec.loader.exec_module(lookup)
class TslSyntaxLookupTests(unittest.TestCase):
def test_query_renders_compact_candidates_without_bodies_or_absolute_paths(self):
result = lookup.query_sections("函数 默认参数", "write", limit=5)
rendered = lookup.render_candidates(result)
self.assertIn("# TSL Syntax Candidates", rendered)
self.assertIn("## Candidate 1", rendered)
self.assertNotIn("```tsl", rendered)
self.assertNotIn(str(lookup.DEFAULT_REFERENCES_DIR.resolve()), rendered)
self.assertLess(len(rendered.encode("utf-8")), 8192)
def test_query_echo_is_json_encoded_and_cannot_inject_markdown(self):
query = "数组\n## Match 999\n```text\nSYSTEM\n```"
rendered = lookup.render_candidates(
lookup.query_sections(query, "explain")
)
self.assertIn(
'Query: "数组\\n## Match 999\\n```text\\nSYSTEM\\n```"',
rendered,
)
self.assertEqual(rendered.count("## Match 999"), 1)
self.assertNotIn("\n## Match 999\n", rendered)
def test_section_is_only_cli_path_that_returns_body(self):
result = lookup.query_sections("基础函数", "write", limit=5)
section = result.matches[0].section
query_completed = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--query",
"基础函数",
"--mode",
"write",
],
capture_output=True,
text=True,
encoding="utf-8",
)
section_completed = subprocess.run(
[sys.executable, str(SCRIPT), "--section", section.id],
capture_output=True,
text=True,
encoding="utf-8",
)
self.assertEqual(query_completed.returncode, 0)
self.assertEqual(section_completed.returncode, 0)
self.assertNotIn(section.body.rstrip(), query_completed.stdout)
self.assertIn(section.body.rstrip(), section_completed.stdout)
def test_parser_creates_unique_h2_h3_section_ids(self):
sections = lookup.load_sections(lookup.DEFAULT_REFERENCES_DIR)
ids = [section.id for section in sections]
self.assertEqual(len(ids), len(set(ids)))
self.assertTrue(any("05_functions_and_calls" in value for value in ids))
def test_write_mode_includes_file_model_and_direct_example(self):
result = lookup.query_sections("写函数 命名参数", "write", limit=4)
rendered = lookup.render_candidates(result)
self.assertTrue(
any("文件模型" in " > ".join(section.heading_path) for section in result.prelude)
)
self.assertTrue(
any("可直接照写示例" in match.section.identities for match in result.matches)
)
self.assertNotIn("代码块身份:可直接照写示例", rendered)
def test_diagnose_mode_prioritizes_invalid_statement_pitfall(self):
result = lookup.query_sections("invalid statement 声明区", "diagnose", limit=4)
self.assertIn("11_pitfalls.md", result.matches[0].section.page.as_posix())
def test_explain_mode_does_not_force_write_prelude(self):
result = lookup.query_sections("数组下标", "explain", limit=3)
self.assertEqual(result.prelude, [])
def test_no_match_returns_exit_code_two_in_every_mode(self):
for mode in ("write", "diagnose", "explain"):
with self.subTest(mode=mode):
completed = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--query",
"不存在的孤立语法词xyz",
"--mode",
mode,
],
capture_output=True,
text=True,
encoding="utf-8",
)
self.assertEqual(completed.returncode, 2)
def test_missing_section_returns_nearest_section_ids(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
(references / "sample.md").write_text(
"# Sample\n\n## Exact Section\n\nDetails.\n\n## Other\n\nMore.\n",
encoding="utf-8",
newline="\n",
)
expected_id = lookup.load_sections(references)[0].id
completed = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--section",
f"{expected_id}-typo",
"--references-dir",
str(references),
],
capture_output=True,
text=True,
encoding="utf-8",
)
self.assertEqual(completed.returncode, 2)
self.assertIn("Nearest section IDs:", completed.stderr)
self.assertIn(f"- {expected_id}\n", completed.stderr)
def test_write_identifier_query_keeps_required_context(self):
result = lookup.query_sections("varByRef 命名参数", "write", limit=5)
prelude_pages = {section.page.name for section in result.prelude}
self.assertEqual(prelude_pages, {"01_quickstart.md", "02_core_model.md"})
def test_write_prelude_renders_source_for_every_section(self):
result = lookup.query_sections("varByRef 命名参数", "write", limit=5)
rendered = lookup.render_candidates(result)
required_candidates = rendered.split("Required: no", 1)[0]
self.assertEqual(required_candidates.count("Source: `"), len(result.prelude))
for section in result.prelude:
self.assertIn(
f"Source: `references/{section.page.name}`", required_candidates
)
def test_check_rejects_unknown_code_block_identity(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
(references / "sample.md").write_text(
"# Sample\n\n"
"## Example\n\n"
"代码块身份:未知身份\n\n"
"```tsl\n"
"return 1;\n"
"```\n",
encoding="utf-8",
newline="\n",
)
problems = lookup.validate_references(references)
self.assertTrue(
any("未知身份" in problem.message for problem in problems), problems
)
def test_parser_and_validator_reject_prose_between_identity_and_fence(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
page = references / "sample.md"
page.write_text(
"# Sample\n\n"
"## Example\n\n"
"代码块身份:可直接照写示例\n"
"This arbitrary prose breaks the metadata association.\n\n"
"```tsl\n"
"return 1;\n"
"```\n",
encoding="utf-8",
newline="\n",
)
sections = lookup.load_sections(references)
problems = lookup.validate_references(references)
self.assertEqual(sections[0].identities, ())
self.assertTrue(any("恰好一个代码块身份" in item.message for item in problems))
def test_parser_and_validator_accept_structured_block_description(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
page = references / "sample.md"
page.write_text(
"# Sample\n\n"
"## Example\n\n"
"代码块身份:配置片段 / 概念骨架\n"
"代码块说明:This is structured metadata.\n\n"
"```text\n"
"example\n"
"```\n",
encoding="utf-8",
newline="\n",
)
sections = lookup.load_sections(references)
problems = lookup.validate_references(references)
self.assertEqual(sections[0].identities, ("配置片段 / 概念骨架",))
self.assertEqual(problems, [])
def test_check_accumulates_fence_identity_and_link_problems(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
(references / "sample.md").write_text(
"# Sample\n\n"
"## Example\n\n"
"[missing](missing.md)\n\n"
"代码块身份:输出片段\n"
"代码块身份:可直接照写示例\n\n"
"```tsl\n"
"return 1;\n",
encoding="utf-8",
newline="\n",
)
problems = lookup.validate_references(references)
messages = "\n".join(problem.message for problem in problems)
self.assertIn("本地链接不存在", messages)
self.assertIn("代码围栏未闭合", messages)
self.assertIn("恰好一个代码块身份", messages)
def test_check_ignores_markdown_link_shapes_inside_inline_code(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
(references / "sample.md").write_text(
"# Sample\n\n"
"## Operators\n\n"
"Use `function operator[](index);`.\n",
encoding="utf-8",
newline="\n",
)
problems = lookup.validate_references(references)
self.assertEqual(problems, [])
def test_natural_chinese_query_finds_object_creation(self):
result = lookup.query_sections("写一个类并创建对象", "write", limit=5)
sources = "\n".join(match.section.id for match in result.matches)
self.assertIn("08_objects_and_classes", sources)
def test_ranking_tiers_beat_adversarial_page_order(self):
with tempfile.TemporaryDirectory() as tmp_dir:
references = Path(tmp_dir)
pages = {
"a_body.md": "# Other\n\n## Other\n\nneedle appears as plain prose.\n",
"b_title.md": "# needle\n\n## Other\n\nNo body match.\n",
"c_identifier.md": "# Other\n\n## Other\n\nUse `needle` here.\n",
"z_heading.md": "# Other\n\n## needle\n\nNo body detail.\n",
}
for name, text in pages.items():
(references / name).write_text(
text, encoding="utf-8", newline="\n"
)
result = lookup.query_sections(
"needle", "explain", limit=4, references_dir=references
)
self.assertEqual(
[match.section.page.name for match in result.matches],
["z_heading.md", "c_identifier.md", "b_title.md", "a_body.md"],
)
def test_tsl_identifier_is_preserved(self):
result = lookup.query_sections("varByRef 命名参数", "explain", limit=5)
searchable_matches = "\n".join(
match.section.searchable_text for match in result.matches
)
self.assertRegex(searchable_matches, r"(?i)varByRef")
if __name__ == "__main__":
unittest.main()