Feat/agent mode #1

Closed
tmm621 wants to merge 85 commits from feat/agent-mode into main
3 changed files with 36 additions and 16 deletions
Showing only changes of commit 58d060e6ce - Show all commits
+9 -4
View File
@@ -936,7 +936,8 @@ def extract_structure(cobol_source: str) -> dict:
}
def generate_data(cobol_source: str, structure: dict = None) -> list[dict]:
def generate_data(cobol_source: str, structure: dict = None,
copybook_dirs: list = None) -> list[dict]:
"""根据 COBOL 源码生成覆盖所有路径的测试数据。
Args:
@@ -955,7 +956,11 @@ def generate_data(cobol_source: str, structure: dict = None) -> list[dict]:
if branch_tree is None:
return []
preprocessed = preprocess(cobol_source)
if copybook_dirs:
src_resolved = resolve_copybooks(cobol_source, '.', extra_search_paths=copybook_dirs)
preprocessed = preprocess(src_resolved)
else:
preprocessed = preprocess(cobol_source)
data_div = extract_data_division(preprocessed)
data_fields = parse_data_division(data_div) if data_div else []
@@ -994,7 +999,7 @@ def generate_data(cobol_source: str, structure: dict = None) -> list[dict]:
_fdict_names = {f['name'] for f in fields_dict}
def _resolve_field(fn: str) -> str:
if fn == "__DP":
if fn.startswith("_"):
return fn
ufn = fn.upper()
if ' OF ' in ufn:
@@ -1009,7 +1014,7 @@ def generate_data(cobol_source: str, structure: dict = None) -> list[dict]:
for c in cons_list:
if len(c) >= 4:
fn = _resolve_field(str(c[0]))
if fn in _fdict_names or fn.startswith("__"):
if fn in _fdict_names or fn.startswith("_"):
c = list(c); c[0] = fn
clean.append(tuple(c))
else:
+14 -3
View File
@@ -52,8 +52,15 @@ def parse_single_condition(text, fields=None):
if ' AND ' in text or ' OR ' in text:
return None
text = text.strip()
if not text:
return None
text = re.sub(r'(\w)\s*\(', r'\1(', text)
field_name = text.split()[0] if text else ''
# AT END: synthetic condition from READ blocks
if text.upper() == 'AT END':
return ('_FILE_STATUS', '=', '10')
# SQLCODE special handling
if field_name.upper() == 'SQLCODE':
text_upper = text.upper()
@@ -86,8 +93,12 @@ def parse_single_condition(text, fields=None):
if ' OF ' in text.upper():
text = text.split(' OF ')[0].strip()
# COBOL class condition: WS-KEY-DGT-N NUMERIC
if re.match(r'^[A-Z][A-Z0-9-]*(?:\([^)]*\))?\s+(NUMERIC|ALPHABETIC|ALPHABETIC-UPPER|POSITIVE|NEGATIVE|ZERO)\s*$', text, re.IGNORECASE):
m = re.match(r'^([A-Z][A-Z0-9-]*(?:\([^)]*\))?)\s+(NUMERIC|ALPHABETIC|ALPHABETIC-UPPER|POSITIVE|NEGATIVE|ZERO)\s*$', text, re.IGNORECASE)
return (m.group(1), '=', m.group(2).upper())
# Bare field reference (no operator, no NOT): WS-EOF → WS-EOF = 'Y'
# (88-level COBOL condition name test)
if re.match(r'^[A-Z][A-Z0-9-]*(?:\([^)]*\))?\s*$', text, re.IGNORECASE):
return (text, '=', 'Y')
@@ -103,7 +114,7 @@ def parse_single_condition(text, fields=None):
inner = text[4:].strip()
inner_parsed = None
# Try standard regex on inner text
m_inner = re.match(r"^(\w[\w-]*(?:\s*\([^)]*\))?)\s*(>=|<=|<>|>|<|=)\s*(.+)$", inner)
m_inner = re.match(r"^(\w[\w-]*(?:\s*\([^)]*\))?)\s*(>=|<=|<>|>|<|=)\s*(.*)$", inner)
if m_inner:
inv_op_map = {'=': '<>', '<>': '=', '>': '<=', '<': '>=', '>=': '<', '<=': '>'}
f = re.sub(r'\s*([(),])\s*', r'\1', m_inner.group(1))
@@ -126,7 +137,7 @@ def parse_single_condition(text, fields=None):
# Standard regex: FIELD OP VALUE
m = re.match(
r"^(\w[\w-]*(?:\s*\([^)]*\))?)\s*(>=|<=|<>|>|<|=)\s*(.+)$",
r"^(\w[\w-]*(?:\s*\([^)]*\))?)\s*(>=|<=|<>|>|<|=)\s*(.*)$",
normalized
)
if m:
+13 -9
View File
@@ -64,6 +64,18 @@ def collect_decision_points(node, fields, counter=None):
ls = LeafStat(field=leaf.field, op=leaf.op, value=leaf.value)
dp.leaves.append(ls)
all_leaves.append(ls)
else:
# Try compound condition parsing for AND/OR expressions
compound = parse_compound_condition(node.condition, fields)
if compound and not isinstance(compound, CondLeaf):
leaves = list(collect_leaves(compound))
if leaves:
dp.cond_tree = compound
dp.cond_leaves = list(leaves)
for leaf in leaves:
ls = LeafStat(field=leaf.field, op=leaf.op, value=leaf.value)
dp.leaves.append(ls)
all_leaves.append(ls)
points.append(dp)
p, l = _walk_collect(node.true_seq, fields, counter)
points.extend(p); all_leaves.extend(l)
@@ -229,15 +241,7 @@ def _mark_if(dp, cons):
if _match_leaf(c, leaf):
dp.active_branches.add('T' if c[3] else 'F')
# Ultimate fallback: if any __DP constraint exists on the path targeting
# THIS decision point kind, this DP was explicitly generated and covered
if not dp.active_branches and cons:
if any(c[0] == "__DP" for c in cons if len(c) >= 4):
dp.active_branches.add('T')
dp.active_branches.add('F')
elif any(c[1] in ('=', '<>', '>', '<', '>=', '<=', 'not_in') for c in cons if len(c) >= 4):
dp.active_branches.add('T')
dp.active_branches.add('F')
def _mark_eval(dp, cons, fields=None):