bc1d56d1a4
P0.6: gcov infrastructure P1: extract_structure output expansion (11 new feature fields) P2: Confusion group rule engine (8 pairs + contradiction + backtrack) P3: 4-factor confidence calculation + quality gate update P4: 33+2 COBOL program type test samples (22 files, 7 categories) P5: parametrized/ test data generation engine P6: japanese_data.py lookup tables P7-10: Type-specific test suites (~159 parametrized tests) P11: Full classification pipeline (classify_program) + orchestrator integration P12: Documentation (module-interfaces, test-plan v3.0, coverage-matrix) Architecture decisions: - classification_pipeline/ merged to hina/pipeline/ - parametrized/ as independent module - japanese_data.py as root-level file - hina/__all__ only exports classify_program() Co-Authored-By: Claude <noreply@anthropic.com>
242 lines
6.9 KiB
Python
242 lines
6.9 KiB
Python
"""CO-01~10: cobol_testgen cond 模块 — 条件表达式解析 + MC/DC"""
|
|
|
|
import sys, os
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
from cobol_testgen.cond import (
|
|
parse_single_condition, parse_compound_condition,
|
|
collect_leaves, evaluate_tree, mcdc_sets, is_field,
|
|
)
|
|
from cobol_testgen.models import CondLeaf, CondAnd, CondOr, CondNot
|
|
|
|
|
|
# ── CO-01~02: parse_single_condition ──
|
|
|
|
def test_parse_single_numeric():
|
|
"""CO-01: 数值比较 AMOUNT > 100"""
|
|
r = parse_single_condition("AMOUNT > 100")
|
|
assert r is not None
|
|
assert r[0] == "AMOUNT"
|
|
assert r[1] == ">"
|
|
assert r[2] == "100"
|
|
|
|
|
|
def test_parse_single_string():
|
|
"""CO-02: 文字列比较 B = 'Y'"""
|
|
r = parse_single_condition("B = 'Y'")
|
|
assert r is not None
|
|
assert r[0] == "B"
|
|
assert r[1] == "="
|
|
assert r[2] == "Y"
|
|
|
|
|
|
def test_parse_single_subscript():
|
|
"""带下标的字段 WS-ITEM(SUB) = 'A'"""
|
|
r = parse_single_condition("WS-ITEM(SUB) = 'A'")
|
|
assert r is not None
|
|
assert r[2] == "A"
|
|
|
|
|
|
def test_parse_single_88_level():
|
|
"""88-level 条件名分解"""
|
|
fields = [{"is_88": True, "name": "STATUS-APPROVED", "parent": "WS-TRAN-STATUS", "value": "A"}]
|
|
r = parse_single_condition("STATUS-APPROVED", fields)
|
|
assert r is not None
|
|
assert r[0] == "WS-TRAN-STATUS"
|
|
assert r[2] == "A"
|
|
|
|
|
|
def test_parse_single_compound_returns_none():
|
|
"""包含 AND/OR 返回 None"""
|
|
assert parse_single_condition("A > 0 AND B < 5") is None
|
|
|
|
|
|
def test_parse_single_unknown_returns_none():
|
|
"""无法解析的表达式返回 None"""
|
|
assert parse_single_condition("NOT A") is None
|
|
|
|
|
|
# ── CO-03~05: parse_compound_condition ──
|
|
|
|
def test_compound_and():
|
|
"""CO-03: A > 0 AND B < 5 → CondAnd"""
|
|
r = parse_compound_condition("A > 0 AND B < 5")
|
|
assert r is not None
|
|
assert isinstance(r, CondAnd)
|
|
assert isinstance(r.left, CondLeaf)
|
|
assert isinstance(r.right, CondLeaf)
|
|
|
|
|
|
def test_compound_or():
|
|
"""CO-04: A = 1 OR B = 2 → CondOr"""
|
|
r = parse_compound_condition("A = 1 OR B = 2")
|
|
assert r is not None
|
|
assert isinstance(r, CondOr)
|
|
assert isinstance(r.left, CondLeaf)
|
|
assert isinstance(r.right, CondLeaf)
|
|
|
|
|
|
def test_compound_nested_and_or():
|
|
"""CO-05: (A > 0 AND B < 5) OR C = 1 → AND优先于OR"""
|
|
r = parse_compound_condition("(A > 0 AND B < 5) OR C = 1")
|
|
assert r is not None
|
|
assert isinstance(r, CondOr)
|
|
assert isinstance(r.left, CondAnd)
|
|
assert isinstance(r.right, CondLeaf)
|
|
|
|
|
|
def test_compound_not():
|
|
"""NOT 前缀"""
|
|
r = parse_compound_condition("NOT A = 1")
|
|
assert r is not None
|
|
assert isinstance(r, CondNot)
|
|
assert isinstance(r.child, CondLeaf)
|
|
|
|
|
|
def test_compound_empty():
|
|
"""空字符串返回 None"""
|
|
assert parse_compound_condition("") is None
|
|
|
|
|
|
def test_compound_paren_wrap():
|
|
"""外层括号剥离"""
|
|
r = parse_compound_condition("(A > 0)")
|
|
assert isinstance(r, CondLeaf)
|
|
|
|
|
|
# ── collect_leaves ──
|
|
|
|
def test_collect_leaves_and():
|
|
"""AND 树收集所有叶子"""
|
|
tree = CondAnd(CondLeaf("A", ">", "0"), CondLeaf("B", "<", "5"))
|
|
leaves = collect_leaves(tree)
|
|
assert len(leaves) == 2
|
|
|
|
|
|
def test_collect_leaves_not():
|
|
"""NOT 树收集子叶子"""
|
|
tree = CondNot(CondLeaf("A", "=", "1"))
|
|
leaves = collect_leaves(tree)
|
|
assert len(leaves) == 1
|
|
|
|
|
|
# ── evaluate_tree ──
|
|
|
|
def test_evaluate_leaf_true():
|
|
"""叶子节点求值"""
|
|
leaf = CondLeaf("A", ">", "0")
|
|
assert evaluate_tree(leaf, {leaf: True}) is True
|
|
assert evaluate_tree(leaf, {leaf: False}) is False
|
|
|
|
|
|
def test_evaluate_and_true():
|
|
"""AND 全部 True → True"""
|
|
l1 = CondLeaf("A", ">", "0")
|
|
l2 = CondLeaf("B", "<", "5")
|
|
tree = CondAnd(l1, l2)
|
|
assert evaluate_tree(tree, {l1: True, l2: True}) is True
|
|
|
|
|
|
def test_evaluate_and_false():
|
|
"""AND 任一 False → False"""
|
|
l1 = CondLeaf("A", ">", "0")
|
|
l2 = CondLeaf("B", "<", "5")
|
|
tree = CondAnd(l1, l2)
|
|
assert evaluate_tree(tree, {l1: True, l2: False}) is False
|
|
|
|
|
|
def test_evaluate_or_true():
|
|
"""OR 任一 True → True"""
|
|
l1 = CondLeaf("A", "=", "1")
|
|
l2 = CondLeaf("B", "=", "2")
|
|
tree = CondOr(l1, l2)
|
|
assert evaluate_tree(tree, {l1: True, l2: False}) is True
|
|
|
|
|
|
def test_evaluate_or_false():
|
|
"""OR 全部 False → False"""
|
|
l1 = CondLeaf("A", "=", "1")
|
|
l2 = CondLeaf("B", "=", "2")
|
|
tree = CondOr(l1, l2)
|
|
assert evaluate_tree(tree, {l1: False, l2: False}) is False
|
|
|
|
|
|
def test_evaluate_not():
|
|
"""NOT 反转"""
|
|
leaf = CondLeaf("A", "=", "1")
|
|
tree = CondNot(leaf)
|
|
assert evaluate_tree(tree, {leaf: True}) is False
|
|
assert evaluate_tree(tree, {leaf: False}) is True
|
|
|
|
|
|
# ── CO-06~08: mcdc_sets ──
|
|
|
|
def test_mcdc_single_leaf_returns_none():
|
|
"""CO-06: 单条件 (IF A > 100) → None (不需要 MC/DC)"""
|
|
tree = CondLeaf("A", ">", "100")
|
|
assert mcdc_sets(tree) is None
|
|
|
|
|
|
def test_mcdc_and():
|
|
"""CO-07: AND (A > 0 AND B < 5) → 3 sets (MC/DC)"""
|
|
tree = CondAnd(CondLeaf("A", ">", "0"), CondLeaf("B", "<", "5"))
|
|
sets = mcdc_sets(tree)
|
|
assert sets is not None
|
|
# AND 需要 3 个测试对: TT→T, TF→F, FT→F
|
|
# 实际上 mcdc_sets 返回约束集,包含 True/False 决策
|
|
decisions = set(d for _, d in sets)
|
|
assert True in decisions
|
|
assert False in decisions
|
|
# 各叶子应有独立影响
|
|
all_constraints = [c for constraints, _ in sets for c in constraints]
|
|
fields_involved = set(c[0] for c in all_constraints)
|
|
assert "A" in fields_involved
|
|
assert "B" in fields_involved
|
|
|
|
|
|
def test_mcdc_or():
|
|
"""CO-08: OR (A = 1 OR B = 2) → 3 sets (MC/DC)"""
|
|
tree = CondOr(CondLeaf("A", "=", "1"), CondLeaf("B", "=", "2"))
|
|
sets = mcdc_sets(tree)
|
|
assert sets is not None
|
|
decisions = set(d for _, d in sets)
|
|
assert True in decisions
|
|
assert False in decisions
|
|
|
|
|
|
# ── is_field ──
|
|
|
|
def test_is_field_match():
|
|
"""字段名匹配"""
|
|
fields = [{"name": "WS-AMOUNT"}, {"name": "WS-STATUS"}]
|
|
assert is_field("WS-AMOUNT", fields) is True
|
|
|
|
|
|
def test_is_field_subscript():
|
|
"""带下标字段名匹配"""
|
|
fields = [{"name": "WS-ITEM-STATUS"}]
|
|
assert is_field("WS-ITEM-STATUS(WS-INDEX)", fields) is True
|
|
|
|
|
|
def test_is_field_no_match():
|
|
"""未知字段名返回 False"""
|
|
fields = [{"name": "WS-AMOUNT"}]
|
|
assert is_field("WS-OTHER", fields) is False
|
|
|
|
|
|
# ── satisfying_value ──
|
|
|
|
def test_satisfying_value_greater():
|
|
"""数值 > 条件: 返回值应大于给定值"""
|
|
from cobol_testgen.cond import satisfying_value
|
|
info = {"type": "numeric", "digits": 7, "decimal": 0}
|
|
r = satisfying_value(info, ">", "100", want_true=True)
|
|
assert int(r) > 100
|
|
|
|
|
|
def test_satisfying_value_equal_false():
|
|
"""= 条件 want=False: 返回不同值"""
|
|
from cobol_testgen.cond import satisfying_value
|
|
info = {"type": "numeric", "digits": 7, "decimal": 0}
|
|
r = satisfying_value(info, "=", "100", want_true=False)
|
|
assert int(r) != 100
|