Object type mix: INTF, TABL, STRU, MSAG, exception tasks (harness G2, mutants, generator notes), balanced generator, kind-deficit job order, dashboard mix card

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Kral
2026-10-05 18:52:29 +02:00
parent 454db6626c
commit b119f1afac
39 changed files with 2329 additions and 24 deletions

View File

@@ -21,7 +21,7 @@ CLEAN_RULES = {
}
DELETE_ORDER = ["CLAS", "INTF", "PROG", "FUNC", "FUGR", "SRVD", "DDLX", "DCLS", "DDLS",
"TTYP", "TABL", "STRU", "DTEL", "DOMA", "MSAG"]
SOURCE_TYPES = ("CLAS", "INTF", "PROG", "FUNC", "DDLS", "DCLS", "DDLX", "TABL")
SOURCE_TYPES = ("CLAS", "INTF", "PROG", "FUNC", "DDLS", "DCLS", "DDLX", "TABL", "STRU")
def _cds_elements(src):
@@ -40,6 +40,13 @@ def _cds_elements(src):
return out
def _ddl_fields(src):
"""Field names of a DDL table or structure source ('key name : type', 'name : type', 'include x')."""
body = re.sub(r"//[^\n]*|/\*.*?\*/", "", src or "", flags=re.S)
return {m.group(1).upper() for m in re.finditer(r"^\s*(?:key\s+)?(\w+)\s*:", body, re.I | re.M)
if not m.group(1).startswith("@") and m.group(1).lower() not in ("define",)}
def _obj_args(otype, name, fg=None):
a = {"objectType": otype, "objectName": name}
if fg:
@@ -136,6 +143,9 @@ class Runner:
includeType="testclasses",
source=o["testclasses_source"]))
ok = ok and not e3 and (_json(t3) or {}).get("success", False)
if o.get("messages"): # message class (MSAG): created empty, messages written with sap_push_message
e5, t5 = mcp.call("sap_push_message", {"objectName": o["name"], "messages": o["messages"]})
ok = ok and not e5 and (_json(t5) or {}).get("success", False)
if o.get("run"):
e4, t4 = mcp.call("sap_run_class", {"className": o["name"]})
ok = ok and not e4 and (_json(t4) or {}).get("success", False)
@@ -285,9 +295,11 @@ class Runner:
g2_detail = []
for c in contract:
src = sources.get((c["type"], c["name"].upper()), "")
if c.get("implements") and not re.search(rf"INTERFACES\s+{re.escape(c['implements'])}\b", src, re.I):
g2 = False
g2_detail.append(f"{c['name']}: does not implement {c['implements']}")
impl = c.get("implements") or []
for iname in ([impl] if isinstance(impl, str) else impl): # one interface or a list
if not re.search(rf"INTERFACES\s+{re.escape(str(iname))}\b", src, re.I):
g2 = False
g2_detail.append(f"{c['name']}: does not implement {iname}")
if c["type"] == "FUNC": # signature: every parameter with its type in the FUNCTION header
# the FUNCTION statement, not the first statement: local classes can come before it (G0107)
fm = re.search(rf"^\s*FUNCTION\s+{re.escape(c['name'])}\b[^.]*\.", src, re.I | re.M)
@@ -303,15 +315,25 @@ class Runner:
if not re.search(rf"(PARAMETERS|SELECT-OPTIONS)\s*:?[^.]*\b{re.escape(prm)}\b", src, re.I):
g2 = False
g2_detail.append(f"{c['name']}: no selection screen parameter {prm}")
if c["type"] == "DDLS" and c.get("fields") and g["G1_active"]:
_, q = mcp.call("sap_sql_query", {"query": f"SELECT * FROM {c['name']}", "maxRows": 1})
cols = {col.get("name", "").upper() for col in (_json(q) or {}).get("columns", [])}
if c["type"] in ("DDLS", "TABL", "STRU") and c.get("fields") and g["G1_active"]:
cols = set()
if c["type"] != "STRU": # a structure cannot be selected
_, q = mcp.call("sap_sql_query", {"query": f"SELECT * FROM {c['name']}", "maxRows": 1})
cols = {col.get("name", "").upper() for col in (_json(q) or {}).get("columns", [])}
if not cols: # view with parameters: SELECT without parameters fails
cols = _cds_elements(src)
cols = _cds_elements(src) if c["type"] == "DDLS" else _ddl_fields(src)
missing = {f.upper() for f in c["fields"]} - cols
if missing:
g2 = False
g2_detail.append(f"{c['name']}: CDS elements missing: {sorted(missing)}")
g2_detail.append(f"{c['name']}: {'CDS elements' if c['type'] == 'DDLS' else 'fields'} missing: {sorted(missing)}")
if c["type"] == "MSAG" and c.get("messages") and g["G1_active"]: # the numbers must exist (texts: hidden tests)
_, q = mcp.call("sap_sql_query", {"query": "SELECT msgnr FROM t100 WHERE arbgb = '%s' AND sprsl = 'E'"
% c["name"].upper(), "maxRows": 999})
have = {r.get("MSGNR") for r in (_json(q) or {}).get("rows", [])}
missing = {str(m["msgno"]).zfill(3) for m in c["messages"]} - have
if missing:
g2 = False
g2_detail.append(f"{c['name']}: message numbers missing: {sorted(missing)}")
# Categories E and I: the contract object is a seed object (refactor or fix in place). It must
# change (else the null agent passes on the legacy code), and it is not out of scope.
contract_upper = {c["name"].upper() for c in contract}