fix: 覆盖率统计全面修复 + 5漏洞修正

## 修复内容

### C1: _mark_eval 反向操作符 (coverage.py)
- EVALUATE 约束匹配支持  操作符
- WHEN OTHER 的自动检测(全部 WHEN 被否定时)

### C2: _mark_perform 反向操作符 (coverage.py)
- PERFORM 同 _mark_if 的反向操作符匹配
- PERFORM UNTIL 条件截断后桥接器通过 branch_names 识别类型

### H1: parse_single_condition 传递 fields (coverage.py)
- collect_decision_points 调用时传 fields 参数
- NOT 前缀条件解析 (NOT WS-X > 50 → WS-X <= 50)

### H4: generate_data 输入约束 (__init__.py)
- 文档注明接收原始源码,非预处理后文本

### M1: not_map break (cond.py)
- NOT 操作符映射循环添加 break

## 覆盖测试结果
- IF: 100% (T/F)
- NOT IF: 100% (NOT_TRUE/NOT_FALSE)
- PERFORM UNTIL: 100% (ENTER/SKIP)
- EVALUATE: 100% (4 WHENs)
- Nested IF: 100% (4 branches)
- S15 回归: 17/17 PASS

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
NB-076
2026-06-24 21:14:50 +08:00
parent 7fb9304212
commit e2a8d53e60
6 changed files with 104 additions and 15 deletions
+26 -2
View File
@@ -50,7 +50,7 @@ def collect_decision_points(node, fields, counter=None):
counter[0] += 1
dp = DecisionPoint(id=counter[0], kind='IF', label=node.condition,
branch_names=['T', 'F'])
simple = parse_single_condition(node.condition)
simple = parse_single_condition(node.condition, fields)
if simple and is_field(simple[0], fields):
dp.parsed = simple
elif simple:
@@ -110,7 +110,7 @@ def collect_decision_points(node, fields, counter=None):
dp = DecisionPoint(id=counter[0], kind='PERFORM',
label=node.condition or '',
branch_names=['Enter', 'Skip'])
simple = parse_single_condition(node.condition) if node.condition else None
simple = parse_single_condition(node.condition, fields) if node.condition else None
if simple and is_field(simple[0], fields):
dp.parsed = simple
elif node.condition:
@@ -178,12 +178,17 @@ def _match_leaf(c, leaf):
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:
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:
@@ -250,13 +255,27 @@ def _mark_eval(dp, cons, fields=None):
if when_fields:
dp.active_branches.add('OTHER')
return
matched_when = False
for c in cons:
if c[0] == dp.label and c[1] == '=':
name = f"WHEN {c[2]}"
if name in dp.branch_names:
dp.active_branches.add(name)
matched_when = True
elif c[0] == dp.label and c[1] == '<>':
pass # Inverted operator — skip (negation of a prior WHEN)
elif c[0] == dp.label and c[1] == 'not_in':
dp.active_branches.add('OTHER')
matched_when = True
# If all subject constraints are '<>' (negations) and no '=' matched,
# this path reaches OTHER (EVALUATE ... WHEN OTHER)
if not matched_when and 'OTHER' in dp.branch_names:
all_negs = all(c[1] == '<>' for c in cons if c[0] == dp.label)
if all_negs:
dp.active_branches.add('OTHER')
elif any(c[1] in ('>=', '<=') for c in cons if c[0] == dp.label):
# THRU-range OTHER detection
pass
thru_lows = {c[2] for c in cons if c[0] == dp.label and c[1] == '>=' and c[3]}
thru_highs = {c[2] for c in cons if c[0] == dp.label and c[1] == '<=' and c[3]}
if thru_lows or thru_highs:
@@ -309,12 +328,17 @@ def _mark_search(dp, cons, fields=None):
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:
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: