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:
hangshuo652
2026-06-19 23:51:55 +08:00
parent 63b5284715
commit bc1d56d1a4
129 changed files with 19378 additions and 261 deletions
+103
View File
@@ -0,0 +1,103 @@
"""JCL Executor 深度测试 — 使用真实 GnuCOBOL"""
import sys, os, tempfile, subprocess, shutil
from pathlib import Path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import pytest
from jcl.executor import JclExecutor
from jcl.parser import parse_jcl, Job, JobStep, CondParam, DDEntry
COBC_OK = subprocess.run(
["cobc", "--version"], capture_output=True, timeout=5
).returncode == 0
def _cobol(prog: str = "TP") -> str:
return f"""
IDENTIFICATION DIVISION.
PROGRAM-ID. {prog}.
PROCEDURE DIVISION.
DISPLAY "OK:{prog}" NO ADVANCING.
STOP RUN.
"""
@pytest.mark.skipif(not COBC_OK, reason="need GnuCOBOL")
def test_compile_and_run():
tmp = tempfile.mkdtemp()
try:
root = Path(tmp)
cbl = root / "cobol"; cbl.mkdir()
(cbl / "P.cbl").write_text(_cobol("P"))
jp = tempfile.NamedTemporaryFile(mode="w", suffix=".jcl", delete=False)
jp.write("//J JOB\n//S1 EXEC PGM=P"); jp.close()
job = parse_jcl(jp.name); os.unlink(jp.name)
ex = JclExecutor(str(root), str(cbl), str(root))
ex.run(job)
assert ex.results["S1"]["status"] == "OK"
finally:
shutil.rmtree(tmp, ignore_errors=True)
@pytest.mark.skipif(not COBC_OK, reason="need GnuCOBOL")
def test_no_dd():
tmp = tempfile.mkdtemp()
try:
root = Path(tmp)
cbl = root / "cobol"; cbl.mkdir()
(cbl / "P.cbl").write_text(_cobol("P"))
job = Job("J"); job.steps.append(JobStep("S1", "P"))
ex = JclExecutor(str(root), str(cbl), str(root))
ex.run(job)
assert ex.results["S1"]["status"] == "OK"
finally:
shutil.rmtree(tmp, ignore_errors=True)
def test_sort():
tmp = tempfile.mkdtemp()
try:
root = Path(tmp)
d = root / "data" / "work"; d.mkdir(parents=True)
(d / "in.txt").write_text("c\nb\na\n")
job = Job("J"); job.steps.append(JobStep("S1", "SORT"))
job.steps[0].dd_entries = [
DDEntry(dd_name="SORTIN", dsn="data/work/in.txt"),
DDEntry(dd_name="SORTOUT", dsn="data/work/out.txt"),
]
ex = JclExecutor(str(root), "", "")
ex._run_sort(job.steps[0])
assert (root / "data" / "work" / "out.txt").read_text().splitlines() == ["a", "b", "c"]
finally:
shutil.rmtree(tmp, ignore_errors=True)
def test_cond_logic():
ex = JclExecutor(".", ".", ".")
# no step_name → execute
assert ex._check_cond(CondParam(code=4, operator="GT")) is True
# COND=(0,EQ) RC=0 → 0==0 True → not True=False → skip
ex.step_rcs["PREV"] = 0
assert ex._check_cond(CondParam(code=0, operator="EQ", step_name="PREV")) is False
# COND=(4,GT) RC=0 → 0>4 False → not False=True → execute
assert ex._check_cond(CondParam(code=4, operator="GT", step_name="PREV")) is True
@pytest.mark.skipif(not COBC_OK, reason="need GnuCOBOL")
def test_rc_tracking():
tmp = tempfile.mkdtemp()
try:
root = Path(tmp)
cbl = root / "cobol"; cbl.mkdir()
(cbl / "A.cbl").write_text(_cobol("A"))
(cbl / "B.cbl").write_text(_cobol("B"))
jp = tempfile.NamedTemporaryFile(mode="w", suffix=".jcl", delete=False)
jp.write("//J JOB\n//S1 EXEC PGM=A\n//S2 EXEC PGM=B"); jp.close()
job = parse_jcl(jp.name); os.unlink(jp.name)
ex = JclExecutor(str(root), str(cbl), str(root))
ex.run(job)
assert ex.step_rcs["S1"] == 0
assert ex.step_rcs["S2"] == 0
finally:
shutil.rmtree(tmp, ignore_errors=True)