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