feat: SQL between/hostvar-key alignment, class-condition parsing, gcov merge across scenario runs

This commit is contained in:
hangshuo652
2026-08-09 17:43:00 +08:00
parent f331c8fa2a
commit 273a3f8211
31 changed files with 3789 additions and 272 deletions
+75 -21
View File
@@ -229,17 +229,25 @@ def _mark_if(dp, cons):
simple = getattr(dp, 'parsed', None)
if simple:
field, op, val = simple
inv_op = {'=': '<>', '<>': '=', '>': '<=', '<': '>=', '>=': '<', '<=': '>'}.get(op, op)
inv_simple = (field, inv_op, val)
for c in cons:
if _match_constraint(c, simple):
if c[3]:
dp.active_branches.add('T')
else:
if len(simple) == 4:
# class condition (field, 'IS', CLASS, base_want):
# path want == base_want → T branch; == not base_want → F branch
field, op, val, base_want = simple
for c in cons:
if _match_constraint(c, (field, op, val)):
dp.active_branches.add('T' if c[3] == base_want else 'F')
else:
field, op, val = simple
inv_op = {'=': '<>', '<>': '=', '>': '<=', '<': '>=', '>=': '<', '<=': '>'}.get(op, op)
inv_simple = (field, inv_op, val)
for c in cons:
if _match_constraint(c, simple):
if c[3]:
dp.active_branches.add('T')
else:
dp.active_branches.add('F')
elif _match_constraint(c, inv_simple):
dp.active_branches.add('F')
elif _match_constraint(c, inv_simple):
dp.active_branches.add('F')
elif dp.cond_tree and dp.cond_leaves:
assignment = {}
for leaf in dp.cond_leaves:
@@ -411,17 +419,24 @@ def _mark_perform(dp, cons):
simple = getattr(dp, 'parsed', None)
if simple:
field, op, val = simple
inv_op = {'=': '<>', '<>': '=', '>': '<=', '<': '>=', '>=': '<', '<=': '>'}.get(op, op)
inv_simple = (field, inv_op, val)
for c in cons:
if _match_constraint(c, simple):
if c[3]:
dp.active_branches.add('Skip')
else:
if len(simple) == 4:
# class condition (field, 'IS', CLASS, base_want): path want == base_want → Skip
field, op, val, base_want = simple
for c in cons:
if _match_constraint(c, (field, op, val)):
dp.active_branches.add('Skip' if c[3] == base_want else 'Enter')
else:
field, op, val = simple
inv_op = {'=': '<>', '<>': '=', '>': '<=', '<': '>=', '>=': '<', '<=': '>'}.get(op, op)
inv_simple = (field, inv_op, val)
for c in cons:
if _match_constraint(c, simple):
if c[3]:
dp.active_branches.add('Skip')
else:
dp.active_branches.add('Enter')
elif _match_constraint(c, inv_simple):
dp.active_branches.add('Enter')
elif _match_constraint(c, inv_simple):
dp.active_branches.add('Enter')
elif dp.cond_tree and dp.cond_leaves:
assignment = {}
for leaf in dp.cond_leaves:
@@ -482,7 +497,42 @@ def locate_decision_lines(decision_points, raw_source):
if re.search(short_pat, lines[i]):
dp.source_line = i + 1
used_indices[dp.label] = i
found = True
break
# Multi-line fallback 2: 条件被换行拆分(如 IF MERGE-REC-TYPE / = CONST
# 用条件首个字段定位 IF 起始行(gcov 分支标记需要源行)
if not found and dp.kind == 'IF':
first_field_pat = _build_first_field_if_pattern(dp)
if first_field_pat:
for i in range(start, len(lines)):
if re.search(first_field_pat, lines[i]):
dp.source_line = i + 1
used_indices[dp.label] = i
break
def _build_first_field_if_pattern(dp):
"""Build a pattern matching 'IF <首字段>' — 用于条件被换行拆分的 IF。
例:条件 'MERGE-REC-TYPE = CNS-PAY-TYPE-SALARY' 在源码中写作
IF MERGE-REC-TYPE
= CNS-PAY-TYPE-SALARY
单行模式无法匹配;此模式仅匹配 'IF MERGE-REC-TYPE' 定位起始行。
"""
if dp.kind != 'IF':
return None
label = dp.label or ''
cond = label[2:].strip() if label.upper().startswith('IF ') else label
cond = cond.strip()
if not cond:
return None
# 去除前导 NOT / 括号,取首字段名
m = re.match(r"^(?:NOT\s+)?\(?([A-Z][A-Z0-9_-]*)", cond, re.IGNORECASE)
if not m:
return None
first = m.group(1)
esc = re.escape(first)
return r'\bIF\b\s+' + esc + r'\b'
def _normalize(text):
@@ -494,7 +544,11 @@ def _normalize(text):
def _build_search_patterns(dp):
texts = []
if dp.kind == 'IF':
texts.append((r'\bIF\b', dp.label))
# dp.label is already like "IF SQLCODE = -803" — avoid emitting
# "\bIF\b\s+IF\s+..." which never matches a real source line.
label = dp.label or ''
cond = label[2:].strip() if label.upper().startswith('IF ') else label
texts.append((r'\bIF\b', cond))
elif dp.kind == 'EVALUATE':
texts.append((r'\bEVALUATE\b', dp.label))
elif dp.kind == 'PERFORM':