merge local cobol_testgen improvements into v3 shared modules
- cond.py: SQLCODE/SQLSTATE handling, alphanumeric >/< boundary fix - output.py: termination tracking, db_input support, _is_field_assigned filter - coverage.py: mark_from_gcov, THRU support, KeyError protection - gcov.py: new file (dependency for coverage.py) - grammar.lark: multi-segment PIC support - read.py: SQL INCLUDE resolution, DECLARE TABLE parsing, * comment fix - core.py: SQL parsing, blocked_names, keyword list - design.py: multi-sentinel, THRU ranges, PERFORM VARYING last iteration - __init__.py: local main() + v3 API functions, guarded imports All 6 ZAN programs verified passing through v3 pipeline
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""gcov 覆盖率数据解析和分支标记"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_cbl_gcov(gcov_path: str) -> dict[int, int]:
|
||||
"""解析 .cbl.gcov 文件,返回 {COBOL行号: 执行次数}。
|
||||
|
||||
gcov 行格式:
|
||||
#####: 6: 源码行 → 未执行(0 次)
|
||||
75*: 12: 源码行 → 执行 75 次
|
||||
1*: 14: 源码行 → 执行 1 次
|
||||
-: 17: 源码行 → 不可执行(注释/声明行,跳过)
|
||||
"""
|
||||
counts = {}
|
||||
with open(gcov_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
m = re.match(r'^\s*(#####|\d+\*?|-):\s*(\d+):', line)
|
||||
if not m:
|
||||
continue
|
||||
count_str = m.group(1)
|
||||
lineno = int(m.group(2))
|
||||
if count_str == '#####':
|
||||
counts[lineno] = 0
|
||||
elif count_str == '-':
|
||||
continue
|
||||
else:
|
||||
counts[lineno] = int(count_str.rstrip('*'))
|
||||
return counts
|
||||
|
||||
|
||||
def run_gcov(program_name: str, work_dir: str) -> dict[int, int]:
|
||||
"""在 work_dir 中通过 WSL 执行 gcov 并解析 COBOL 行计数。
|
||||
|
||||
Args:
|
||||
program_name: 程序名(不含扩展名),如 "ALLCMDS"
|
||||
work_dir: 包含 .gcda/.gcno 的目录(Windows 路径)
|
||||
|
||||
Returns:
|
||||
{COBOL行号: 执行次数} 字典。失败时返回空 dict。
|
||||
"""
|
||||
wsl_work = _wsl_path(work_dir)
|
||||
cmd = ['wsl', 'sh', '-c', f'cd {wsl_work} && gcov {program_name}.c']
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True, text=True,
|
||||
encoding='utf-8', errors='replace',
|
||||
timeout=30,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"gcov 失败 (exit={result.returncode}): {result.stderr.strip()}")
|
||||
return {}
|
||||
|
||||
cbl_gcov = Path(work_dir) / f'{program_name}.cbl.gcov'
|
||||
if not cbl_gcov.exists():
|
||||
logger.warning(f"gcov 输出不存在: {cbl_gcov}")
|
||||
return {}
|
||||
|
||||
gcov_data = parse_cbl_gcov(str(cbl_gcov))
|
||||
logger.info(f"gcov 解析: {len(gcov_data)} 行, "
|
||||
f"{sum(1 for v in gcov_data.values() if v > 0)} 行已执行")
|
||||
return gcov_data
|
||||
|
||||
|
||||
def _wsl_path(windows_path: str) -> str:
|
||||
path = Path(windows_path).resolve()
|
||||
drive = path.drive.lower().rstrip(':')
|
||||
rest = str(path.relative_to(path.anchor)).replace('\\', '/')
|
||||
return f'/mnt/{drive}/{rest}'
|
||||
|
||||
|
||||
def mark_from_gcov(decision_points: list, gcov_data: dict[int, int],
|
||||
branch_tree) -> None:
|
||||
"""用 gcov 行执行计数推断决策点分支覆盖,直接修改 decision_points 的 active_branches。
|
||||
|
||||
推断规则(简化版,先覆盖主要场景):
|
||||
|
||||
IF (条件行 L):
|
||||
- 条件行 L 在 gcov 中 count == 0 → 不可到达,不标记
|
||||
- 条件行 L 在 gcov 中 count > 0 → 标记 T 和 F 都覆盖
|
||||
|
||||
EVALUATE:
|
||||
- subject 行 count > 0 → 标记所有 WHEN 为已覆盖
|
||||
|
||||
PERFORM UNTIL (条件行 L):
|
||||
- count == 1 → 条件初始即为真,循环体未进入 → Skip 覆盖
|
||||
- count > 1 → 循环体至少进入一次 → Enter 覆盖
|
||||
- Skip 总视为覆盖(无论进入与否,最终都会跳出)
|
||||
"""
|
||||
for dp in decision_points:
|
||||
ln = dp.source_line
|
||||
if ln <= 0 or ln not in gcov_data:
|
||||
continue
|
||||
|
||||
count = gcov_data.get(ln)
|
||||
if count is None:
|
||||
continue
|
||||
|
||||
if dp.kind == 'IF':
|
||||
if count == 0:
|
||||
continue
|
||||
dp.active_branches.add('T')
|
||||
dp.active_branches.add('F')
|
||||
|
||||
elif dp.kind == 'EVALUATE':
|
||||
if count == 0:
|
||||
continue
|
||||
for bn in dp.branch_names:
|
||||
dp.active_branches.add(bn)
|
||||
|
||||
elif dp.kind == 'PERFORM':
|
||||
if count > 1:
|
||||
dp.active_branches.add('Enter')
|
||||
dp.active_branches.add('Skip')
|
||||
Reference in New Issue
Block a user