109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
"""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
|