"""SEARCH 分支路径生成 + 二元 COMPUTE 链反演(Phase 3 — TDD RED) 目标:KYU05DED SEARCH WHEN 分支(#9)从不可达变为可达, 配合二元 `-` COMPUTE(WRK-TAXABLE-INCOME = WRK-GROSS-PAYMENT - CNS-TAX-BASIC-DEDUCTION) 反演到输入 R01GROSS-PAYMENT。 """ import sys, os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) from cobol_testgen.design_mcdc import enum_paths from cobol_testgen.core import trace_to_root, invert_through_chain from cobol_testgen.coverage import collect_decision_points, mark_coverage from cobol_testgen.models import BrSearch, BrSeq, CondLeaf, CondAnd def _make_search_node(): node = BrSearch("IW-ENTRY") node.when_list = [("A <= SUBJ", BrSeq())] node.cond_trees = [CondLeaf("A", "<=", "SUBJ")] node.has_at_end = True node.at_end_seq = BrSeq() return node FIELDS = [ {"name": "A(1)", "pic_info": {"type": "numeric", "digits": 9}}, {"name": "SUBJ", "pic_info": {"type": "numeric", "digits": 9}}, ] # ── 1. enum_paths 生成 SEARCH WHEN 路径 ── def test_enum_paths_search_marks_when_branch(): """enum_paths 对 BrSearch 应生成 WHEN 路径,使 _mark_search 标记 WHEN 分支""" node = _make_search_node() paths = enum_paths(node, FIELDS) assert len(paths) >= 1 dps, _ = collect_decision_points(node, FIELDS, [0]) mark_coverage(dps, [], paths, FIELDS) dp = dps[0] assert dp.kind == "SEARCH" assert "WHEN A <= SUBJ" in dp.active_branches def test_enum_paths_search_marks_at_end(): """enum_paths 应生成 AT END 路径(带不匹配 WHEN 叶的约束)""" node = _make_search_node() paths = enum_paths(node, FIELDS) dps, _ = collect_decision_points(node, FIELDS, [0]) mark_coverage(dps, [], paths, FIELDS) dp = dps[0] assert "AT END" in dp.active_branches # ── 2. trace_to_root / invert_through_chain 二元 `-` 反演 ── def test_trace_minus_constant_field_to_input(): """2 源 `-` COMPUTE:SUBJ = GROSS - DED(DED 常量 VALUE)→ 追溯到 INPUT-GROSS""" assignments = { "SUBJ": [{"type": "compute", "source_vars": ["GROSS", "DED"], "op": "-", "expr": "GROSS - DED"}], "GROSS": [{"type": "move", "source_vars": ["INPUT-GROSS"]}], } fields = [ {"name": "DED", "pic_info": {"type": "numeric", "digits": 6}, "value": "50000"}, {"name": "INPUT-GROSS", "pic_info": {"type": "numeric", "digits": 9}}, ] root, chain = trace_to_root("SUBJ", assignments, fields) assert root == "INPUT-GROSS" # 2 源 `-` COMPUTE 常量侧被折叠为单源 compute + const assert any(a.get("const") == float("50000") for _, a in chain) def test_invert_minus_constant_field_boundary(): """SUBJ >= 4701 → GROSS - 50000 >= 4701 → INPUT-GROSS >= 54701""" assignments = { "SUBJ": [{"type": "compute", "source_vars": ["GROSS", "DED"], "op": "-", "expr": "GROSS - DED"}], "GROSS": [{"type": "move", "source_vars": ["INPUT-GROSS"]}], } fields = [ {"name": "DED", "pic_info": {"type": "numeric", "digits": 6}, "value": "50000"}, {"name": "INPUT-GROSS", "pic_info": {"type": "numeric", "digits": 9}}, ] root, chain = trace_to_root("SUBJ", assignments, fields) new_root, op, val = invert_through_chain(root, chain, ">=", "4701") assert new_root == "INPUT-GROSS" assert op == ">=" assert float(val) >= 54701 # ── 3. _mark_search 单 CondLeaf 字段对字段(应已绿,防止回归)── def test_mark_search_field_to_field_constraint(): """SUBJ <= A(1) 形态约束命中 WHEN 叶(value 侧基准字段匹配)""" from cobol_testgen.coverage import DecisionPoint dp = DecisionPoint(id=1, kind="SEARCH", label="IW-ENTRY", branch_names=["WHEN A <= SUBJ", "AT END"]) dp.when_list = [("A <= SUBJ", BrSeq())] dp.cond_trees = [CondLeaf("A", "<=", "SUBJ")] dp.has_other = True branch_paths = [ ([("SUBJ", "<=", "A(1)", True)], []), ] mark_coverage([dp], [], branch_paths, FIELDS) assert "WHEN A <= SUBJ" in dp.active_branches assert "AT END" not in dp.active_branches