# -*- coding: utf-8 -*- """ 文字点选求解器回归测试 (真实线上样本) fixtures 里的 6 张图全部来自真实线上环境: * har_round1..4 —— 用户 `点选.har` 抓包中的 4 道题 (672x480 背景图) * live_probe —— 本次逆向期间实时抓取的一道题 * live_hard_rare_char —— 含生僻字 "筹" 且整题带斜切变形, 用于验证 ddddocr+PP-OCRv4 集成 运行: uv run python -m tcaptcha.tests.test_glyph_solver """ from __future__ import annotations import json import logging import sys from pathlib import Path FIXTURES = Path(__file__).resolve().parent / "fixtures" MANIFEST = FIXTURES / "manifest.json" ROOT = Path(__file__).resolve().parent.parent.parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from tcaptcha.solvers.text_click import TextClickSolver, parse_instruction # noqa: E402 TOLERANCE = 22 # 像素容差 (汉字包围盒中心 vs 人工标注中心) def run(verbose: bool = False) -> int: logging.basicConfig(level=logging.WARNING) cases = json.loads(MANIFEST.read_text(encoding="utf-8")) solver = TextClickSolver() passed = 0 failed = [] for case in cases: img = (FIXTURES / case["image"]).read_bytes() result = solver.solve_ex(img, case["instruction"]) ok_points = result.ok and all( abs(p[0] - e[0]) <= TOLERANCE and abs(p[1] - e[1]) <= TOLERANCE for p, e in zip(result.points, case["expected_points"]) ) if verbose: print(f" {case['image']:24s} {case['instruction']}") print(f" pred={result.points}") print(f" exp ={case['expected_points']} post={result.posterior:.4f} " f"{'OK' if ok_points else 'MISMATCH'}") if ok_points: passed += 1 else: failed.append((case["image"], result.points, case["expected_points"])) total = len(cases) print(f"\n文字点选求解器回归: {passed}/{total} 通过") for image, pred, exp in failed: print(f" ✗ {image}: pred={pred} exp={exp}") return 0 if passed == total else 1 def test_parse_instruction(): """提示语解析的边界用例 (图标点选/滑块的模板串不能解析出目标字)""" assert parse_instruction("请依次点击:虫 呈 般") == ["虫", "呈", "般"] assert parse_instruction("请依次点击:虫呈般") == ["虫", "呈", "般"] assert parse_instruction("请依次点击:") == [] # 图标点选 assert parse_instruction("拖动下方滑块完成拼图") == [] # 滑块 if __name__ == "__main__": import argparse ap = argparse.ArgumentParser() ap.add_argument("-v", "--verbose", action="store_true") args = ap.parse_args() test_parse_instruction() print("提示语解析边界用例: OK") sys.exit(run(verbose=args.verbose))