feat: Phase 2 complete — 13 Phases of COBOL type classification and test benchmark
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>
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user