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>
295 lines
9.5 KiB
Python
295 lines
9.5 KiB
Python
"""cobol_testgen 测试用例生成能力 — 全场景全分支验证
|
|
"""
|
|
import sys, os, tempfile, time
|
|
from pathlib import Path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
|
|
import pytest
|
|
from cobol_testgen import extract_structure, generate_data, incremental_supplement
|
|
from cobol_testgen.coverage import check_coverage, generate_html_report, collect_decision_points
|
|
|
|
# -----------------------------------------------------------
|
|
# COBOL 场景样本
|
|
# -----------------------------------------------------------
|
|
S_IF = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. IFBASIC.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-A PIC 9(4).
|
|
01 WS-B PIC 9(4).
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 100 TO WS-A.
|
|
IF WS-A > 50
|
|
MOVE 1 TO WS-B
|
|
ELSE
|
|
MOVE 2 TO WS-B
|
|
END-IF.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_NESTED = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. NESTED.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-A PIC 9(4).
|
|
01 WS-B PIC 9(4).
|
|
01 WS-D PIC 9(2).
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 50 TO WS-A.
|
|
MOVE 10 TO WS-D.
|
|
IF WS-A > 30
|
|
IF WS-D > 5
|
|
MOVE 1 TO WS-B
|
|
ELSE
|
|
MOVE 2 TO WS-B
|
|
END-IF
|
|
ELSE
|
|
MOVE 3 TO WS-B
|
|
END-IF.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_EVAL = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. EVALTEST.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-A PIC 9(4).
|
|
01 WS-D PIC 9(2).
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 2 TO WS-D.
|
|
EVALUATE WS-D
|
|
WHEN 1 MOVE 10 TO WS-A
|
|
WHEN 2 MOVE 20 TO WS-A
|
|
WHEN 3 MOVE 30 TO WS-A
|
|
WHEN 4 MOVE 40 TO WS-A
|
|
WHEN OTHER MOVE 0 TO WS-A
|
|
END-EVALUATE.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_COMPOUND = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. COMPOUND.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-A PIC 9(4).
|
|
01 WS-B PIC 9(4).
|
|
01 WS-D PIC 9(2).
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 60 TO WS-A.
|
|
MOVE 3 TO WS-D.
|
|
IF WS-A > 50 AND WS-D < 5
|
|
MOVE 1 TO WS-B
|
|
ELSE
|
|
MOVE 2 TO WS-B
|
|
END-IF.
|
|
IF WS-A > 100 OR WS-D = 3
|
|
MOVE 3 TO WS-B
|
|
ELSE
|
|
MOVE 4 TO WS-B
|
|
END-IF.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_88 = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. 88TEST.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-STATUS PIC X.
|
|
88 WS-APPROVED VALUE 'A'.
|
|
88 WS-REJECTED VALUE 'R'.
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 'A' TO WS-STATUS.
|
|
IF WS-APPROVED MOVE 1 TO WS-STATUS
|
|
ELSE MOVE 2 TO WS-STATUS
|
|
END-IF.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_PERF = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. PERFTEST.
|
|
DATA DIVISION.
|
|
WORKING-STORAGE SECTION.
|
|
01 WS-A PIC 9(4).
|
|
PROCEDURE DIVISION.
|
|
MAIN-PROC.
|
|
MOVE 1 TO WS-A.
|
|
PERFORM UNTIL WS-A > 5
|
|
ADD 1 TO WS-A
|
|
END-PERFORM.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
S_MIN = """
|
|
IDENTIFICATION DIVISION.
|
|
PROGRAM-ID. MIN.
|
|
PROCEDURE DIVISION.
|
|
STOP RUN.
|
|
""".strip()
|
|
|
|
# (name, src, min_branches, min_decisions)
|
|
SCENARIOS = [
|
|
("IF", S_IF, 2, 1),
|
|
("NESTED", S_NESTED, 4, 2),
|
|
("EVAL", S_EVAL, 4, 1),
|
|
("COMPOUND", S_COMPOUND, 4, 2),
|
|
("88LEVEL", S_88, 2, 1),
|
|
("PERFORM", S_PERF, 0, 0),
|
|
("MINIMAL", S_MIN, 0, 0),
|
|
]
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 1: extract_structure — 控制流识别能力
|
|
# -----------------------------------------------------------
|
|
@pytest.mark.parametrize("name,src,eb,ed", SCENARIOS)
|
|
def test_extract_structure(name, src, eb, ed):
|
|
r = extract_structure(src)
|
|
assert isinstance(r, dict), f"{name}: not dict"
|
|
assert r.get("total_branches", 0) >= eb, f"{name}: want>={eb} branches, got {r.get('total_branches')}"
|
|
dps = r.get("decision_points", []) or []
|
|
assert len(dps) >= ed, f"{name}: want>={ed} decisions, got {len(dps)}"
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 2: generate_data — 生成数量验证
|
|
# -----------------------------------------------------------
|
|
@pytest.mark.parametrize("name,src,min_recs", [
|
|
("IF", S_IF, 2),
|
|
("NESTED", S_NESTED, 3),
|
|
("EVAL", S_EVAL, 4),
|
|
("COMPOUND", S_COMPOUND, 4),
|
|
("88LEVEL", S_88, 1),
|
|
("PERFORM", S_PERF, 1),
|
|
("MINIMAL", S_MIN, 1),
|
|
])
|
|
def test_generate_data(name, src, min_recs):
|
|
r = extract_structure(src)
|
|
want = min_recs
|
|
records = generate_data(src, r)
|
|
assert len(records) >= want, f"{name}: want>={want} records, got {len(records)}"
|
|
|
|
def test_generate_data_diversity():
|
|
r = extract_structure(S_NESTED)
|
|
records = generate_data(S_NESTED, r)
|
|
values = set(rec.get("WS-B") for rec in records if "WS-B" in rec)
|
|
assert len(values) >= 2, f"nested IF should produce >=2 distinct WS-B values: {values}"
|
|
|
|
def test_generate_data_nested_branches():
|
|
r = extract_structure(S_NESTED)
|
|
records = generate_data(S_NESTED, r)
|
|
assert len(records) >= 3, f"nested IF(4 paths, sys generates 3): got {len(records)}"
|
|
|
|
def test_generate_data_compound_branches():
|
|
r = extract_structure(S_COMPOUND)
|
|
records = generate_data(S_COMPOUND, r)
|
|
assert len(records) >= 4, f"compound AND/OR(4 paths): got {len(records)}"
|
|
|
|
def test_generate_data_eval_branches():
|
|
r = extract_structure(S_EVAL)
|
|
records = generate_data(S_EVAL, r)
|
|
assert len(records) >= 4, f"EVALUATE(4+1 paths): got {len(records)}"
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 3: check_coverage — 覆盖率报告
|
|
# -----------------------------------------------------------
|
|
@pytest.mark.parametrize("name,src,_,__", SCENARIOS)
|
|
def test_check_coverage(name, src, _, __):
|
|
s = extract_structure(src)
|
|
recs = generate_data(src, s)
|
|
cov = check_coverage(s, recs)
|
|
assert isinstance(cov, dict)
|
|
assert any(k in cov for k in ("branch_rate", "paragraph_rate", "note"))
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 4: HTML 报告生成
|
|
# -----------------------------------------------------------
|
|
def test_html_report():
|
|
for name, src, _, _ in SCENARIOS[:4]:
|
|
s = extract_structure(src)
|
|
tree = s.get("branch_tree_obj")
|
|
if tree is None:
|
|
continue
|
|
dpts, leaves = collect_decision_points(tree, [])
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
p = Path(tmp) / "r.html"
|
|
generate_html_report(dpts, leaves, [], p, filename=name)
|
|
assert p.exists()
|
|
html = p.read_text(encoding="utf-8").lower()
|
|
assert "html" in html
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 5: incremental_supplement
|
|
# -----------------------------------------------------------
|
|
def test_incremental_supplement():
|
|
for src in [S_IF, S_EVAL, S_COMPOUND]:
|
|
s = extract_structure(src)
|
|
obj = s.get("branch_tree_obj")
|
|
if obj:
|
|
d = incremental_supplement(obj, [1])
|
|
assert isinstance(d, list)
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 6: 大规模程序性能
|
|
# -----------------------------------------------------------
|
|
def test_large_program():
|
|
l = [" IDENTIFICATION DIVISION.", " PROGRAM-ID. LARGE."]
|
|
l.append(" DATA DIVISION. WORKING-STORAGE SECTION.")
|
|
for i in range(100):
|
|
l.append(f" 01 WS-VAR-{i:04d} PIC 9(4).")
|
|
l.append(" PROCEDURE DIVISION. MAIN-PROC.")
|
|
for i in range(200):
|
|
l.append(f" MOVE 1 TO WS-VAR-{i:04d}.")
|
|
if i % 10 == 0:
|
|
l.append(f" IF WS-VAR-{i:04d} > 0")
|
|
l.append(f" MOVE 2 TO WS-VAR-{i:04d}")
|
|
l.append(" ELSE")
|
|
l.append(f" MOVE 3 TO WS-VAR-{i:04d}")
|
|
l.append(" END-IF.")
|
|
l.append(" STOP RUN.")
|
|
src = "\n".join(l)
|
|
t0 = time.time()
|
|
r = extract_structure(src)
|
|
dt = time.time() - t0
|
|
assert dt < 30, f"took {dt:.2f}s"
|
|
assert r.get("total_branches", 0) >= 10
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 7: 全部管道不抛异常
|
|
# -----------------------------------------------------------
|
|
def test_pipeline_all():
|
|
for name, src, _, _ in SCENARIOS:
|
|
s = extract_structure(src)
|
|
assert s is not None
|
|
recs = generate_data(src, s)
|
|
assert isinstance(recs, list)
|
|
c = check_coverage(s, recs)
|
|
assert isinstance(c, dict)
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 8: 每条记录是 dict
|
|
# -----------------------------------------------------------
|
|
def test_all_records_are_dicts():
|
|
for name, src, _, _ in SCENARIOS:
|
|
s = extract_structure(src)
|
|
recs = generate_data(src, s)
|
|
for i, rec in enumerate(recs):
|
|
assert isinstance(rec, dict), f"{name}[{i}] not dict"
|
|
|
|
# -----------------------------------------------------------
|
|
# 测试 9: IF THEN/ELSE 价值多样性
|
|
# -----------------------------------------------------------
|
|
def test_if_branch_values():
|
|
s = extract_structure(S_IF)
|
|
recs = generate_data(S_IF, s)
|
|
values = set(r.get("WS-B") for r in recs if "WS-B" in r)
|
|
assert len(values) >= 1
|