feat: config-driven multi-scenario + gcda accumulation fix + gcov merge fix

Config-driven architecture:
- coverage_dates: YAML-driven date replacement for LEAVE_RECORDS
- row_overrides: per-scenario field overrides (e.g. STATUS='9')
- delete_all_rows: per-scenario table emptying for empty-cursor tests

gcda accumulation fix (V3 bug #7):
- Delete .gcda before each scenario run to prevent GnuCOBOL accumulation
- Force-copy scenario gcda (skip mtime check)
- Copy scenario DB to CWD data/kin.db for CONNECT TO path resolution

Multi-run gcov merge fix:
- Always merge multi-run gcov data regardless of generate_coverage flag
- Fix v3_root UnboundLocalError in cleanup path

Other fixes:
- _make_key_unique: skip WHERE-constrained columns to avoid PK conflict
- incremental_supplement: support fields_dict for base record generation
- check_coverage: use structure coverage data if available
- orchestrator.py: filter _-prefixed fields in TestCase; merge Agent2Data cases
This commit is contained in:
hangshuo652
2026-07-18 08:42:55 +08:00
parent 327ede372f
commit 0203ead96b
7 changed files with 186 additions and 21 deletions
+11 -7
View File
@@ -28,7 +28,7 @@ from .core import build_branch_tree, classify_field_roles, _init_child_names, sq
from .cond import parse_single_condition, is_field, collect_leaves
from .pipeline_bridge import build_branch_tree_fallback
from .design_mcdc import enum_paths as mcdc_enum_paths, _filter_stop
from .design import enum_paths, generate_records, get_term_type, extend_abend_programs
from .design import enum_paths, generate_records, get_term_type, extend_abend_programs, make_base_record
from .output import output_json, output_input_files
from .coverage import run_coverage, generate_coverage_index, collect_decision_points, mark_coverage
from japanese_data import generate_fullwidth_text, generate_halfwidth_katakana, generate_wareki_date
@@ -1195,6 +1195,7 @@ def extract_structure(cobol_source: str, copybook_dirs: list = None) -> dict:
"if_types": if_types,
"variable_patterns": variable_patterns,
"open_pattern": open_pattern,
"data_fields": fields_dict,
}
@@ -1353,12 +1354,13 @@ def generate_data(cobol_source: str, structure: dict = None,
return records
def incremental_supplement(branch_tree, decision_gaps: list[int]) -> list[dict]:
def incremental_supplement(branch_tree, decision_gaps: list[int], fields_dict: list = None) -> list[dict]:
"""针对未覆盖的决策点,增量生成补充测试数据。
Args:
branch_tree: extract_structure() 返回的 branch_tree 字段
decision_gaps: 未覆盖的决策点 ID 列表,如 [1, 3, 5]
fields_dict: 字段定义列表(DATA DIVISION 展开后),提供后生成含字段值的记录
Returns:
list[dict]: 增量测试数据,格式与 generate_data() 兼容
@@ -1390,10 +1392,12 @@ def incremental_supplement(branch_tree, decision_gaps: list[int]) -> list[dict]:
supplements = []
for i, (kind, label) in enumerate(found):
supplements.append({
"_dec_id": f"incr_{i}",
"_kind": kind,
"_label": str(label)[:60],
})
rec = {}
if fields_dict:
rec = make_base_record(i + 1, fields_dict)
rec["_dec_id"] = f"incr_{i}"
rec["_kind"] = kind
rec["_label"] = str(label)[:60]
supplements.append(rec)
return supplements
+23 -1
View File
@@ -1358,11 +1358,33 @@ def run_coverage(branch_tree, branch_paths_with_assigns, fields,
def check_coverage(structure: dict, test_records: list[dict]) -> dict:
total_paragraphs = structure.get("total_paragraphs", 0)
total_branches = structure.get("total_branches", 0)
decision_points = structure.get("decision_points", [])
has_data = len(test_records) > 0
paragraph_rate = 1.0 if (total_paragraphs > 0 and has_data) else 0.0
cov = structure.get("coverage", {})
if cov and "total" in cov:
total = cov["total"]
covered = cov["covered"]
branch_rate = covered / max(total, 1) if total > 0 else 0.0
decision_rate = branch_rate
uncovered = []
for dp in cov.get("decision_points", []):
if dp["covered"] < dp["branches"]:
uncovered.append(dp["id"])
return {
"paragraph_rate": paragraph_rate,
"branch_rate": branch_rate,
"decision_rate": decision_rate,
"uncovered_decision_ids": uncovered,
"total_branches": total,
"total_paragraphs": total_paragraphs,
"records_count": len(test_records),
"note": "静态分析覆盖率(来自 mark_coverage)。精确数据通过 gcov 获取。",
}
return {
"paragraph_rate": paragraph_rate,
"branch_rate": 0.0,
+3 -1
View File
@@ -434,12 +434,14 @@ def build_db_input(
if sc not in [c['name'] for c in col_infos]:
col_infos.append({'name': sc, 'db_type': 'CHAR', 'size': 20})
where_cols = set()
for col_info in col_infos:
col_name = col_info['name']
val = None
for wc in where_cons:
if wc['type'] == 'literal' and wc.get('col', '').upper() == col_name:
val = wc.get('literal', '')
where_cols.add(col_name)
break
if wc['type'] == 'host_var':
hv = wc.get('host_var', '').upper()
@@ -473,7 +475,7 @@ def build_db_input(
if col_infos:
first_col = col_infos[0]['name']
if first_col in row:
if first_col in row and (not where_cols or first_col not in where_cols):
row[first_col] = _make_key_unique(row[first_col], path_idx, seen_keys[table])
db_input[table].append(row)