"""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