feat: SQL between/hostvar-key alignment, class-condition parsing, gcov merge across scenario runs
This commit is contained in:
+75
-21
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user