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,152 @@
|
||||
"""gcov 覆盖率采集全链路测试
|
||||
|
||||
测试内容:
|
||||
1. cobc --coverage 编译含 IF 分支的简单 COBOL 程序
|
||||
2. 运行生成 .gcda 文件
|
||||
3. collect_gcov() 解析 line_rate > 0
|
||||
4. 清理中间产物
|
||||
"""
|
||||
|
||||
import sys, os, subprocess, tempfile
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
import pytest
|
||||
from hina.gcov_collector import collect_gcov
|
||||
|
||||
|
||||
HAVE_COBC = None
|
||||
|
||||
|
||||
def _check_cobc() -> bool:
|
||||
"""检查 cobc 是否在 PATH 且支持 --coverage"""
|
||||
global HAVE_COBC
|
||||
if HAVE_COBC is not None:
|
||||
return HAVE_COBC
|
||||
try:
|
||||
r = subprocess.run(["cobc", "--version"], capture_output=True, text=True, timeout=15)
|
||||
HAVE_COBC = r.returncode == 0
|
||||
except FileNotFoundError:
|
||||
HAVE_COBC = False
|
||||
return HAVE_COBC
|
||||
|
||||
|
||||
# ── 嵌入一个简单的 COBOL 程序 (IF 分支) ──
|
||||
|
||||
SAMPLE_COBOL = """\
|
||||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. test-gcov.
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-X PIC 9(2) VALUE 0.
|
||||
01 WS-Y PIC 9(2) VALUE 0.
|
||||
PROCEDURE DIVISION.
|
||||
MOVE 10 TO WS-X.
|
||||
IF WS-X > 5 THEN
|
||||
MOVE 1 TO WS-Y
|
||||
ELSE
|
||||
MOVE 2 TO WS-Y
|
||||
END-IF.
|
||||
DISPLAY "Y=" WS-Y.
|
||||
STOP RUN.
|
||||
"""
|
||||
|
||||
|
||||
# ── 夹具: 创建临时目录存放 COBOL 源和编译产物 ──
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def work_dir() -> Path:
|
||||
"""创建临时工作目录"""
|
||||
with tempfile.TemporaryDirectory(prefix="gcov_test_") as tmp:
|
||||
yield Path(tmp)
|
||||
|
||||
|
||||
# ── 辅助函数 ──
|
||||
|
||||
|
||||
def _compile_with_coverage(src_path: Path, out_dir: Path) -> bool:
|
||||
"""用 cobc --coverage 编译, 返回是否成功"""
|
||||
r = subprocess.run(
|
||||
["cobc", "-x", "--coverage", str(src_path), "-o", str(out_dir / "test-gcov.exe")],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
cwd=str(out_dir),
|
||||
)
|
||||
if r.returncode != 0:
|
||||
print(f"[compile] stderr: {r.stderr[:300]}")
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
def _run_executable(exe_path: Path, run_dir: Path) -> bool:
|
||||
"""运行可执行文件, 返回是否成功"""
|
||||
r = subprocess.run(
|
||||
[str(exe_path)],
|
||||
capture_output=True, text=True, timeout=15,
|
||||
cwd=str(run_dir),
|
||||
)
|
||||
if r.returncode != 0:
|
||||
print(f"[run] stderr: {r.stderr[:300]}")
|
||||
print(f"[run] stdout: {r.stdout.strip()}")
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
# ── 测试用例 ──
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _check_cobc(), reason="cobc 未安装或不在 PATH 中")
|
||||
def test_gcov_basic_collect(work_dir: Path) -> None:
|
||||
"""全链路: 编译 → 运行 → collect_gcov → 验证 line_rate"""
|
||||
|
||||
# 1. 写入 COBOL 源文件
|
||||
src = work_dir / "test-gcov.cbl"
|
||||
src.write_text(SAMPLE_COBOL, encoding="utf-8")
|
||||
|
||||
# 2. 编译 (--coverage)
|
||||
assert _compile_with_coverage(src, work_dir), "cobc --coverage 编译失败"
|
||||
|
||||
# 3. 确认 .gcno 已生成
|
||||
gcno_files = list(work_dir.glob("*.gcno"))
|
||||
assert len(gcno_files) > 0, "编译后未生成 .gcno 文件"
|
||||
|
||||
# 4. 运行程序 (生成 .gcda)
|
||||
exe = work_dir / "test-gcov.exe"
|
||||
assert _run_executable(exe, work_dir), "程序运行失败"
|
||||
|
||||
# 5. 确认 .gcda 已生成
|
||||
gcda_files = list(work_dir.glob("*.gcda"))
|
||||
assert len(gcda_files) > 0, "运行后未生成 .gcda 文件"
|
||||
|
||||
# 6. 调用 collect_gcov() 采集覆盖率
|
||||
result = collect_gcov(cobol_src=src, work_dir=work_dir)
|
||||
print(f"[gcov] collect_gcov returned: {result}")
|
||||
|
||||
# 7. 验证结果
|
||||
assert result["available"] is True, f"覆盖率采集失败: {result.get('reason', 'unknown')}"
|
||||
assert result["line_rate"] > 0, f"line_rate 应为正值, 实际: {result['line_rate']}"
|
||||
assert result["total_lines"] > 0, f"total_lines 应为正值, 实际: {result['total_lines']}"
|
||||
assert result["executed_lines"] > 0, f"executed_lines 应为正值, 实际: {result['executed_lines']}"
|
||||
|
||||
# 8. 验证分支覆盖 (IF 的两路应至少覆盖了一路)
|
||||
assert result["line_rate"] <= 1.0, f"line_rate 不应超过 1.0"
|
||||
print(f"[gcov] ✅ line_rate={result['line_rate']} ({result['executed_lines']}/{result['total_lines']})")
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _check_cobc(), reason="cobc 未安装或不在 PATH 中")
|
||||
def test_gcov_no_gcda_graceful(work_dir: Path) -> None:
|
||||
"""无 .gcda 文件时 collect_gcov 应优雅降级"""
|
||||
|
||||
src = work_dir / "test-gcov.cbl"
|
||||
src.write_text(SAMPLE_COBOL, encoding="utf-8")
|
||||
|
||||
# 编译但不运行, 所以没有 .gcda
|
||||
subprocess.run(
|
||||
["cobc", "-x", "--coverage", str(src), "-o", str(work_dir / "test-gcov.exe")],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
cwd=str(work_dir),
|
||||
)
|
||||
|
||||
result = collect_gcov(cobol_src=src, work_dir=work_dir)
|
||||
# 没有 .gcda 时应 graceful 返回 {available: False}
|
||||
assert result["available"] is False
|
||||
print(f"[gcov] 无 .gcda 降级正常: {result}")
|
||||
Reference in New Issue
Block a user