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:
@@ -53,6 +53,8 @@ class OracleAgent:
|
|||||||
ident["functionGroup"] = o["functionGroup"]
|
ident["functionGroup"] = o["functionGroup"]
|
||||||
proxy.call("sap_create_object", dict(ident, packageName="$TMP",
|
proxy.call("sap_create_object", dict(ident, packageName="$TMP",
|
||||||
description=o.get("description", o["name"])[:60]))
|
description=o.get("description", o["name"])[:60]))
|
||||||
|
if o.get("messages"): # message class: messages are written with sap_push_message
|
||||||
|
proxy.call("sap_push_message", {"objectName": o["name"], "messages": o["messages"]})
|
||||||
if o.get("source"):
|
if o.get("source"):
|
||||||
proxy.call("sap_push_source", dict(ident, source=o["source"]))
|
proxy.call("sap_push_source", dict(ident, source=o["source"]))
|
||||||
if o.get("testclasses_source"):
|
if o.get("testclasses_source"):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import time
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
from .adt_client import load_env
|
from .adt_client import load_env
|
||||||
|
from . import mix
|
||||||
from .ledger import _env_budget, spent
|
from .ledger import _env_budget, spent
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
@@ -242,6 +243,29 @@ def agg_table(title, evs, rows, key, note=""):
|
|||||||
% (E(title), E(key.replace("_", " ")), trs, "<div class=note>%s</div>" % E(note) if note else ""))
|
% (E(title), E(key.replace("_", " ")), trs, "<div class=note>%s</div>" % E(note) if note else ""))
|
||||||
|
|
||||||
|
|
||||||
|
def mix_table(rows, evs):
|
||||||
|
kt = mix.accepted_task_counts()
|
||||||
|
kr = {}
|
||||||
|
for r, e in zip(rows, evs):
|
||||||
|
k = mix.kind_of_task_dir(r["task"])
|
||||||
|
a = kr.setdefault(k, [0, 0])
|
||||||
|
a[0] += 1
|
||||||
|
a[1] += e["accepted"]
|
||||||
|
tt, tr = max(sum(kt.values()), 1), max(sum(v[1] for v in kr.values()), 1)
|
||||||
|
ss = sum(mix.TYPE_SHARE.values())
|
||||||
|
trs = ""
|
||||||
|
for k, v in mix.TYPE_SHARE.items():
|
||||||
|
tgt = 100.0 * v / ss
|
||||||
|
pt, pr = 100.0 * kt.get(k, 0) / tt, 100.0 * kr.get(k, [0, 0])[1] / tr
|
||||||
|
cls = "g" if abs(pt - tgt) < 4 else ("w" if abs(pt - tgt) < 10 else "r")
|
||||||
|
trs += ("<tr><td>%s</td><td class=n>%.0f%%</td><td class=n>%d (%.0f%%)</td><td style='width:25%%'>%s</td>"
|
||||||
|
"<td class=n>%d/%d (%.0f%%)</td></tr>" % (k, tgt, kt.get(k, 0), pt, bar(pt, max(tgt * 2, 1), cls),
|
||||||
|
kr.get(k, [0, 0])[1], kr.get(k, [0, 0])[0], pr))
|
||||||
|
return ("<div class=card><h2>Object type mix vs target</h2><div class=b><table><tr><th>kind</th><th class=n>target</th>"
|
||||||
|
"<th class=n>tasks</th><th>vs target</th><th class=n>trajectories acc/runs</th></tr>%s</table></div>"
|
||||||
|
"<div class=note>CLAS/INTF 35, CDS 25, FUNC 15, PROG 10, DDIC 10, MSAG + exception 5 (percent of accepted tasks).</div></div>" % trs)
|
||||||
|
|
||||||
|
|
||||||
def gen_table(logs, key, title):
|
def gen_table(logs, key, title):
|
||||||
agg = {}
|
agg = {}
|
||||||
for l in logs:
|
for l in logs:
|
||||||
@@ -351,7 +375,7 @@ def render(d):
|
|||||||
E("\n".join(d["procs"]))))
|
E("\n".join(d["procs"]))))
|
||||||
logc = "<div class=card><h2>Pipeline log</h2><div class=b><div class='log mono'>%s</div></div></div>" % E("\n".join(d["log_tail"]))
|
logc = "<div class=card><h2>Pipeline log</h2><div class=b><div class='log mono'>%s</div></div></div>" % E("\n".join(d["log_tail"]))
|
||||||
body = (banner + "<div class=tiles>" + tiles + "</div><div class=grid>" + budget + prog + stops + sysc
|
body = (banner + "<div class=tiles>" + tiles + "</div><div class=grid>" + budget + prog + stops + sysc
|
||||||
+ agg_table("Trajectories by category", evs, rows, "category") + agg_table("Trajectories by object type", evs, rows, "object_type")
|
+ mix_table(rows, evs) + agg_table("Trajectories by category", evs, rows, "category") + agg_table("Trajectories by object type", evs, rows, "object_type")
|
||||||
+ gen_table(logs, "category", "Task generation by category") + gen_table(logs, "object_type", "Task generation by object type")
|
+ gen_table(logs, "category", "Task generation by category") + gen_table(logs, "object_type", "Task generation by object type")
|
||||||
+ tok + recent + logc + "</div>")
|
+ tok + recent + logc + "</div>")
|
||||||
stamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(d["now"]))
|
stamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(d["now"]))
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ ABAPLINT = os.path.join(ROOT, "node_modules", ".bin", "abaplint")
|
|||||||
MAX_OUT_TOKENS = 80000
|
MAX_OUT_TOKENS = 80000
|
||||||
RUNS_PER_ATTEMPT = 10
|
RUNS_PER_ATTEMPT = 10
|
||||||
ABAPLINT_VERSIONS = {"v702", "v740sp05", "v750", "v758"}
|
ABAPLINT_VERSIONS = {"v702", "v740sp05", "v750", "v758"}
|
||||||
EXAMPLE_FOR = {"CLAS": "T01", "INTF": "T01", "FUNC": "T13", "PROG": "T14", "DDLS": "T15", "TABL": "T15"}
|
EXAMPLE_FOR = {"CLAS": "T01", "INTF": "T01", "FUNC": "T13", "PROG": "T14", "DDLS": "T15", "TABL": "T15",
|
||||||
|
"STRU": "T15", "MSAG": "T13"}
|
||||||
CATEGORIES = {
|
CATEGORIES = {
|
||||||
"A": "pure logic in a new class (language, OO design, Clean ABAP)",
|
"A": "pure logic in a new class (language, OO design, Clean ABAP)",
|
||||||
"B": "database access (ABAP SQL, CDS) with test doubles",
|
"B": "database access (ABAP SQL, CDS) with test doubles",
|
||||||
@@ -50,6 +51,57 @@ CATEGORIES = {
|
|||||||
"hidden_tests is []. Do not write the gap in Open questions (write \"None.\")",
|
"hidden_tests is []. Do not write the gap in Open questions (write \"None.\")",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Extra format notes for object types that the main prompt does not describe (training mode, 2026-10-05).
|
||||||
|
# The example bundle is of another type; these notes say how this type differs.
|
||||||
|
TYPE_NOTES = {
|
||||||
|
"INTF": """The model must create an INTERFACE (type INTF) as the main contract object: the methods, types and constants
|
||||||
|
named in the spec. Contract entry: {"type": "INTF", "name": "{{P}}IF_X"}. Reference: the interface source
|
||||||
|
("reference/x.intf.abap") and ONE global own-test class (type CLAS, source with a global class DEFINITION FOR TESTING;
|
||||||
|
an interface has no test include). Hidden tests: one global test class with a local class that IMPLEMENTS the
|
||||||
|
interface (it only compiles when every signature matches), calls the methods through the interface, reads the
|
||||||
|
constants (values), and checks type lengths with RTTI (cl_abap_typedescr=>describe_by_name). The business rules give the
|
||||||
|
exact constant values, parameter names and types. No other object is needed.""",
|
||||||
|
"TABL": """The model must create a transparent DATABASE TABLE (type TABL) as the main contract object. The table name has at
|
||||||
|
most 16 characters including the prefix: use a short name, for example {{P}}ORD (7 characters after the prefix at most).
|
||||||
|
Source is DDL: annotations @EndUserText.label, @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE,
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT, @AbapCatalog.deliveryClass : #A, @AbapCatalog.dataMaintenance : #RESTRICTED,
|
||||||
|
then "define table {{p}}ord { key client : abap.clnt not null; key item_id : abap.char(10) not null; ... }".
|
||||||
|
Contract entry: {"type": "TABL", "name": "{{P}}ORD", "fields": ["client", "item_id", ...]} (all fields, lower case).
|
||||||
|
The spec (Business rules) lists every field with its data type and length, the key fields, and not-null fields. Use
|
||||||
|
only built-in types (abap.char, abap.numc, abap.dec, abap.int4, abap.dats). Avoid abap.curr and abap.quan; if the spec
|
||||||
|
needs an amount or a quantity, the reference field needs a reference annotation in the form 'tablename.fieldname'
|
||||||
|
(for example @Semantics.amount.currencyCode : '{{p}}ord.currency_code' on the amount and a field currency_code :
|
||||||
|
abap.cuky in the same table; @Semantics.quantity.unitOfMeasure : '{{p}}ord.unit' with unit : abap.unit(3)); a
|
||||||
|
reference without the table name fails with "Annotation with reference to unit code ... is uncomplete". Reference: the DDL file and ONE global own-test class
|
||||||
|
(type CLAS). Hidden tests (one global class): RTTI on the table: cast cl_abap_typedescr=>describe_by_name( '{{P}}ORD' ) to
|
||||||
|
cl_abap_structdescr and check get_ddic_field_list( ) (field names, key flags, lengths, decimals, types), and a
|
||||||
|
cl_osql_test_environment test (create( i_dependency_list = VALUE #( ( '{{P}}ORD' ) ) ), insert, select back; a second
|
||||||
|
INSERT with the same key gives sy-subrc = 4). No seed is needed.""",
|
||||||
|
"STRU": """The model must create a DDIC STRUCTURE (type STRU) as the main contract object. DDL source: annotations
|
||||||
|
@EndUserText.label and @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE, then
|
||||||
|
"define structure {{p}}name { code : abap.char(4); amount : abap.dec(9,2); ... }" (include another structure with
|
||||||
|
"include {{p}}other;" when the spec asks). Contract entry: {"type": "STRU", "name": "{{P}}NAME", "fields": [...]}
|
||||||
|
(the field names in lower case). The Business rules list every field with data type and length. Reference: the DDL file
|
||||||
|
and ONE global own-test class (type CLAS). Hidden tests (one global class): RTTI: cl_abap_typedescr=>describe_by_name(
|
||||||
|
'{{P}}NAME' ) cast to cl_abap_structdescr; check components (names, length, decimals, type kind) and the order.""",
|
||||||
|
"MSAG": """The model must create a MESSAGE CLASS (type MSAG) as the main contract object. The name has at most 20
|
||||||
|
characters including the prefix. A message class has NO source file. In task.json the reference entry has no "file":
|
||||||
|
{"type": "MSAG", "name": "{{P}}MSG", "description": "...", "messages": [{"msgno": "001", "text": "Order &1 is blocked"}, ...]}
|
||||||
|
and the contract entry is {"type": "MSAG", "name": "{{P}}MSG", "messages": [{"msgno": "001"}, ...]} (the numbers that
|
||||||
|
must exist). The Business rules give for each message the number, the exact text with the placeholders &1 to &4 (at
|
||||||
|
most 72 characters), and when it is used (error, warning, information). Reference: the message class entry and ONE global
|
||||||
|
own-test class (type CLAS) that reads the messages. Hidden tests (one global class): for each message use
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '001' WITH 'A' 'B' INTO DATA(lv_text) and assert the final text; also check one
|
||||||
|
placeholder substitution. The model may also be asked for an exception class that uses the message class (then both
|
||||||
|
are contract entries and the exception class is a reference file).""",
|
||||||
|
"EXC": """The main contract object is a class-based EXCEPTION class (CLAS, name {{P}}CX_...) that inherits from
|
||||||
|
CX_STATIC_CHECK, CX_DYNAMIC_CHECK or CX_NO_CHECK. The spec asks for constants for the error cases, attributes with
|
||||||
|
context data, a constructor with these parameters, and a message text (through IF_T100_DYN_MSG / IF_T100_MESSAGE with
|
||||||
|
a message class that is a seed object, or through a redefined get_text). Hidden tests raise the exception from a small
|
||||||
|
seed class, catch it and check the attributes and the text (get_text( )). Reference: the exception class with its
|
||||||
|
testclasses_file or ONE global own-test class (an exception class has few methods).""",
|
||||||
|
}
|
||||||
|
|
||||||
SYSTEM = """You write evaluation tasks for an ABAP developer model. Each task is a bundle of files.
|
SYSTEM = """You write evaluation tasks for an ABAP developer model. Each task is a bundle of files.
|
||||||
The model gets only spec.md and works on an SAP ABAP Platform 2025 system (SAP_BASIS 816, client 001)
|
The model gets only spec.md and works on an SAP ABAP Platform 2025 system (SAP_BASIS 816, client 001)
|
||||||
through ADT tools. The harness installs the seed objects, runs the model, then checks the result with
|
through ADT tools. The harness installs the seed objects, runs the model, then checks the result with
|
||||||
@@ -220,7 +272,7 @@ def lint_files(b):
|
|||||||
m = DDL_KIND.search(b.get("files", {}).get(o.get("file", ""), ""))
|
m = DDL_KIND.search(b.get("files", {}).get(o.get("file", ""), ""))
|
||||||
if not m:
|
if not m:
|
||||||
continue
|
continue
|
||||||
want = {"table": "TABL", "structure": "TABL", "view": "DDLS"}[m.group(1).lower()]
|
want = {"table": "TABL", "structure": "STRU", "view": "DDLS"}[m.group(1).lower()]
|
||||||
if o.get("type") != want:
|
if o.get("type") != want:
|
||||||
errs.append(f"{k}: {o.get('name')} has type {o.get('type')}, but its source is "
|
errs.append(f"{k}: {o.get('name')} has type {o.get('type')}, but its source is "
|
||||||
f"'define {m.group(1)}'; use type {want}")
|
f"'define {m.group(1)}'; use type {want}")
|
||||||
@@ -294,9 +346,24 @@ def check_bundle(b):
|
|||||||
errs.append(f"{k}: file {o[fk]} missing")
|
errs.append(f"{k}: file {o[fk]} missing")
|
||||||
if not o.get("name", "").startswith("{{P}}"):
|
if not o.get("name", "").startswith("{{P}}"):
|
||||||
errs.append(f"{k}: name {o.get('name')} does not start with {{{{P}}}}")
|
errs.append(f"{k}: name {o.get('name')} does not start with {{{{P}}}}")
|
||||||
limit = 16 if o.get("type") == "TABL" else 26 if o.get("type") == "FUGR" else 30
|
limit = {"TABL": 16, "FUGR": 26, "MSAG": 20}.get(o.get("type"), 30)
|
||||||
if len(o.get("name", "").replace("{{P}}", "Z0000000_")) > limit:
|
if len(o.get("name", "").replace("{{P}}", "Z0000000_")) > limit:
|
||||||
errs.append(f"{k}: name {o.get('name')} too long (max {limit})")
|
errs.append(f"{k}: name {o.get('name')} too long (max {limit})")
|
||||||
|
for k in ("seed", "reference"):
|
||||||
|
for o in t.get(k, []):
|
||||||
|
if o.get("type") == "MSAG":
|
||||||
|
msgs = o.get("messages") or []
|
||||||
|
if k == "reference" and not msgs:
|
||||||
|
errs.append(f"reference message class {o.get('name')} has no 'messages' list")
|
||||||
|
for m in msgs:
|
||||||
|
if not re.fullmatch(r"\d{1,3}", str(m.get("msgno", ""))) or not m.get("text") or len(m["text"]) > 72:
|
||||||
|
errs.append(f"message class {o.get('name')}: message {m.get('msgno')} needs a 1 to 3 digit "
|
||||||
|
"msgno and a text of at most 72 characters")
|
||||||
|
for c in t.get("contract", []):
|
||||||
|
if c.get("type") in ("TABL", "STRU") and not c.get("fields"):
|
||||||
|
errs.append(f"contract {c.get('name')}: a {c['type']} entry needs the list 'fields'")
|
||||||
|
if c.get("type") == "MSAG" and not c.get("messages"):
|
||||||
|
errs.append(f"contract {c.get('name')}: a MSAG entry needs the list 'messages'")
|
||||||
if not t.get("hidden_tests") and not stop:
|
if not t.get("hidden_tests") and not stop:
|
||||||
errs.append("no hidden test class")
|
errs.append("no hidden test class")
|
||||||
return errs
|
return errs
|
||||||
@@ -399,9 +466,11 @@ def generate(task_id, pool, object_type, category, difficulty, model, base_url,
|
|||||||
pool_root = os.path.join(ROOT, "tasks_gen", pool)
|
pool_root = os.path.join(ROOT, "tasks_gen", pool)
|
||||||
task_dir = os.path.join(pool_root, task_id)
|
task_dir = os.path.join(pool_root, task_id)
|
||||||
example = bundle_of(os.path.join(ROOT, "tasks", EXAMPLE_FOR[object_type]))
|
example = bundle_of(os.path.join(ROOT, "tasks", EXAMPLE_FOR[object_type]))
|
||||||
|
note_key = "EXC" if (object_type == "CLAS" and topic and "exception class" in topic.lower()) else object_type
|
||||||
ask = (f"Write one new task.\nObject type of the main contract object: {object_type}.\n"
|
ask = (f"Write one new task.\nObject type of the main contract object: {object_type}.\n"
|
||||||
f"Skill category {category}: {CATEGORIES[category]}.\nDifficulty {difficulty} of 3.\n"
|
f"Skill category {category}: {CATEGORIES[category]}.\nDifficulty {difficulty} of 3.\n"
|
||||||
+ (f"Topic idea: {topic}\n" if topic else "Choose a new, realistic business topic.\n")
|
+ (f"Topic idea: {topic}\n" if topic else "Choose a new, realistic business topic.\n")
|
||||||
|
+ (("Format notes for this object type:\n" + TYPE_NOTES[note_key] + "\n") if note_key in TYPE_NOTES else "")
|
||||||
+ "Here is an example bundle of a different task (same format):\n" + json.dumps(example))
|
+ "Here is an example bundle of a different task (same format):\n" + json.dumps(example))
|
||||||
messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": ask}]
|
messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": ask}]
|
||||||
log = {"id": task_id, "pool": pool, "object_type": object_type, "category": category, "attempts": []}
|
log = {"id": task_id, "pool": pool, "object_type": object_type, "category": category, "attempts": []}
|
||||||
|
|||||||
79
harness/mix.py
Normal file
79
harness/mix.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""Object type mix of the training data (CLAUDE.md section 1, Opus review 2026-10-05).
|
||||||
|
|
||||||
|
Target share of the accepted tasks (and of the accepted trajectories) per "kind":
|
||||||
|
CLAS/INTF 35 % (CLAS 28, INTF 7), CDS (DDLS) 25 %, FUNC 15 %, PROG 10 %, DDIC 10 % (TABL 8, STRU 2),
|
||||||
|
MSAG + exception 5 % (MSAG 2.5, EXC 2.5).
|
||||||
|
A task has the kind of its main contract object; a CLAS whose reference inherits from CX_ is EXC.
|
||||||
|
DTEL / DOMA tasks are not made yet (the DDIC share is covered by TABL and STRU).
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
POOL = os.path.join(ROOT, "tasks_gen", "train")
|
||||||
|
TYPE_SHARE = {"CLAS": 28.0, "INTF": 7.0, "DDLS": 25.0, "FUNC": 15.0, "PROG": 10.0, "TABL": 8.0, "STRU": 2.0,
|
||||||
|
"MSAG": 2.5, "EXC": 2.5}
|
||||||
|
# categories a kind supports (docs/faz1-tasarim.md 8): the generator picks the one with the biggest deficit
|
||||||
|
KIND_CATEGORIES = {"CLAS": "ACDEFGHI", "FUNC": "ACDEFGHI", "PROG": "BCEGH", "DDLS": "BEFHI", "INTF": "A",
|
||||||
|
"TABL": "B", "STRU": "B", "MSAG": "D", "EXC": "D"}
|
||||||
|
CATEGORY_SHARE = {"A": 10, "B": 15, "C": 15, "D": 10, "E": 15, "F": 10, "G": 10, "I": 5, "H": 10}
|
||||||
|
_CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
|
def kind_of_task_dir(task_id):
|
||||||
|
"""Kind of an accepted training task (cached)."""
|
||||||
|
if task_id in _CACHE:
|
||||||
|
return _CACHE[task_id]
|
||||||
|
d = os.path.join(POOL, task_id)
|
||||||
|
try:
|
||||||
|
t = json.load(open(os.path.join(d, "task.json")))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return None
|
||||||
|
otype = t.get("object_type") or "CLAS"
|
||||||
|
for c in t.get("contract", []): # K variants and old tasks: the contract object is the truth
|
||||||
|
otype = c.get("type", otype)
|
||||||
|
break
|
||||||
|
kind = otype
|
||||||
|
if otype == "CLAS":
|
||||||
|
for o in t.get("reference", []):
|
||||||
|
if o.get("file") and o.get("name") == (t.get("contract") or [{}])[0].get("name"):
|
||||||
|
try:
|
||||||
|
src = open(os.path.join(d, o["file"])).read()
|
||||||
|
except OSError:
|
||||||
|
src = ""
|
||||||
|
if re.search(r"INHERITING\s+FROM\s+\S*CX_", src, re.I):
|
||||||
|
kind = "EXC"
|
||||||
|
_CACHE[task_id] = kind
|
||||||
|
return kind
|
||||||
|
|
||||||
|
|
||||||
|
def deficit_pick(counts, share=TYPE_SHARE, allowed=None):
|
||||||
|
"""Kind with the largest (target - actual) for the next item. counts: {kind: n}."""
|
||||||
|
total = sum(counts.values()) + 1
|
||||||
|
best, best_def = None, None
|
||||||
|
tot_share = sum(share.values())
|
||||||
|
for k, s in share.items():
|
||||||
|
if allowed and k not in allowed:
|
||||||
|
continue
|
||||||
|
d = total * s / tot_share - counts.get(k, 0)
|
||||||
|
if best_def is None or d > best_def:
|
||||||
|
best, best_def = k, d
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def accepted_task_counts(logs_dir=None):
|
||||||
|
"""{kind: accepted tasks} from the generation logs of the training pool (K variants count by their kind)."""
|
||||||
|
import glob
|
||||||
|
logs_dir = logs_dir or os.path.join(POOL, "_logs")
|
||||||
|
out = {}
|
||||||
|
for f in glob.glob(os.path.join(logs_dir, "G*.json")):
|
||||||
|
try:
|
||||||
|
l = json.load(open(f))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
continue
|
||||||
|
if l.get("accepted"):
|
||||||
|
k = kind_of_task_dir(l["id"])
|
||||||
|
if k:
|
||||||
|
out[k] = out.get(k, 0) + 1
|
||||||
|
return out
|
||||||
@@ -148,6 +148,72 @@ def mutants(src, otype, seed, n=MAX_MUTANTS):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def decl_mutants(src, otype, seed, n=MAX_MUTANTS):
|
||||||
|
"""Mutants of declarations that carry the behavior of DDIC objects and interfaces (no executable code):
|
||||||
|
TABL / STRU: field length, decimals, data type, key flag. INTF: constant values, type lengths."""
|
||||||
|
sites = [] # (kind, start, end, new, line, old)
|
||||||
|
def add(kind, m, new):
|
||||||
|
sites.append((kind, m.start(), m.end(), new, src.count("\n", 0, m.start()) + 1, m.group(0)))
|
||||||
|
if otype in ("TABL", "STRU"):
|
||||||
|
for m in re.finditer(r"abap\.(char|numc|dec|curr|quan|lang|cuky|unit)\((\d+)(?:,\s*(\d+))?\)", src, re.I):
|
||||||
|
kind_, ln, dec = m.group(1).lower(), int(m.group(2)), m.group(3)
|
||||||
|
if kind_ in ("char", "numc") and ln > 1:
|
||||||
|
add("length", m, f"abap.{kind_}({ln - 1})")
|
||||||
|
if dec is not None and int(dec) < ln - 1:
|
||||||
|
add("decimals", m, f"abap.{kind_}({ln},{int(dec) + 1})")
|
||||||
|
if kind_ == "char" and ln > 1:
|
||||||
|
add("type", m, f"abap.numc({ln})")
|
||||||
|
for m in re.finditer(r"abap\.(int4|int8|timestamp|dats|tims)\b", src, re.I):
|
||||||
|
add("type", m, {"int4": "abap.int8", "int8": "abap.int4", "timestamp": "abap.dats",
|
||||||
|
"dats": "abap.tims", "tims": "abap.dats"}[m.group(1).lower()])
|
||||||
|
for m in re.finditer(r"^(\s*)key(\s+)(?!client\b)(\w+\s*:)", src, re.I | re.M):
|
||||||
|
add("key", m, f"{m.group(1)}{m.group(3)}")
|
||||||
|
if otype == "INTF":
|
||||||
|
for m in re.finditer(r"(\bVALUE\s+)(\d+)(?=\s*\.)", src, re.I):
|
||||||
|
add("const", m, f"{m.group(1)}{int(m.group(2)) + 1}")
|
||||||
|
for m in re.finditer(r"(\bVALUE\s+)'([^'\n]{1,20})'", src, re.I):
|
||||||
|
v = m.group(2)
|
||||||
|
add("lit", m, f"{m.group(1)}'" + ("Z" if v[0] != "Z" else "Y") + v[1:] + "'")
|
||||||
|
for m in re.finditer(r"(\bVALUE\s+)(abap_true|abap_false)\b", src, re.I):
|
||||||
|
add("bool", m, m.group(1) + ("abap_false" if m.group(2).lower() == "abap_true" else "abap_true"))
|
||||||
|
for m in re.finditer(r"\bLENGTH\s+(\d+)", src, re.I):
|
||||||
|
if int(m.group(1)) > 1:
|
||||||
|
add("length", m, f"LENGTH {int(m.group(1)) - 1}")
|
||||||
|
rnd = random.Random(seed)
|
||||||
|
rnd.shuffle(sites)
|
||||||
|
chosen, kinds = [], set()
|
||||||
|
for prefer_new in (True, False):
|
||||||
|
for st in sites:
|
||||||
|
if len(chosen) >= n:
|
||||||
|
break
|
||||||
|
if st in chosen or (prefer_new and st[0] in kinds):
|
||||||
|
continue
|
||||||
|
chosen.append(st)
|
||||||
|
kinds.add(st[0])
|
||||||
|
return [(f"line {line}: {old.strip()} -> {new.strip()} ({kind})", src[:a] + new + src[b:])
|
||||||
|
for kind, a, b, new, line, old in sorted(chosen, key=lambda x: x[1])]
|
||||||
|
|
||||||
|
|
||||||
|
def msag_mutants(messages, seed, n=MAX_MUTANTS):
|
||||||
|
"""Mutants of a message class: changed text, changed placeholder, a message moved to another number."""
|
||||||
|
out = []
|
||||||
|
for i, m in enumerate(messages):
|
||||||
|
t = m.get("text", "")
|
||||||
|
mut = [dict(x) for x in messages]
|
||||||
|
mut[i]["text"] = t + " x" if len(t) < 70 else t[:-1]
|
||||||
|
out.append((f"message {m['msgno']}: text + ' x' (text)", mut))
|
||||||
|
if "&1" in t:
|
||||||
|
mut = [dict(x) for x in messages]
|
||||||
|
mut[i]["text"] = t.replace("&1", "&2", 1)
|
||||||
|
out.append((f"message {m['msgno']}: &1 -> &2 (placeholder)", mut))
|
||||||
|
if len(messages) > 1:
|
||||||
|
mut = [dict(x) for x in messages]
|
||||||
|
mut[i]["msgno"] = str(int(m["msgno"]) + 50).zfill(3)
|
||||||
|
out.append((f"message {m['msgno']}: number + 50 (number)", mut))
|
||||||
|
random.Random(seed).shuffle(out)
|
||||||
|
return out[:n]
|
||||||
|
|
||||||
|
|
||||||
def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False):
|
def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False):
|
||||||
"""Run the mutants of one task. Returns a summary dict; writes it to <task>/mutation.json."""
|
"""Run the mutants of one task. Returns a summary dict; writes it to <task>/mutation.json."""
|
||||||
task_dir = os.path.join(pool_root, task_id)
|
task_dir = os.path.join(pool_root, task_id)
|
||||||
@@ -156,14 +222,22 @@ def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False):
|
|||||||
def is_test(o):
|
def is_test(o):
|
||||||
return o["type"] == "CLAS" and re.search(r"^\s*CLASS\s+\S+\s+DEFINITION[^.]*FOR\s+TESTING",
|
return o["type"] == "CLAS" and re.search(r"^\s*CLASS\s+\S+\s+DEFINITION[^.]*FOR\s+TESTING",
|
||||||
open(os.path.join(task_dir, o["file"])).read(), re.I | re.M)
|
open(os.path.join(task_dir, o["file"])).read(), re.I | re.M)
|
||||||
targets = [o for o in meta["reference"] if o.get("file") and o["type"] in ("CLAS", "FUNC", "PROG", "DDLS")
|
targets = [o for o in meta["reference"] if o.get("file") and o["type"] in ("CLAS", "FUNC", "PROG", "DDLS", "INTF",
|
||||||
|
"TABL", "STRU")
|
||||||
and not is_test(o)]
|
and not is_test(o)]
|
||||||
|
targets += [o for o in meta["reference"] if o["type"] == "MSAG" and o.get("messages")]
|
||||||
work = os.path.join(ROOT, "runs", "gen", "mut")
|
work = os.path.join(ROOT, "runs", "gen", "mut")
|
||||||
os.makedirs(work, exist_ok=True)
|
os.makedirs(work, exist_ok=True)
|
||||||
runner = Runner(os.path.join(work, "pool"), os.path.join(work, "runs"))
|
runner = Runner(os.path.join(work, "pool"), os.path.join(work, "runs"))
|
||||||
# candidates per object, then round robin: objects without mutation sites (exception classes) give their share
|
# candidates per object, then round robin: objects without mutation sites (exception classes) give their share
|
||||||
cand = [[(o, d, m) for d, m in mutants(open(os.path.join(task_dir, o["file"])).read(), o["type"],
|
def _muts(o):
|
||||||
f"{task_id}:{o['name']}", n)] for o in targets]
|
if o["type"] == "MSAG":
|
||||||
|
return msag_mutants(o["messages"], f"{task_id}:{o['name']}", n)
|
||||||
|
src = open(os.path.join(task_dir, o["file"])).read()
|
||||||
|
if o["type"] in ("TABL", "STRU", "INTF"):
|
||||||
|
return decl_mutants(src, o["type"], f"{task_id}:{o['name']}", n)
|
||||||
|
return mutants(src, o["type"], f"{task_id}:{o['name']}", n)
|
||||||
|
cand = [[(o, d, m) for d, m in _muts(o)] for o in targets]
|
||||||
plan = []
|
plan = []
|
||||||
while len(plan) < n and any(cand):
|
while len(plan) < n and any(cand):
|
||||||
for c in cand:
|
for c in cand:
|
||||||
@@ -174,6 +248,13 @@ def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False):
|
|||||||
mdir = os.path.join(work, "pool", task_id)
|
mdir = os.path.join(work, "pool", task_id)
|
||||||
shutil.rmtree(mdir, ignore_errors=True)
|
shutil.rmtree(mdir, ignore_errors=True)
|
||||||
shutil.copytree(task_dir, mdir)
|
shutil.copytree(task_dir, mdir)
|
||||||
|
if o["type"] == "MSAG": # the mutant changes the messages of the reference in task.json
|
||||||
|
tj = json.load(open(os.path.join(mdir, "task.json")))
|
||||||
|
for r in tj["reference"]:
|
||||||
|
if r["name"] == o["name"]:
|
||||||
|
r["messages"] = msrc
|
||||||
|
json.dump(tj, open(os.path.join(mdir, "task.json"), "w"), indent=2)
|
||||||
|
else:
|
||||||
open(os.path.join(mdir, o["file"]), "w").write(msrc)
|
open(os.path.join(mdir, o["file"]), "w").write(msrc)
|
||||||
rep, _ = runner.run(task_id, OracleAgent(), run_base + k)
|
rep, _ = runner.run(task_id, OracleAgent(), run_base + k)
|
||||||
h = rep.get("hidden_tests") or {}
|
h = rep.get("hidden_tests") or {}
|
||||||
@@ -187,7 +268,7 @@ def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False):
|
|||||||
results.append({"object": o["name"], "mutant": desc, "status": status,
|
results.append({"object": o["name"], "mutant": desc, "status": status,
|
||||||
"hidden": f"{h.get('passed')}/{h.get('total')}",
|
"hidden": f"{h.get('passed')}/{h.get('total')}",
|
||||||
"failed_tests": [d["method"] for d in h.get("detail", []) if not d["ok"]]})
|
"failed_tests": [d["method"] for d in h.get("detail", []) if not d["ok"]]})
|
||||||
if keep and status == "killed": # candidate faulty reference for own-test scoring
|
if keep and status == "killed" and o["type"] != "MSAG": # candidate faulty reference for own-test scoring
|
||||||
fdir = os.path.join(task_dir, "faulty")
|
fdir = os.path.join(task_dir, "faulty")
|
||||||
os.makedirs(fdir, exist_ok=True)
|
os.makedirs(fdir, exist_ok=True)
|
||||||
open(os.path.join(fdir, f"m{k}_{os.path.basename(o['file'])}"), "w").write(msrc)
|
open(os.path.join(fdir, f"m{k}_{os.path.basename(o['file'])}"), "w").write(msrc)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .adt_client import load_env
|
from .adt_client import load_env
|
||||||
from . import trainset, trajectories
|
from . import mix, trainset, trajectories
|
||||||
from .ledger import spent
|
from .ledger import spent
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
@@ -129,10 +129,21 @@ class Pipeline:
|
|||||||
done = {(r["task"], r["attempt"]) for r in rows}
|
done = {(r["task"], r["attempt"]) for r in rows}
|
||||||
ev0 = {r["task"]: self.evaluate(r) for r in rows if r["attempt"] == 0}
|
ev0 = {r["task"]: self.evaluate(r) for r in rows if r["attempt"] == 0}
|
||||||
tasks = trajectories.accepted_tasks()
|
tasks = trajectories.accepted_tasks()
|
||||||
for t in tasks: # first attempt for every task first
|
cands = [t for t in tasks if (t, 0) not in done and (t, 0) not in self.inflight]
|
||||||
if (t, 0) not in done and (t, 0) not in self.inflight:
|
if cands: # first attempts: the kind with the biggest deficit against the target mix goes first
|
||||||
self.inflight.add((t, 0))
|
counts = {}
|
||||||
return t, 0
|
for r in rows:
|
||||||
|
if self.evaluate(r)["accepted"]:
|
||||||
|
k = mix.kind_of_task_dir(r["task"])
|
||||||
|
counts[k] = counts.get(k, 0) + 1
|
||||||
|
for (t, _a) in self.inflight:
|
||||||
|
k = mix.kind_of_task_dir(t)
|
||||||
|
counts[k] = counts.get(k, 0) + 1
|
||||||
|
total, tot_share = sum(counts.values()) + 1, sum(mix.TYPE_SHARE.values())
|
||||||
|
best = max(cands, key=lambda t: (total * mix.TYPE_SHARE.get(mix.kind_of_task_dir(t), 0) / tot_share
|
||||||
|
- counts.get(mix.kind_of_task_dir(t), 0), -int(t[1:])))
|
||||||
|
self.inflight.add((best, 0))
|
||||||
|
return best, 0
|
||||||
for t in tasks: # second attempt: first failed, or accepted without a repair
|
for t in tasks: # second attempt: first failed, or accepted without a repair
|
||||||
e = ev0.get(t)
|
e = ev0.get(t)
|
||||||
if e and (t, 1) not in done and (t, 1) not in self.inflight and (not e["accepted"] or not e["repair"]):
|
if e and (t, 1) not in done and (t, 1) not in self.inflight and (not e["accepted"] or not e["repair"]):
|
||||||
@@ -275,6 +286,25 @@ class Pipeline:
|
|||||||
a[1] += e["accepted"]
|
a[1] += e["accepted"]
|
||||||
a[2] += e["accepted"] and e["repair"]
|
a[2] += e["accepted"] and e["repair"]
|
||||||
return "; ".join(f"{k} {v[1]}/{v[0]} (repair {v[2]})" for k, v in sorted(agg.items()))
|
return "; ".join(f"{k} {v[1]}/{v[0]} (repair {v[2]})" for k, v in sorted(agg.items()))
|
||||||
|
kinds_t, kinds_r = mix.accepted_task_counts(), {}
|
||||||
|
for r, e in zip(rows, evs):
|
||||||
|
k = mix.kind_of_task_dir(r["task"])
|
||||||
|
a = kinds_r.setdefault(k, [0, 0])
|
||||||
|
a[0] += 1
|
||||||
|
a[1] += e["accepted"]
|
||||||
|
tt, tr_ = max(sum(kinds_t.values()), 1), max(sum(v[1] for v in kinds_r.values()), 1)
|
||||||
|
share_sum = sum(mix.TYPE_SHARE.values())
|
||||||
|
kind_line = "; ".join("%s tasks %d (%.0f%%) traj %d/%d (%.0f%%) target %.0f%%" % (
|
||||||
|
k, kinds_t.get(k, 0), 100.0 * kinds_t.get(k, 0) / tt, kinds_r.get(k, [0, 0])[1], kinds_r.get(k, [0, 0])[0],
|
||||||
|
100.0 * kinds_r.get(k, [0, 0])[1] / tr_, 100.0 * v / share_sum) for k, v in mix.TYPE_SHARE.items())
|
||||||
|
ddls = {}
|
||||||
|
for r, e in zip(rows, evs):
|
||||||
|
if mix.kind_of_task_dir(r["task"]) == "DDLS" and not e["accepted"]:
|
||||||
|
for w in (e["reasons"] or ["?"]):
|
||||||
|
w = w.split("=")[0] if w.startswith(("end_reason", "score")) else w
|
||||||
|
ddls[w] = ddls.get(w, 0) + 1
|
||||||
|
ddls_n = sum(1 for r in rows if mix.kind_of_task_dir(r["task"]) == "DDLS")
|
||||||
|
ddls_acc = sum(1 for r, e in zip(rows, evs) if mix.kind_of_task_dir(r["task"]) == "DDLS" and e["accepted"])
|
||||||
s = spent()
|
s = spent()
|
||||||
used = (s - BASE_LEDGER) / LEDGER_TO_USAGE
|
used = (s - BASE_LEDGER) / LEDGER_TO_USAGE
|
||||||
from .ledger import _env_budget
|
from .ledger import _env_budget
|
||||||
@@ -290,6 +320,8 @@ trajectory runs {len(rows)}, accepted trajectories {len(accepted)} (acceptance {
|
|||||||
- By category, accepted/runs: {table('category')}.
|
- By category, accepted/runs: {table('category')}.
|
||||||
- By object type, accepted/runs: {table('object_type')}.
|
- By object type, accepted/runs: {table('object_type')}.
|
||||||
- Tokens of accepted samples (20 tool schemas kept): p50 {pct(0.5)}, p90 {pct(0.9)}, p95 {pct(0.95)}, max {toks[-1] if toks else None}, n {len(toks)}.
|
- Tokens of accepted samples (20 tool schemas kept): p50 {pct(0.5)}, p90 {pct(0.9)}, p95 {pct(0.95)}, max {toks[-1] if toks else None}, n {len(toks)}.
|
||||||
|
- Object type mix (accepted tasks; accepted/runs trajectories; target): {kind_line}.
|
||||||
|
- DDLS (CDS) runs: {ddls_acc} accepted of {ddls_n}; reject reasons {ddls or 'none'} (activation, hidden tests and ATC rejections are separate gate failures, they show as score reasons).
|
||||||
- Syntax hints (proxy syntaxCheck added): {sum(e['hints'] for e in evs)} in {sum(1 for e in evs if e['hints'])} runs.
|
- Syntax hints (proxy syntaxCheck added): {sum(e['hints'] for e in evs)} in {sum(1 for e in evs if e['hints'])} runs.
|
||||||
- Harness events: {sum(self.events.values())} ({self.events or 'none'}); trajectory workers now {self.max_workers}.
|
- Harness events: {sum(self.events.values())} ({self.events or 'none'}); trajectory workers now {self.max_workers}.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ CLEAN_RULES = {
|
|||||||
}
|
}
|
||||||
DELETE_ORDER = ["CLAS", "INTF", "PROG", "FUNC", "FUGR", "SRVD", "DDLX", "DCLS", "DDLS",
|
DELETE_ORDER = ["CLAS", "INTF", "PROG", "FUNC", "FUGR", "SRVD", "DDLX", "DCLS", "DDLS",
|
||||||
"TTYP", "TABL", "STRU", "DTEL", "DOMA", "MSAG"]
|
"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):
|
def _cds_elements(src):
|
||||||
@@ -40,6 +40,13 @@ def _cds_elements(src):
|
|||||||
return out
|
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):
|
def _obj_args(otype, name, fg=None):
|
||||||
a = {"objectType": otype, "objectName": name}
|
a = {"objectType": otype, "objectName": name}
|
||||||
if fg:
|
if fg:
|
||||||
@@ -136,6 +143,9 @@ class Runner:
|
|||||||
includeType="testclasses",
|
includeType="testclasses",
|
||||||
source=o["testclasses_source"]))
|
source=o["testclasses_source"]))
|
||||||
ok = ok and not e3 and (_json(t3) or {}).get("success", False)
|
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"):
|
if o.get("run"):
|
||||||
e4, t4 = mcp.call("sap_run_class", {"className": o["name"]})
|
e4, t4 = mcp.call("sap_run_class", {"className": o["name"]})
|
||||||
ok = ok and not e4 and (_json(t4) or {}).get("success", False)
|
ok = ok and not e4 and (_json(t4) or {}).get("success", False)
|
||||||
@@ -285,9 +295,11 @@ class Runner:
|
|||||||
g2_detail = []
|
g2_detail = []
|
||||||
for c in contract:
|
for c in contract:
|
||||||
src = sources.get((c["type"], c["name"].upper()), "")
|
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):
|
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 = False
|
||||||
g2_detail.append(f"{c['name']}: does not implement {c['implements']}")
|
g2_detail.append(f"{c['name']}: does not implement {iname}")
|
||||||
if c["type"] == "FUNC": # signature: every parameter with its type in the FUNCTION header
|
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)
|
# 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)
|
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):
|
if not re.search(rf"(PARAMETERS|SELECT-OPTIONS)\s*:?[^.]*\b{re.escape(prm)}\b", src, re.I):
|
||||||
g2 = False
|
g2 = False
|
||||||
g2_detail.append(f"{c['name']}: no selection screen parameter {prm}")
|
g2_detail.append(f"{c['name']}: no selection screen parameter {prm}")
|
||||||
if c["type"] == "DDLS" and c.get("fields") and g["G1_active"]:
|
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})
|
_, 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", [])}
|
cols = {col.get("name", "").upper() for col in (_json(q) or {}).get("columns", [])}
|
||||||
if not cols: # view with parameters: SELECT without parameters fails
|
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
|
missing = {f.upper() for f in c["fields"]} - cols
|
||||||
if missing:
|
if missing:
|
||||||
g2 = False
|
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
|
# 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.
|
# 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}
|
contract_upper = {c["name"].upper() for c in contract}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .evalset import SLOTS, RELEASES, accepted_goals
|
|||||||
from .generator import ROOT, generate, make_k_variant
|
from .generator import ROOT, generate, make_k_variant
|
||||||
from .ledger import BudgetExceeded, spent
|
from .ledger import BudgetExceeded, spent
|
||||||
from . import overlap
|
from . import overlap
|
||||||
|
from . import mix
|
||||||
|
|
||||||
POOL = os.path.join(ROOT, "tasks_gen", "train")
|
POOL = os.path.join(ROOT, "tasks_gen", "train")
|
||||||
PLAN = os.path.join(POOL, "plan.json")
|
PLAN = os.path.join(POOL, "plan.json")
|
||||||
@@ -186,6 +187,137 @@ def run(part, parts, target, stop_ledger, plan_name="plan", deadline=None):
|
|||||||
print(json.dumps(log), flush=True)
|
print(json.dumps(log), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
BAL_FIRST_ID = 1910
|
||||||
|
BAL_RUN_BASE = 370000 # 40 per slot; below 466560 (a digit must lead the 4-char base36 run)
|
||||||
|
BAL_ERROR_KINDS = {"CLAS": ["named-type", "long-names"], "FUNC": ["named-type"], "DDLS": ["reserved-word"],
|
||||||
|
"TABL": ["reserved-word"], "STRU": ["reserved-word"]}
|
||||||
|
BAL_HINTS = dict(((c, t), h) for c, t, _, h in SLOTS if h)
|
||||||
|
|
||||||
|
|
||||||
|
def _claims_dir():
|
||||||
|
d = os.path.join(POOL, "_claims")
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _claim_slot():
|
||||||
|
"""Next free balanced slot number, claimed with O_EXCL (several workers). Returns (n, claim path)."""
|
||||||
|
for n in range(BAL_FIRST_ID, BAL_FIRST_ID + 600):
|
||||||
|
sid = "G%04d" % n
|
||||||
|
if os.path.exists(os.path.join(POOL, "_logs", sid + ".json")):
|
||||||
|
continue
|
||||||
|
path = os.path.join(_claims_dir(), sid + ".json")
|
||||||
|
try:
|
||||||
|
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
||||||
|
except FileExistsError:
|
||||||
|
continue
|
||||||
|
os.close(fd)
|
||||||
|
return n, path
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def _kind_stats():
|
||||||
|
"""({kind: accepted}, {kind: attempted}) from the generation logs; claims of running slots count as attempted."""
|
||||||
|
acc = mix.accepted_task_counts()
|
||||||
|
att = {}
|
||||||
|
for f in glob.glob(os.path.join(POOL, "_logs", "G*.json")):
|
||||||
|
try:
|
||||||
|
l = json.load(open(f))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
continue
|
||||||
|
k = l.get("kind") or l.get("object_type")
|
||||||
|
if k and l.get("category") != "K":
|
||||||
|
att[k] = att.get(k, 0) + 1
|
||||||
|
return acc, att
|
||||||
|
|
||||||
|
|
||||||
|
def run_balanced(part, parts, deadline):
|
||||||
|
"""Generation without a fixed plan: each slot takes the kind with the biggest deficit against mix.TYPE_SHARE.
|
||||||
|
A kind with 6 or more tries and an acceptance below 20 % is skipped (a harness or prompt problem: do not burn budget)."""
|
||||||
|
base_url = os.environ.get("LLM_BASE_URL", "http://127.0.0.1:11434/v1")
|
||||||
|
evals = overlap.load_pool("eval")
|
||||||
|
while True:
|
||||||
|
while backlog() > BACKLOG_LIMIT and not os.path.exists(STOP_FLAG) and not (deadline and time.time() > deadline):
|
||||||
|
time.sleep(120)
|
||||||
|
if os.path.exists(STOP_FLAG):
|
||||||
|
print("STOP flag", flush=True)
|
||||||
|
return
|
||||||
|
if deadline and time.time() > deadline:
|
||||||
|
print("DEADLINE", flush=True)
|
||||||
|
return
|
||||||
|
acc, att = _kind_stats()
|
||||||
|
running = {}
|
||||||
|
for f in glob.glob(os.path.join(_claims_dir(), "G*.json")):
|
||||||
|
try:
|
||||||
|
k = json.load(open(f)).get("kind")
|
||||||
|
except (OSError, ValueError):
|
||||||
|
k = None
|
||||||
|
if k:
|
||||||
|
running[k] = running.get(k, 0) + 1
|
||||||
|
counts = {k: acc.get(k, 0) + running.get(k, 0) for k in set(acc) | set(running) | set(mix.TYPE_SHARE)}
|
||||||
|
blocked = {k for k in mix.TYPE_SHARE if att.get(k, 0) >= 6 and acc.get(k, 0) < 0.2 * att.get(k, 0)}
|
||||||
|
if blocked:
|
||||||
|
print("kinds skipped (low acceptance):", sorted(blocked), flush=True)
|
||||||
|
kind = mix.deficit_pick(counts, allowed=set(mix.TYPE_SHARE) - blocked)
|
||||||
|
n, claim = _claim_slot()
|
||||||
|
if n is None:
|
||||||
|
print("no free slot", flush=True)
|
||||||
|
return
|
||||||
|
sid = "G%04d" % n
|
||||||
|
json.dump({"kind": kind}, open(claim, "w"))
|
||||||
|
otype = "CLAS" if kind == "EXC" else kind
|
||||||
|
cats = mix.KIND_CATEGORIES[kind]
|
||||||
|
logs = [json.load(open(f)) for f in glob.glob(os.path.join(POOL, "_logs", "G*.json"))]
|
||||||
|
ccount = {c: sum(1 for l in logs if l.get("accepted") and l.get("category") == c) for c in cats}
|
||||||
|
tot = sum(ccount.values()) + 1
|
||||||
|
cat = max(cats, key=lambda c: tot * mix.CATEGORY_SHARE[c] / sum(mix.CATEGORY_SHARE[x] for x in cats) - ccount[c])
|
||||||
|
idx = n - BAL_FIRST_ID
|
||||||
|
error_kind = None
|
||||||
|
if idx % 5 == 2 and kind in BAL_ERROR_KINDS: # 20 % error-targeted slots
|
||||||
|
error_kind = BAL_ERROR_KINDS[kind][(idx // 5) % len(BAL_ERROR_KINDS[kind])]
|
||||||
|
cat = ERROR_CATEGORY[error_kind] if kind in ("CLAS", "FUNC") else cat
|
||||||
|
hard = cat != "H" and idx % 10 in (3, 6, 9) # 30 % hard
|
||||||
|
topic = None
|
||||||
|
if error_kind:
|
||||||
|
topic = dict(ERROR_HINTS)[error_kind]
|
||||||
|
elif kind == "EXC":
|
||||||
|
topic = "exception class (CX_...): " + ["a domain exception with context attributes and message texts",
|
||||||
|
"an exception hierarchy with a common super class",
|
||||||
|
"an exception that wraps a previous exception"][idx % 3]
|
||||||
|
elif (cat, otype) in BAL_HINTS:
|
||||||
|
h = BAL_HINTS[(cat, otype)]
|
||||||
|
topic = h[idx % len(h)]
|
||||||
|
if cat == "G":
|
||||||
|
topic = (topic + "; " if topic else "") + f"release target {RELEASES[idx % 2]}"
|
||||||
|
avoid = [g for g in accepted_goals() if g][-170:]
|
||||||
|
full = ((topic + ". ") if topic else "Choose a new, realistic business topic. ") + \
|
||||||
|
"Do not repeat these existing topics: " + "; ".join(avoid)
|
||||||
|
pool_now = evals + overlap.load_pool("train")
|
||||||
|
|
||||||
|
def extra(b, _pool=pool_now):
|
||||||
|
hits = overlap.check(overlap.load_bundle(b), _pool)
|
||||||
|
return [f"Too close to task {i} (similarity spec {sc['spec']:.2f}, rules {sc['core']:.2f}, "
|
||||||
|
f"names {sc['name']:.2f}). Choose a different business topic and different object names."
|
||||||
|
for i, sc in hits[:3]]
|
||||||
|
try:
|
||||||
|
log = generate(sid, "train", otype, cat, 3 if hard else 2, "deepseek-v4.1-flash:cloud", base_url,
|
||||||
|
BAL_RUN_BASE + 40 * idx, full, extra_check=extra)
|
||||||
|
except BudgetExceeded as e:
|
||||||
|
print("BUDGET", e, flush=True)
|
||||||
|
os.remove(claim)
|
||||||
|
return
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
log = {"id": sid, "error": str(e)[:500]}
|
||||||
|
log.update(kind=kind, error_kind=error_kind, difficulty=3 if hard else 2, spent_total=spent())
|
||||||
|
os.makedirs(os.path.join(POOL, "_logs"), exist_ok=True)
|
||||||
|
json.dump(log, open(os.path.join(POOL, "_logs", sid + ".json"), "w"), indent=1)
|
||||||
|
stray = os.path.join(POOL, "generation.json")
|
||||||
|
if os.path.exists(stray):
|
||||||
|
os.remove(stray)
|
||||||
|
os.remove(claim)
|
||||||
|
print(json.dumps(log), flush=True)
|
||||||
|
|
||||||
|
|
||||||
K_FIRST_ID = 1300
|
K_FIRST_ID = 1300
|
||||||
K_RUN_BASE = 41800 # 20 per variant; above the trajectory run numbers (41000-41700)
|
K_RUN_BASE = 41800 # 20 per variant; above the trajectory run numbers (41000-41700)
|
||||||
K_COUNT = 18 # K share of the eval plan: 10 of 110 (9 %); counted inside the 200 accepted tasks
|
K_COUNT = 18 # K share of the eval plan: 10 of 110 (9 %); counted inside the 200 accepted tasks
|
||||||
@@ -240,7 +372,7 @@ def main():
|
|||||||
ap.add_argument("--part", type=int, default=0)
|
ap.add_argument("--part", type=int, default=0)
|
||||||
ap.add_argument("--parts", type=int, default=1)
|
ap.add_argument("--parts", type=int, default=1)
|
||||||
ap.add_argument("--target", type=int, default=200)
|
ap.add_argument("--target", type=int, default=200)
|
||||||
ap.add_argument("--plan", default="plan", help="plan (first 223 slots) or plan2 (hard and error share raised)")
|
ap.add_argument("--plan", default="plan", help="plan (first 223 slots), plan2 (hard and error share raised) or balanced (kind with the biggest deficit)")
|
||||||
ap.add_argument("--deadline", help="YYYY-MM-DDTHH:MM local time: no new slot after it")
|
ap.add_argument("--deadline", help="YYYY-MM-DDTHH:MM local time: no new slot after it")
|
||||||
ap.add_argument("--k-count", type=int, default=K_COUNT)
|
ap.add_argument("--k-count", type=int, default=K_COUNT)
|
||||||
ap.add_argument("--stop-ledger", type=float, default=27.0, help="ledger USD for this phase (10 USD usage = 27)")
|
ap.add_argument("--stop-ledger", type=float, default=27.0, help="ledger USD for this phase (10 USD usage = 27)")
|
||||||
@@ -255,6 +387,9 @@ def main():
|
|||||||
run_k(a.k_count)
|
run_k(a.k_count)
|
||||||
return
|
return
|
||||||
dl = time.mktime(time.strptime(a.deadline, "%Y-%m-%dT%H:%M")) if a.deadline else None
|
dl = time.mktime(time.strptime(a.deadline, "%Y-%m-%dT%H:%M")) if a.deadline else None
|
||||||
|
if a.plan == "balanced":
|
||||||
|
run_balanced(a.part, a.parts, dl)
|
||||||
|
return
|
||||||
run(a.part, a.parts, a.target, a.stop_ledger, a.plan, dl)
|
run(a.part, a.parts, a.target, a.stop_ledger, a.plan, dl)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
51
tasks_gen/train/G1900/faulty/m0_if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/faulty/m0_if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 9.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE '1',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE '3',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'OK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'ZONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
51
tasks_gen/train/G1900/faulty/m1_if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/faulty/m1_if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE 'Z',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE '3',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'OK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'ZONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
51
tasks_gen/train/G1900/faulty/m2_if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/faulty/m2_if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE '1',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE 'Z',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'OK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'ZONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
51
tasks_gen/train/G1900/faulty/m3_if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/faulty/m3_if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE '1',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE '3',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'ZK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'ZONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
51
tasks_gen/train/G1900/faulty/m4_if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/faulty/m4_if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE '1',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE '3',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'OK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'YONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
29
tasks_gen/train/G1900/generation.json
Normal file
29
tasks_gen/train/G1900/generation.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"id": "G1900",
|
||||||
|
"pool": "train",
|
||||||
|
"object_type": "INTF",
|
||||||
|
"category": "A",
|
||||||
|
"attempts": [
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 0,
|
||||||
|
"null": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 80.6,
|
||||||
|
"null": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 100.0,
|
||||||
|
"null": 0,
|
||||||
|
"mutation": {
|
||||||
|
"valid": 5,
|
||||||
|
"killed": 5,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
159
tasks_gen/train/G1900/hidden/parcel_fee_hidden.clas.abap
Normal file
159
tasks_gen/train/G1900/hidden/parcel_fee_hidden.clas.abap
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
CLASS {{p}}parcel_fee_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PUBLIC SECTION.
|
||||||
|
INTERFACES {{p}}if_parcel_fee.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
METHODS zone_values FOR TESTING.
|
||||||
|
METHODS reason_values FOR TESTING.
|
||||||
|
METHODS limit_values FOR TESTING.
|
||||||
|
METHODS surcharge_values FOR TESTING.
|
||||||
|
METHODS type_lengths FOR TESTING.
|
||||||
|
METHODS check_input_sig FOR TESTING.
|
||||||
|
METHODS base_fee_sig FOR TESTING.
|
||||||
|
METHODS weight_surcharge_sig FOR TESTING.
|
||||||
|
METHODS total_fee_sig FOR TESTING.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}parcel_fee_hidden IMPLEMENTATION.
|
||||||
|
METHOD {{p}}if_parcel_fee~check_input.
|
||||||
|
IF iv_weight_g < {{p}}if_parcel_fee=>c_limit-min_weight
|
||||||
|
OR iv_weight_g > {{p}}if_parcel_fee=>c_limit-max_weight.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-weight.
|
||||||
|
ELSEIF iv_zone <> {{p}}if_parcel_fee=>c_zone-home
|
||||||
|
AND iv_zone <> {{p}}if_parcel_fee=>c_zone-national
|
||||||
|
AND iv_zone <> {{p}}if_parcel_fee=>c_zone-europe.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-zone.
|
||||||
|
ELSE.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-ok.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~base_fee.
|
||||||
|
CASE iv_zone.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-home.
|
||||||
|
rv_cents = 350.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-national.
|
||||||
|
rv_cents = 620.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-europe.
|
||||||
|
rv_cents = 990.
|
||||||
|
WHEN OTHERS.
|
||||||
|
rv_cents = 0.
|
||||||
|
ENDCASE.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~weight_surcharge.
|
||||||
|
IF iv_weight_g > 1000.
|
||||||
|
rv_cents = ( iv_weight_g - 1000 + 999 ) DIV 1000
|
||||||
|
* {{p}}if_parcel_fee=>c_surcharge-per_step.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~total_fee.
|
||||||
|
IF me->{{p}}if_parcel_fee~check_input( iv_weight_g = iv_weight_g
|
||||||
|
iv_zone = iv_zone )
|
||||||
|
<> {{p}}if_parcel_fee=>c_reason-ok.
|
||||||
|
RETURN.
|
||||||
|
ENDIF.
|
||||||
|
rv_cents = me->{{p}}if_parcel_fee~base_fee( iv_zone )
|
||||||
|
+ me->{{p}}if_parcel_fee~weight_surcharge( iv_weight_g ).
|
||||||
|
IF iv_express = abap_true.
|
||||||
|
rv_cents = rv_cents + {{p}}if_parcel_fee=>c_surcharge-express.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD zone_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '1' act = {{p}}if_parcel_fee=>c_zone-home ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '2' act = {{p}}if_parcel_fee=>c_zone-national ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '3' act = {{p}}if_parcel_fee=>c_zone-europe ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD reason_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'OK' act = {{p}}if_parcel_fee=>c_reason-ok ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'WEIGHT' act = {{p}}if_parcel_fee=>c_reason-weight ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'ZONE' act = {{p}}if_parcel_fee=>c_reason-zone ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD limit_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 1 act = {{p}}if_parcel_fee=>c_limit-min_weight ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 31500 act = {{p}}if_parcel_fee=>c_limit-max_weight ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD surcharge_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 90 act = {{p}}if_parcel_fee=>c_surcharge-per_step ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 750 act = {{p}}if_parcel_fee=>c_surcharge-express ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD type_lengths.
|
||||||
|
DATA lv_zone TYPE {{p}}if_parcel_fee=>ty_zone.
|
||||||
|
DATA lv_reason TYPE {{p}}if_parcel_fee=>ty_reason.
|
||||||
|
DATA lv_weight TYPE {{p}}if_parcel_fee=>ty_weight_g.
|
||||||
|
DATA lv_cents TYPE {{p}}if_parcel_fee=>ty_cents.
|
||||||
|
DATA(lv_char1) = 'A'.
|
||||||
|
DATA(lv_char10) = 'ABCDEFGHIJ'.
|
||||||
|
DATA(lo_zone) = cl_abap_typedescr=>describe_by_data( lv_zone ).
|
||||||
|
DATA(lo_reason) = cl_abap_typedescr=>describe_by_data( lv_reason ).
|
||||||
|
DATA(lo_weight) = cl_abap_typedescr=>describe_by_data( lv_weight ).
|
||||||
|
DATA(lo_cents) = cl_abap_typedescr=>describe_by_data( lv_cents ).
|
||||||
|
DATA(lo_char1) = cl_abap_typedescr=>describe_by_data( lv_char1 ).
|
||||||
|
DATA(lo_char10) = cl_abap_typedescr=>describe_by_data( lv_char10 ).
|
||||||
|
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_char
|
||||||
|
act = lo_zone->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = lo_char1->length act = lo_zone->length ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_char
|
||||||
|
act = lo_reason->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = lo_char10->length act = lo_reason->length ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_int
|
||||||
|
act = lo_weight->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_int
|
||||||
|
act = lo_cents->type_kind ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD check_input_sig.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = {{p}}if_parcel_fee=>c_reason-ok
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 500 iv_zone = {{p}}if_parcel_fee=>c_zone-home ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = {{p}}if_parcel_fee=>c_reason-weight
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 0 iv_zone = {{p}}if_parcel_fee=>c_zone-home ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = {{p}}if_parcel_fee=>c_reason-zone
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 500 iv_zone = '9' ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD base_fee_sig.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 350
|
||||||
|
act = lo_cut->base_fee( iv_zone = {{p}}if_parcel_fee=>c_zone-home ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 620
|
||||||
|
act = lo_cut->base_fee( iv_zone = {{p}}if_parcel_fee=>c_zone-national ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 990
|
||||||
|
act = lo_cut->base_fee( iv_zone = {{p}}if_parcel_fee=>c_zone-europe ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD weight_surcharge_sig.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 1000 ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 90
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 1001 ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 180
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 2001 ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD total_fee_sig.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 530
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 2500 iv_zone = '1' iv_express = abap_false ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 1280
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 2500 iv_zone = '1' iv_express = abap_true ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 0 iv_zone = '1' iv_express = abap_false ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
55
tasks_gen/train/G1900/mutation.json
Normal file
55
tasks_gen/train/G1900/mutation.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"task": "G1900",
|
||||||
|
"mutants": [
|
||||||
|
{
|
||||||
|
"object": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"mutant": "line 5: LENGTH 10 -> LENGTH 9 (length)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/9",
|
||||||
|
"failed_tests": [
|
||||||
|
"TYPE_LENGTHS"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"mutant": "line 9: VALUE '1' -> VALUE 'Z' (lit)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "7/9",
|
||||||
|
"failed_tests": [
|
||||||
|
"TOTAL_FEE_SIG",
|
||||||
|
"ZONE_VALUES"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"mutant": "line 11: VALUE '3' -> VALUE 'Z' (lit)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/9",
|
||||||
|
"failed_tests": [
|
||||||
|
"ZONE_VALUES"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"mutant": "line 16: VALUE 'OK' -> VALUE 'ZK' (lit)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/9",
|
||||||
|
"failed_tests": [
|
||||||
|
"REASON_VALUES"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"mutant": "line 18: VALUE 'ZONE' -> VALUE 'YONE' (lit)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/9",
|
||||||
|
"failed_tests": [
|
||||||
|
"REASON_VALUES"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid": 5,
|
||||||
|
"killed": 5,
|
||||||
|
"kill_rate": 1.0,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
51
tasks_gen/train/G1900/reference/if_parcel_fee.intf.abap
Normal file
51
tasks_gen/train/G1900/reference/if_parcel_fee.intf.abap
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
INTERFACE {{p}}if_parcel_fee PUBLIC.
|
||||||
|
TYPES ty_weight_g TYPE i.
|
||||||
|
TYPES ty_cents TYPE i.
|
||||||
|
TYPES ty_zone TYPE c LENGTH 1.
|
||||||
|
TYPES ty_reason TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_zone,
|
||||||
|
home TYPE ty_zone VALUE '1',
|
||||||
|
national TYPE ty_zone VALUE '2',
|
||||||
|
europe TYPE ty_zone VALUE '3',
|
||||||
|
END OF c_zone.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_reason,
|
||||||
|
ok TYPE ty_reason VALUE 'OK',
|
||||||
|
weight TYPE ty_reason VALUE 'WEIGHT',
|
||||||
|
zone TYPE ty_reason VALUE 'ZONE',
|
||||||
|
END OF c_reason.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_limit,
|
||||||
|
min_weight TYPE ty_weight_g VALUE 1,
|
||||||
|
max_weight TYPE ty_weight_g VALUE 31500,
|
||||||
|
END OF c_limit.
|
||||||
|
|
||||||
|
CONSTANTS:
|
||||||
|
BEGIN OF c_surcharge,
|
||||||
|
per_step TYPE ty_cents VALUE 90,
|
||||||
|
express TYPE ty_cents VALUE 750,
|
||||||
|
END OF c_surcharge.
|
||||||
|
|
||||||
|
METHODS check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason.
|
||||||
|
|
||||||
|
METHODS base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
|
||||||
|
METHODS total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents.
|
||||||
|
ENDINTERFACE.
|
||||||
180
tasks_gen/train/G1900/reference/parcel_fee_test.clas.abap
Normal file
180
tasks_gen/train/G1900/reference/parcel_fee_test.clas.abap
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
CLASS {{p}}parcel_fee_test DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PUBLIC SECTION.
|
||||||
|
INTERFACES {{p}}if_parcel_fee.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
METHODS zone_values FOR TESTING.
|
||||||
|
METHODS reason_values FOR TESTING.
|
||||||
|
METHODS limit_values FOR TESTING.
|
||||||
|
METHODS surcharge_values FOR TESTING.
|
||||||
|
METHODS type_lengths FOR TESTING.
|
||||||
|
METHODS valid_input FOR TESTING.
|
||||||
|
METHODS weight_out_of_range FOR TESTING.
|
||||||
|
METHODS unknown_zone FOR TESTING.
|
||||||
|
METHODS base_fee_by_zone FOR TESTING.
|
||||||
|
METHODS surcharge_steps FOR TESTING.
|
||||||
|
METHODS total_fee_with_express FOR TESTING.
|
||||||
|
METHODS total_fee_invalid FOR TESTING.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}parcel_fee_test IMPLEMENTATION.
|
||||||
|
METHOD {{p}}if_parcel_fee~check_input.
|
||||||
|
IF iv_weight_g < {{p}}if_parcel_fee=>c_limit-min_weight
|
||||||
|
OR iv_weight_g > {{p}}if_parcel_fee=>c_limit-max_weight.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-weight.
|
||||||
|
ELSEIF iv_zone <> {{p}}if_parcel_fee=>c_zone-home
|
||||||
|
AND iv_zone <> {{p}}if_parcel_fee=>c_zone-national
|
||||||
|
AND iv_zone <> {{p}}if_parcel_fee=>c_zone-europe.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-zone.
|
||||||
|
ELSE.
|
||||||
|
rv_reason = {{p}}if_parcel_fee=>c_reason-ok.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~base_fee.
|
||||||
|
CASE iv_zone.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-home.
|
||||||
|
rv_cents = 350.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-national.
|
||||||
|
rv_cents = 620.
|
||||||
|
WHEN {{p}}if_parcel_fee=>c_zone-europe.
|
||||||
|
rv_cents = 990.
|
||||||
|
WHEN OTHERS.
|
||||||
|
rv_cents = 0.
|
||||||
|
ENDCASE.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~weight_surcharge.
|
||||||
|
IF iv_weight_g > 1000.
|
||||||
|
rv_cents = ( iv_weight_g - 1000 + 999 ) DIV 1000
|
||||||
|
* {{p}}if_parcel_fee=>c_surcharge-per_step.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD {{p}}if_parcel_fee~total_fee.
|
||||||
|
IF me->{{p}}if_parcel_fee~check_input( iv_weight_g = iv_weight_g
|
||||||
|
iv_zone = iv_zone )
|
||||||
|
<> {{p}}if_parcel_fee=>c_reason-ok.
|
||||||
|
RETURN.
|
||||||
|
ENDIF.
|
||||||
|
rv_cents = me->{{p}}if_parcel_fee~base_fee( iv_zone )
|
||||||
|
+ me->{{p}}if_parcel_fee~weight_surcharge( iv_weight_g ).
|
||||||
|
IF iv_express = abap_true.
|
||||||
|
rv_cents = rv_cents + {{p}}if_parcel_fee=>c_surcharge-express.
|
||||||
|
ENDIF.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD zone_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '1' act = {{p}}if_parcel_fee=>c_zone-home ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '2' act = {{p}}if_parcel_fee=>c_zone-national ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = '3' act = {{p}}if_parcel_fee=>c_zone-europe ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD reason_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'OK' act = {{p}}if_parcel_fee=>c_reason-ok ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'WEIGHT' act = {{p}}if_parcel_fee=>c_reason-weight ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'ZONE' act = {{p}}if_parcel_fee=>c_reason-zone ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD limit_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 1 act = {{p}}if_parcel_fee=>c_limit-min_weight ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 31500 act = {{p}}if_parcel_fee=>c_limit-max_weight ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD surcharge_values.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 90 act = {{p}}if_parcel_fee=>c_surcharge-per_step ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 750 act = {{p}}if_parcel_fee=>c_surcharge-express ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD type_lengths.
|
||||||
|
DATA lv_zone TYPE {{p}}if_parcel_fee=>ty_zone.
|
||||||
|
DATA lv_reason TYPE {{p}}if_parcel_fee=>ty_reason.
|
||||||
|
DATA lv_weight TYPE {{p}}if_parcel_fee=>ty_weight_g.
|
||||||
|
DATA lv_cents TYPE {{p}}if_parcel_fee=>ty_cents.
|
||||||
|
DATA(lv_char1) = 'A'.
|
||||||
|
DATA(lv_char10) = 'ABCDEFGHIJ'.
|
||||||
|
DATA(lo_zone) = cl_abap_typedescr=>describe_by_data( lv_zone ).
|
||||||
|
DATA(lo_reason) = cl_abap_typedescr=>describe_by_data( lv_reason ).
|
||||||
|
DATA(lo_weight) = cl_abap_typedescr=>describe_by_data( lv_weight ).
|
||||||
|
DATA(lo_cents) = cl_abap_typedescr=>describe_by_data( lv_cents ).
|
||||||
|
DATA(lo_char1) = cl_abap_typedescr=>describe_by_data( lv_char1 ).
|
||||||
|
DATA(lo_char10) = cl_abap_typedescr=>describe_by_data( lv_char10 ).
|
||||||
|
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_char
|
||||||
|
act = lo_zone->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = lo_char1->length act = lo_zone->length ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_char
|
||||||
|
act = lo_reason->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = lo_char10->length act = lo_reason->length ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_int
|
||||||
|
act = lo_weight->type_kind ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = cl_abap_typedescr=>typekind_int
|
||||||
|
act = lo_cents->type_kind ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD valid_input.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_parcel_fee=>c_reason-ok
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 1 iv_zone = '1' ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_parcel_fee=>c_reason-ok
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 31500 iv_zone = '3' ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD weight_out_of_range.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_parcel_fee=>c_reason-weight
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 0 iv_zone = '1' ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_parcel_fee=>c_reason-weight
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 31501 iv_zone = '1' ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD unknown_zone.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_parcel_fee=>c_reason-zone
|
||||||
|
act = lo_cut->check_input( iv_weight_g = 500 iv_zone = '9' ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD base_fee_by_zone.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 350 act = lo_cut->base_fee( iv_zone = '1' ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 620 act = lo_cut->base_fee( iv_zone = '2' ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 990 act = lo_cut->base_fee( iv_zone = '3' ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0 act = lo_cut->base_fee( iv_zone = '9' ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD surcharge_steps.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 1000 ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 90
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 1001 ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 90
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 2000 ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 180
|
||||||
|
act = lo_cut->weight_surcharge( iv_weight_g = 2001 ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD total_fee_with_express.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 530
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 2500 iv_zone = '1' iv_express = abap_false ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 1280
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 2500 iv_zone = '1' iv_express = abap_true ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD total_fee_invalid.
|
||||||
|
DATA lo_cut TYPE REF TO {{p}}if_parcel_fee.
|
||||||
|
lo_cut = me.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 0 iv_zone = '1' iv_express = abap_true ) ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0
|
||||||
|
act = lo_cut->total_fee( iv_weight_g = 500 iv_zone = '9' iv_express = abap_false ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
106
tasks_gen/train/G1900/spec.md
Normal file
106
tasks_gen/train/G1900/spec.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# 1. Goal
|
||||||
|
A logistics program calculates the shipping fee of a parcel. The program must not
|
||||||
|
contain the fee rules itself. It uses an interface as the contract. Create this
|
||||||
|
interface.
|
||||||
|
|
||||||
|
# 2. Open questions
|
||||||
|
None.
|
||||||
|
|
||||||
|
# 3. Context
|
||||||
|
- Package $TMP, client 001.
|
||||||
|
- The interface {{P}}IF_PARCEL_FEE does not exist yet. Create it.
|
||||||
|
- A parcel has a weight in grams, a shipping zone, and an express flag. A fee is
|
||||||
|
an amount in whole cents.
|
||||||
|
- The interface is only a contract. Other programs implement it and calculate the
|
||||||
|
fee. The interface declares four methods:
|
||||||
|
- check_input: checks the weight and the zone and returns a reason code.
|
||||||
|
- base_fee: returns the base fee of a zone.
|
||||||
|
- weight_surcharge: returns the surcharge that the weight causes.
|
||||||
|
- total_fee: returns the complete fee of a parcel.
|
||||||
|
- The meaning of the values: zone '1' is the home zone, zone '2' is the national
|
||||||
|
zone, zone '3' is the europe zone. The reason 'OK' means that the input is
|
||||||
|
valid, 'WEIGHT' means that the weight is out of range, and 'ZONE' means that the
|
||||||
|
zone is unknown. The base fee is 350 cents for zone '1', 620 cents for zone '2',
|
||||||
|
and 990 cents for zone '3'. The first 1000 grams are included in the base fee.
|
||||||
|
Each started 1000 grams above 1000 grams costs 90 cents. The express option
|
||||||
|
costs 750 cents. A call with invalid input returns the fee 0.
|
||||||
|
|
||||||
|
# 4. Contract
|
||||||
|
Create the interface {{P}}IF_PARCEL_FEE in package $TMP. The interface is public.
|
||||||
|
It contains exactly the types, the constants, and the methods below. Do not add
|
||||||
|
other components.
|
||||||
|
|
||||||
|
Types:
|
||||||
|
- ty_weight_g TYPE i
|
||||||
|
- ty_cents TYPE i
|
||||||
|
- ty_zone TYPE c LENGTH 1
|
||||||
|
- ty_reason TYPE c LENGTH 10
|
||||||
|
|
||||||
|
Constants:
|
||||||
|
- c_zone: components home, national, europe, each TYPE ty_zone
|
||||||
|
- c_reason: components ok, weight, zone, each TYPE ty_reason
|
||||||
|
- c_limit: components min_weight, max_weight, each TYPE ty_weight_g
|
||||||
|
- c_surcharge: components per_step, express, each TYPE ty_cents
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
- check_input
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_reason) TYPE ty_reason
|
||||||
|
- base_fee
|
||||||
|
IMPORTING iv_zone TYPE ty_zone
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents
|
||||||
|
- weight_surcharge
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents
|
||||||
|
- total_fee
|
||||||
|
IMPORTING iv_weight_g TYPE ty_weight_g
|
||||||
|
iv_zone TYPE ty_zone
|
||||||
|
iv_express TYPE abap_bool
|
||||||
|
RETURNING VALUE(rv_cents) TYPE ty_cents
|
||||||
|
|
||||||
|
# 5. Business rules
|
||||||
|
1. The type ty_zone is a character type with length 1. The type ty_reason is a
|
||||||
|
character type with length 10. The types ty_weight_g and ty_cents are integer
|
||||||
|
types.
|
||||||
|
2. The constant structure c_zone has the components home, national, and europe.
|
||||||
|
Each component has the type ty_zone. The values are '1', '2', and '3'.
|
||||||
|
3. The constant structure c_reason has the components ok, weight, and zone. Each
|
||||||
|
component has the type ty_reason. The values are 'OK', 'WEIGHT', and 'ZONE'.
|
||||||
|
4. The constant structure c_limit has the components min_weight and max_weight.
|
||||||
|
Each component has the type ty_weight_g. The values are 1 and 31500.
|
||||||
|
5. The constant structure c_surcharge has the components per_step and express.
|
||||||
|
Each component has the type ty_cents. The values are 90 and 750.
|
||||||
|
6. The method check_input has the importing parameter iv_weight_g of type
|
||||||
|
ty_weight_g, the importing parameter iv_zone of type ty_zone, and the returning
|
||||||
|
parameter rv_reason of type ty_reason. It returns c_reason-weight if the weight
|
||||||
|
is below c_limit-min_weight or above c_limit-max_weight. Else it returns
|
||||||
|
c_reason-zone if the zone is not one of the values of c_zone. Else it returns
|
||||||
|
c_reason-ok.
|
||||||
|
7. The method base_fee has the importing parameter iv_zone of type ty_zone and
|
||||||
|
the returning parameter rv_cents of type ty_cents. It returns 350 for zone '1',
|
||||||
|
620 for zone '2', 990 for zone '3', and 0 for every other zone.
|
||||||
|
8. The method weight_surcharge has the importing parameter iv_weight_g of type
|
||||||
|
ty_weight_g and the returning parameter rv_cents of type ty_cents. It returns 0
|
||||||
|
for a weight up to 1000 grams. For every started 1000 grams above 1000 grams it
|
||||||
|
returns c_surcharge-per_step cents.
|
||||||
|
9. The method total_fee has the importing parameters iv_weight_g of type
|
||||||
|
ty_weight_g, iv_zone of type ty_zone, and iv_express of type abap_bool, and the
|
||||||
|
returning parameter rv_cents of type ty_cents. It returns 0 if check_input does
|
||||||
|
not return c_reason-ok. Else it returns base_fee plus weight_surcharge, and it
|
||||||
|
adds c_surcharge-express if iv_express is abap_true.
|
||||||
|
|
||||||
|
# 6. Constraints
|
||||||
|
- Release target: SAP_BASIS 816 (SAP ABAP Platform 2025).
|
||||||
|
- Coding standards: Clean ABAP. No global variables. No comment that restates the
|
||||||
|
code.
|
||||||
|
- Out of scope: no database table, no CDS view, no function module, no report, and
|
||||||
|
no application class.
|
||||||
|
|
||||||
|
# 7. Acceptance
|
||||||
|
- The interface {{P}}IF_PARCEL_FEE is active and has no syntax error.
|
||||||
|
- The hidden tests pass.
|
||||||
|
- Write your own ABAP Unit tests for the interface. An interface has no test
|
||||||
|
include, so create one global test class in package $TMP. The test class
|
||||||
|
implements the interface and checks the constants, the type lengths, and the
|
||||||
|
method signatures. Keep all test code in this one global class.
|
||||||
52
tasks_gen/train/G1900/task.json
Normal file
52
tasks_gen/train/G1900/task.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"id": "G1900",
|
||||||
|
"category": "A",
|
||||||
|
"object_type": "INTF",
|
||||||
|
"difficulty": 2,
|
||||||
|
"release_target": "v816",
|
||||||
|
"expected_outcome": "implement",
|
||||||
|
"budget": {
|
||||||
|
"max_tool_calls": 60,
|
||||||
|
"max_activations": 15
|
||||||
|
},
|
||||||
|
"seed": [],
|
||||||
|
"contract": [
|
||||||
|
{
|
||||||
|
"type": "INTF",
|
||||||
|
"name": "{{P}}IF_PARCEL_FEE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"out_of_scope": [
|
||||||
|
"No database table",
|
||||||
|
"No CDS view",
|
||||||
|
"No function module",
|
||||||
|
"No report",
|
||||||
|
"No application class"
|
||||||
|
],
|
||||||
|
"hidden_tests": [
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}PARCEL_FEE_HIDDEN",
|
||||||
|
"file": "hidden/parcel_fee_hidden.clas.abap",
|
||||||
|
"description": "Contract tests for {{P}}IF_PARCEL_FEE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reference": [
|
||||||
|
{
|
||||||
|
"type": "INTF",
|
||||||
|
"name": "{{P}}IF_PARCEL_FEE",
|
||||||
|
"file": "reference/if_parcel_fee.intf.abap",
|
||||||
|
"description": "Parcel fee contract"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}PARCEL_FEE_TEST",
|
||||||
|
"file": "reference/parcel_fee_test.clas.abap",
|
||||||
|
"description": "Own tests for the interface"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craft_checks": [
|
||||||
|
"method_length",
|
||||||
|
"no_global_variables"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
tasks_gen/train/G1901/faulty/m1_stock.tabl.asabap
Normal file
16
tasks_gen/train/G1901/faulty/m1_stock.tabl.asabap
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@EndUserText.label : 'Bin stock'
|
||||||
|
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT
|
||||||
|
@AbapCatalog.deliveryClass : #A
|
||||||
|
@AbapCatalog.dataMaintenance : #RESTRICTED
|
||||||
|
define table {{p}}stock {
|
||||||
|
key client : abap.clnt not null;
|
||||||
|
key bin_id : abap.numc(8) not null;
|
||||||
|
key article_id : abap.char(12) not null;
|
||||||
|
@Semantics.quantity.unitOfMeasure : '{{p}}stock.unit_of_measure'
|
||||||
|
quantity : abap.dec(13,3);
|
||||||
|
unit_of_measure : abap.unit(3);
|
||||||
|
shelf_no : abap.numc(4);
|
||||||
|
last_count_date : abap.dats;
|
||||||
|
blocked : abap.char(1);
|
||||||
|
}
|
||||||
16
tasks_gen/train/G1901/faulty/m2_stock.tabl.asabap
Normal file
16
tasks_gen/train/G1901/faulty/m2_stock.tabl.asabap
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@EndUserText.label : 'Bin stock'
|
||||||
|
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT
|
||||||
|
@AbapCatalog.deliveryClass : #A
|
||||||
|
@AbapCatalog.dataMaintenance : #RESTRICTED
|
||||||
|
define table {{p}}stock {
|
||||||
|
key client : abap.clnt not null;
|
||||||
|
key bin_id : abap.char(8) not null;
|
||||||
|
key article_id : abap.char(12) not null;
|
||||||
|
@Semantics.quantity.unitOfMeasure : '{{p}}stock.unit_of_measure'
|
||||||
|
quantity : abap.dec(13,4);
|
||||||
|
unit_of_measure : abap.unit(3);
|
||||||
|
shelf_no : abap.numc(4);
|
||||||
|
last_count_date : abap.dats;
|
||||||
|
blocked : abap.char(1);
|
||||||
|
}
|
||||||
16
tasks_gen/train/G1901/faulty/m3_stock.tabl.asabap
Normal file
16
tasks_gen/train/G1901/faulty/m3_stock.tabl.asabap
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@EndUserText.label : 'Bin stock'
|
||||||
|
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT
|
||||||
|
@AbapCatalog.deliveryClass : #A
|
||||||
|
@AbapCatalog.dataMaintenance : #RESTRICTED
|
||||||
|
define table {{p}}stock {
|
||||||
|
key client : abap.clnt not null;
|
||||||
|
key bin_id : abap.char(8) not null;
|
||||||
|
key article_id : abap.char(12) not null;
|
||||||
|
@Semantics.quantity.unitOfMeasure : '{{p}}stock.unit_of_measure'
|
||||||
|
quantity : abap.dec(13,3);
|
||||||
|
unit_of_measure : abap.unit(3);
|
||||||
|
shelf_no : abap.numc(3);
|
||||||
|
last_count_date : abap.dats;
|
||||||
|
blocked : abap.char(1);
|
||||||
|
}
|
||||||
16
tasks_gen/train/G1901/faulty/m4_stock.tabl.asabap
Normal file
16
tasks_gen/train/G1901/faulty/m4_stock.tabl.asabap
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@EndUserText.label : 'Bin stock'
|
||||||
|
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT
|
||||||
|
@AbapCatalog.deliveryClass : #A
|
||||||
|
@AbapCatalog.dataMaintenance : #RESTRICTED
|
||||||
|
define table {{p}}stock {
|
||||||
|
key client : abap.clnt not null;
|
||||||
|
key bin_id : abap.char(8) not null;
|
||||||
|
key article_id : abap.char(12) not null;
|
||||||
|
@Semantics.quantity.unitOfMeasure : '{{p}}stock.unit_of_measure'
|
||||||
|
quantity : abap.dec(13,3);
|
||||||
|
unit_of_measure : abap.unit(3);
|
||||||
|
shelf_no : abap.numc(4);
|
||||||
|
last_count_date : abap.tims;
|
||||||
|
blocked : abap.char(1);
|
||||||
|
}
|
||||||
19
tasks_gen/train/G1901/generation.json
Normal file
19
tasks_gen/train/G1901/generation.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"id": "G1901",
|
||||||
|
"pool": "train",
|
||||||
|
"object_type": "TABL",
|
||||||
|
"category": "B",
|
||||||
|
"attempts": [
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 100.0,
|
||||||
|
"null": 0,
|
||||||
|
"mutation": {
|
||||||
|
"valid": 4,
|
||||||
|
"killed": 4,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
182
tasks_gen/train/G1901/hidden/t20_hidden.clas.abap
Normal file
182
tasks_gen/train/G1901/hidden/t20_hidden.clas.abap
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
CLASS {{p}}t20_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
CLASS-DATA go_env TYPE REF TO if_osql_test_environment.
|
||||||
|
CLASS-METHODS class_setup.
|
||||||
|
CLASS-METHODS class_teardown.
|
||||||
|
METHODS setup.
|
||||||
|
METHODS fields_exist FOR TESTING.
|
||||||
|
METHODS key_fields FOR TESTING.
|
||||||
|
METHODS field_lengths FOR TESTING.
|
||||||
|
METHODS field_types FOR TESTING.
|
||||||
|
METHODS insert_and_read FOR TESTING.
|
||||||
|
METHODS decimal_roundtrip FOR TESTING.
|
||||||
|
METHODS duplicate_key FOR TESTING.
|
||||||
|
METHODS other_key_allowed FOR TESTING.
|
||||||
|
METHODS get_fields RETURNING VALUE(rt_fields) TYPE ddfields.
|
||||||
|
METHODS field_of IMPORTING it_fields TYPE ddfields
|
||||||
|
iv_name TYPE string
|
||||||
|
RETURNING VALUE(rs_field) TYPE dfies.
|
||||||
|
METHODS assert_has_field IMPORTING it_fields TYPE ddfields
|
||||||
|
iv_name TYPE string.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}t20_hidden IMPLEMENTATION.
|
||||||
|
|
||||||
|
METHOD class_setup.
|
||||||
|
go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}STOCK' ) ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD class_teardown.
|
||||||
|
go_env->destroy( ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD setup.
|
||||||
|
go_env->clear_doubles( ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD get_fields.
|
||||||
|
DATA(lo_descr) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_name( '{{P}}STOCK' ) ).
|
||||||
|
rt_fields = lo_descr->get_ddic_field_list( ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD field_of.
|
||||||
|
LOOP AT it_fields INTO DATA(ls_field).
|
||||||
|
IF to_upper( ls_field-fieldname ) = to_upper( iv_name ).
|
||||||
|
rs_field = ls_field.
|
||||||
|
RETURN.
|
||||||
|
ENDIF.
|
||||||
|
ENDLOOP.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD assert_has_field.
|
||||||
|
cl_abap_unit_assert=>assert_true(
|
||||||
|
act = boolc( field_of( it_fields = it_fields iv_name = iv_name )-fieldname IS NOT INITIAL )
|
||||||
|
msg = |Field { iv_name } is missing| ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD fields_exist.
|
||||||
|
DATA(lt_fields) = get_fields( ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 8 act = lines( lt_fields ) ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'CLIENT' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'BIN_ID' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'ARTICLE_ID' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'QUANTITY' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'UNIT_OF_MEASURE' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'SHELF_NO' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'LAST_COUNT_DATE' ).
|
||||||
|
assert_has_field( it_fields = lt_fields iv_name = 'BLOCKED' ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD key_fields.
|
||||||
|
DATA(lt_fields) = get_fields( ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'X' act = field_of( it_fields = lt_fields iv_name = 'CLIENT' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'X' act = field_of( it_fields = lt_fields iv_name = 'BIN_ID' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'X' act = field_of( it_fields = lt_fields iv_name = 'ARTICLE_ID' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ' ' act = field_of( it_fields = lt_fields iv_name = 'QUANTITY' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ' ' act = field_of( it_fields = lt_fields iv_name = 'UNIT_OF_MEASURE' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ' ' act = field_of( it_fields = lt_fields iv_name = 'SHELF_NO' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ' ' act = field_of( it_fields = lt_fields iv_name = 'LAST_COUNT_DATE' )-keyflag ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ' ' act = field_of( it_fields = lt_fields iv_name = 'BLOCKED' )-keyflag ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD field_lengths.
|
||||||
|
DATA(lt_fields) = get_fields( ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 3 act = field_of( it_fields = lt_fields iv_name = 'CLIENT' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 8 act = field_of( it_fields = lt_fields iv_name = 'BIN_ID' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 12 act = field_of( it_fields = lt_fields iv_name = 'ARTICLE_ID' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 13 act = field_of( it_fields = lt_fields iv_name = 'QUANTITY' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 3 act = field_of( it_fields = lt_fields iv_name = 'UNIT_OF_MEASURE' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 4 act = field_of( it_fields = lt_fields iv_name = 'SHELF_NO' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 8 act = field_of( it_fields = lt_fields iv_name = 'LAST_COUNT_DATE' )-leng ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 1 act = field_of( it_fields = lt_fields iv_name = 'BLOCKED' )-leng ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD field_types.
|
||||||
|
DATA(lt_fields) = get_fields( ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'CLNT' act = field_of( it_fields = lt_fields iv_name = 'CLIENT' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'CHAR' act = field_of( it_fields = lt_fields iv_name = 'BIN_ID' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'CHAR' act = field_of( it_fields = lt_fields iv_name = 'ARTICLE_ID' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'P' act = field_of( it_fields = lt_fields iv_name = 'QUANTITY' )-inttype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 3 act = field_of( it_fields = lt_fields iv_name = 'QUANTITY' )-decimals ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'UNIT' act = field_of( it_fields = lt_fields iv_name = 'UNIT_OF_MEASURE' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'NUMC' act = field_of( it_fields = lt_fields iv_name = 'SHELF_NO' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'DATS' act = field_of( it_fields = lt_fields iv_name = 'LAST_COUNT_DATE' )-datatype ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'CHAR' act = field_of( it_fields = lt_fields iv_name = 'BLOCKED' )-datatype ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD insert_and_read.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA ls_read TYPE {{p}}stock.
|
||||||
|
DATA lv_subrc TYPE sy-subrc.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0001'.
|
||||||
|
ls_row-article_id = 'ART-000001'.
|
||||||
|
ls_row-quantity = '12.500'.
|
||||||
|
ls_row-unit_of_measure = 'ST'.
|
||||||
|
ls_row-shelf_no = '0042'.
|
||||||
|
ls_row-last_count_date = '20250115'.
|
||||||
|
ls_row-blocked = 'X'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_subrc ).
|
||||||
|
SELECT SINGLE * FROM {{p}}stock
|
||||||
|
WHERE bin_id = 'BIN-0001' AND article_id = 'ART-000001'
|
||||||
|
INTO @ls_read.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-quantity act = ls_read-quantity ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-unit_of_measure act = ls_read-unit_of_measure ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-shelf_no act = ls_read-shelf_no ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-last_count_date act = ls_read-last_count_date ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-blocked act = ls_read-blocked ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-client act = ls_read-client ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD decimal_roundtrip.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA lv_quantity TYPE {{p}}stock-quantity.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0002'.
|
||||||
|
ls_row-article_id = 'ART-000002'.
|
||||||
|
ls_row-quantity = '0.125'.
|
||||||
|
ls_row-unit_of_measure = 'KG'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
SELECT SINGLE quantity FROM {{p}}stock
|
||||||
|
WHERE bin_id = 'BIN-0002' AND article_id = 'ART-000002'
|
||||||
|
INTO @lv_quantity.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = CONV decfloat34( '0.125' )
|
||||||
|
act = CONV decfloat34( lv_quantity ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD duplicate_key.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA lv_subrc TYPE sy-subrc.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0003'.
|
||||||
|
ls_row-article_id = 'ART-000003'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_subrc ).
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 4 act = lv_subrc ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD other_key_allowed.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA lv_subrc TYPE sy-subrc.
|
||||||
|
DATA lv_count TYPE i.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0004'.
|
||||||
|
ls_row-article_id = 'ART-000004'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
ls_row-article_id = 'ART-000005'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_subrc ).
|
||||||
|
SELECT COUNT(*) FROM {{p}}stock INTO @lv_count.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_count ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
ENDCLASS.
|
||||||
55
tasks_gen/train/G1901/mutation.json
Normal file
55
tasks_gen/train/G1901/mutation.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"task": "G1901",
|
||||||
|
"mutants": [
|
||||||
|
{
|
||||||
|
"object": "{{P}}STOCK",
|
||||||
|
"mutant": "line 8: key bin_id : -> bin_id : (key)",
|
||||||
|
"status": "invalid",
|
||||||
|
"hidden": "0/0",
|
||||||
|
"failed_tests": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}STOCK",
|
||||||
|
"mutant": "line 8: abap.char(8) -> abap.numc(8) (type)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "5/8",
|
||||||
|
"failed_tests": [
|
||||||
|
"DECIMAL_ROUNDTRIP",
|
||||||
|
"FIELD_TYPES",
|
||||||
|
"INSERT_AND_READ"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}STOCK",
|
||||||
|
"mutant": "line 11: abap.dec(13,3) -> abap.dec(13,4) (decimals)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "7/8",
|
||||||
|
"failed_tests": [
|
||||||
|
"FIELD_TYPES"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}STOCK",
|
||||||
|
"mutant": "line 13: abap.numc(4) -> abap.numc(3) (length)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "7/8",
|
||||||
|
"failed_tests": [
|
||||||
|
"FIELD_LENGTHS"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}STOCK",
|
||||||
|
"mutant": "line 14: abap.dats -> abap.tims (type)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "6/8",
|
||||||
|
"failed_tests": [
|
||||||
|
"FIELD_LENGTHS",
|
||||||
|
"FIELD_TYPES"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid": 4,
|
||||||
|
"killed": 4,
|
||||||
|
"kill_rate": 1.0,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
16
tasks_gen/train/G1901/reference/stock.tabl.asabap
Normal file
16
tasks_gen/train/G1901/reference/stock.tabl.asabap
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@EndUserText.label : 'Bin stock'
|
||||||
|
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
||||||
|
@AbapCatalog.tableCategory : #TRANSPARENT
|
||||||
|
@AbapCatalog.deliveryClass : #A
|
||||||
|
@AbapCatalog.dataMaintenance : #RESTRICTED
|
||||||
|
define table {{p}}stock {
|
||||||
|
key client : abap.clnt not null;
|
||||||
|
key bin_id : abap.char(8) not null;
|
||||||
|
key article_id : abap.char(12) not null;
|
||||||
|
@Semantics.quantity.unitOfMeasure : '{{p}}stock.unit_of_measure'
|
||||||
|
quantity : abap.dec(13,3);
|
||||||
|
unit_of_measure : abap.unit(3);
|
||||||
|
shelf_no : abap.numc(4);
|
||||||
|
last_count_date : abap.dats;
|
||||||
|
blocked : abap.char(1);
|
||||||
|
}
|
||||||
80
tasks_gen/train/G1901/reference/t20_test.clas.abap
Normal file
80
tasks_gen/train/G1901/reference/t20_test.clas.abap
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
CLASS {{p}}t20_test DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
CLASS-DATA go_env TYPE REF TO if_osql_test_environment.
|
||||||
|
CLASS-METHODS class_setup.
|
||||||
|
CLASS-METHODS class_teardown.
|
||||||
|
METHODS setup.
|
||||||
|
METHODS stores_row FOR TESTING.
|
||||||
|
METHODS rejects_duplicate FOR TESTING.
|
||||||
|
METHODS keeps_three_decimals FOR TESTING.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}t20_test IMPLEMENTATION.
|
||||||
|
|
||||||
|
METHOD class_setup.
|
||||||
|
go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}STOCK' ) ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD class_teardown.
|
||||||
|
go_env->destroy( ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD setup.
|
||||||
|
go_env->clear_doubles( ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD stores_row.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA ls_read TYPE {{p}}stock.
|
||||||
|
DATA lv_subrc TYPE sy-subrc.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0001'.
|
||||||
|
ls_row-article_id = 'ART-000001'.
|
||||||
|
ls_row-quantity = '12.500'.
|
||||||
|
ls_row-unit_of_measure = 'ST'.
|
||||||
|
ls_row-shelf_no = '0042'.
|
||||||
|
ls_row-last_count_date = '20250115'.
|
||||||
|
ls_row-blocked = 'X'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_subrc ).
|
||||||
|
SELECT SINGLE * FROM {{p}}stock
|
||||||
|
WHERE bin_id = 'BIN-0001' AND article_id = 'ART-000001'
|
||||||
|
INTO @ls_read.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-quantity act = ls_read-quantity ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-unit_of_measure act = ls_read-unit_of_measure ).
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = ls_row-blocked act = ls_read-blocked ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD rejects_duplicate.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA lv_subrc TYPE sy-subrc.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0002'.
|
||||||
|
ls_row-article_id = 'ART-000002'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
lv_subrc = sy-subrc.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 4 act = lv_subrc ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD keeps_three_decimals.
|
||||||
|
DATA ls_row TYPE {{p}}stock.
|
||||||
|
DATA lv_quantity TYPE {{p}}stock-quantity.
|
||||||
|
ls_row-client = sy-mandt.
|
||||||
|
ls_row-bin_id = 'BIN-0003'.
|
||||||
|
ls_row-article_id = 'ART-000003'.
|
||||||
|
ls_row-quantity = '0.125'.
|
||||||
|
ls_row-unit_of_measure = 'KG'.
|
||||||
|
INSERT {{p}}stock FROM @ls_row.
|
||||||
|
SELECT SINGLE quantity FROM {{p}}stock
|
||||||
|
WHERE bin_id = 'BIN-0003' AND article_id = 'ART-000003'
|
||||||
|
INTO @lv_quantity.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = CONV decfloat34( '0.125' )
|
||||||
|
act = CONV decfloat34( lv_quantity ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
ENDCLASS.
|
||||||
36
tasks_gen/train/G1901/spec.md
Normal file
36
tasks_gen/train/G1901/spec.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 1. Goal
|
||||||
|
The warehouse team counts the stock of each article in each storage bin. The team needs one table that stores the result of these counts. Other programs read the table, so the structure of the table is fixed.
|
||||||
|
|
||||||
|
# 2. Open questions
|
||||||
|
None.
|
||||||
|
|
||||||
|
# 3. Context
|
||||||
|
- System: SAP ABAP Platform 2025 (SAP_BASIS 816), client 001.
|
||||||
|
- Package: $TMP.
|
||||||
|
- No table for this data exists. Do not use tables of SAP application components.
|
||||||
|
|
||||||
|
# 4. Contract
|
||||||
|
- Create the transparent database table {{P}}STOCK in package $TMP.
|
||||||
|
- The table contains these fields: CLIENT, BIN_ID, ARTICLE_ID, QUANTITY, UNIT_OF_MEASURE, SHELF_NO, LAST_COUNT_DATE, BLOCKED.
|
||||||
|
- Key fields: CLIENT, BIN_ID, ARTICLE_ID.
|
||||||
|
|
||||||
|
# 5. Business rules
|
||||||
|
1. One row of the table is the stock of one article in one storage bin of one client. The key fields CLIENT, BIN_ID and ARTICLE_ID identify the row. A second row with the same key values is not allowed.
|
||||||
|
2. CLIENT: data type CLNT, length 3. Key field. Not null.
|
||||||
|
3. BIN_ID: data type CHAR, length 8. Key field. Not null. The number of the storage bin.
|
||||||
|
4. ARTICLE_ID: data type CHAR, length 12. Key field. Not null. The number of the article.
|
||||||
|
5. QUANTITY: data type DEC, length 13, 3 decimals. The stock quantity. It is a quantity in the unit of measure that UNIT_OF_MEASURE contains.
|
||||||
|
6. UNIT_OF_MEASURE: data type UNIT, length 3. The unit of measure of QUANTITY.
|
||||||
|
7. SHELF_NO: data type NUMC, length 4. The shelf number in the storage bin.
|
||||||
|
8. LAST_COUNT_DATE: data type DATS. The date of the last count.
|
||||||
|
9. BLOCKED: data type CHAR, length 1. The value 'X' means that the storage bin is blocked for withdrawals. The value is initial if the storage bin is not blocked.
|
||||||
|
|
||||||
|
# 6. Constraints
|
||||||
|
- Release target: SAP_BASIS 816 (ABAP Platform 2025).
|
||||||
|
- Coding standards: Clean ABAP. Use ABAP SQL and ABAP Unit.
|
||||||
|
- Out of scope: do not create a CDS view, a report or a function module. Do not change other objects.
|
||||||
|
|
||||||
|
# 7. Acceptance
|
||||||
|
- The table {{P}}STOCK is active and has no syntax error.
|
||||||
|
- The hidden tests pass.
|
||||||
|
- Write ABAP Unit tests with CL_OSQL_TEST_ENVIRONMENT in a global test class.
|
||||||
53
tasks_gen/train/G1901/task.json
Normal file
53
tasks_gen/train/G1901/task.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"id": "G1901",
|
||||||
|
"category": "B",
|
||||||
|
"object_type": "TABL",
|
||||||
|
"difficulty": 2,
|
||||||
|
"release_target": "v816",
|
||||||
|
"expected_outcome": "implement",
|
||||||
|
"budget": {
|
||||||
|
"max_tool_calls": 60,
|
||||||
|
"max_activations": 15
|
||||||
|
},
|
||||||
|
"seed": [],
|
||||||
|
"contract": [
|
||||||
|
{
|
||||||
|
"type": "TABL",
|
||||||
|
"name": "{{P}}STOCK",
|
||||||
|
"fields": [
|
||||||
|
"client",
|
||||||
|
"bin_id",
|
||||||
|
"article_id",
|
||||||
|
"quantity",
|
||||||
|
"unit_of_measure",
|
||||||
|
"shelf_no",
|
||||||
|
"last_count_date",
|
||||||
|
"blocked"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"out_of_scope": [],
|
||||||
|
"hidden_tests": [
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}T20_HIDDEN",
|
||||||
|
"file": "hidden/t20_hidden.clas.abap",
|
||||||
|
"description": "T20 hidden tests"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reference": [
|
||||||
|
{
|
||||||
|
"type": "TABL",
|
||||||
|
"name": "{{P}}STOCK",
|
||||||
|
"file": "reference/stock.tabl.asabap",
|
||||||
|
"description": "Bin stock table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}T20_TEST",
|
||||||
|
"file": "reference/t20_test.clas.abap",
|
||||||
|
"description": "T20 own tests"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craft_checks": []
|
||||||
|
}
|
||||||
24
tasks_gen/train/G1902/generation.json
Normal file
24
tasks_gen/train/G1902/generation.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"id": "G1902",
|
||||||
|
"pool": "train",
|
||||||
|
"object_type": "MSAG",
|
||||||
|
"category": "D",
|
||||||
|
"attempts": [
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 85.0,
|
||||||
|
"null": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stage": "validate",
|
||||||
|
"oracle": 100.0,
|
||||||
|
"null": 0,
|
||||||
|
"mutation": {
|
||||||
|
"valid": 5,
|
||||||
|
"killed": 5,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
97
tasks_gen/train/G1902/hidden/t41_hidden.clas.abap
Normal file
97
tasks_gen/train/G1902/hidden/t41_hidden.clas.abap
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
CLASS {{p}}t41_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
METHODS msg_001_text FOR TESTING.
|
||||||
|
METHODS msg_002_text FOR TESTING.
|
||||||
|
METHODS msg_003_text FOR TESTING.
|
||||||
|
METHODS msg_004_text FOR TESTING.
|
||||||
|
METHODS msg_005_text FOR TESTING.
|
||||||
|
METHODS placeholder_substitution FOR TESTING.
|
||||||
|
METHODS exception_text_two_args FOR TESTING.
|
||||||
|
METHODS exception_text_one_arg FOR TESTING.
|
||||||
|
METHODS exception_is_catchable FOR TESTING.
|
||||||
|
METHODS exception_is_static_check FOR TESTING.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}t41_hidden IMPLEMENTATION.
|
||||||
|
METHOD msg_001_text.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '001' WITH 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'Course ABAP101 is fully booked' act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_002_text.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '002' WITH 'P-100' 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Participant P-100 is already booked for course ABAP101'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_003_text.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '003' WITH 'ABAP101' '3' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Course ABAP101 has only 3 free seats left'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_004_text.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '004' WITH 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Booking for course ABAP101 is confirmed'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_005_text.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '005' WITH 'ABAP101' '20250301' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Course ABAP101 was cancelled on 20250301'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD placeholder_substitution.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '001' WITH 'ABAP202' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'Course ABAP202 is fully booked' act = lv_text ).
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '001' WITH 'ABAP303' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'Course ABAP303 is fully booked' act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_text_two_args.
|
||||||
|
DATA(lo_cx) = NEW {{p}}cx_booking( iv_msgno = '002' iv_arg1 = 'P-100' iv_arg2 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Participant P-100 is already booked for course ABAP101'
|
||||||
|
act = lo_cx->get_text( ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_text_one_arg.
|
||||||
|
DATA(lo_cx) = NEW {{p}}cx_booking( iv_msgno = '004' iv_arg1 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Booking for course ABAP101 is confirmed'
|
||||||
|
act = lo_cx->get_text( ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_is_catchable.
|
||||||
|
DATA lv_caught TYPE abap_bool.
|
||||||
|
TRY.
|
||||||
|
RAISE EXCEPTION TYPE {{p}}cx_booking
|
||||||
|
EXPORTING iv_msgno = '003' iv_arg1 = 'ABAP101' iv_arg2 = '1'.
|
||||||
|
CATCH {{p}}cx_booking INTO DATA(lo_cx).
|
||||||
|
lv_caught = abap_true.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Course ABAP101 has only 1 free seats left'
|
||||||
|
act = lo_cx->get_text( ) ).
|
||||||
|
ENDTRY.
|
||||||
|
cl_abap_unit_assert=>assert_true( lv_caught ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_is_static_check.
|
||||||
|
DATA lo_cx TYPE REF TO cx_static_check.
|
||||||
|
lo_cx = NEW {{p}}cx_booking( iv_msgno = '001' iv_arg1 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_bound( lo_cx ).
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
58
tasks_gen/train/G1902/mutation.json
Normal file
58
tasks_gen/train/G1902/mutation.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"task": "G1902",
|
||||||
|
"mutants": [
|
||||||
|
{
|
||||||
|
"object": "{{P}}MSG",
|
||||||
|
"mutant": "message 005: &1 -> &2 (placeholder)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "9/10",
|
||||||
|
"failed_tests": [
|
||||||
|
"MSG_005_TEXT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}MSG",
|
||||||
|
"mutant": "message 003: &1 -> &2 (placeholder)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/10",
|
||||||
|
"failed_tests": [
|
||||||
|
"EXCEPTION_IS_CATCHABLE",
|
||||||
|
"MSG_003_TEXT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}MSG",
|
||||||
|
"mutant": "message 004: &1 -> &2 (placeholder)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/10",
|
||||||
|
"failed_tests": [
|
||||||
|
"EXCEPTION_TEXT_ONE_ARG",
|
||||||
|
"MSG_004_TEXT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}MSG",
|
||||||
|
"mutant": "message 002: text + ' x' (text)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/10",
|
||||||
|
"failed_tests": [
|
||||||
|
"EXCEPTION_TEXT_TWO_ARGS",
|
||||||
|
"MSG_002_TEXT"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "{{P}}MSG",
|
||||||
|
"mutant": "message 001: text + ' x' (text)",
|
||||||
|
"status": "killed",
|
||||||
|
"hidden": "8/10",
|
||||||
|
"failed_tests": [
|
||||||
|
"MSG_001_TEXT",
|
||||||
|
"PLACEHOLDER_SUBSTITUTION"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid": 5,
|
||||||
|
"killed": 5,
|
||||||
|
"kill_rate": 1.0,
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
26
tasks_gen/train/G1902/reference/cx_booking.clas.abap
Normal file
26
tasks_gen/train/G1902/reference/cx_booking.clas.abap
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
CLASS {{p}}cx_booking DEFINITION PUBLIC INHERITING FROM cx_static_check FINAL CREATE PUBLIC.
|
||||||
|
PUBLIC SECTION.
|
||||||
|
METHODS constructor
|
||||||
|
IMPORTING iv_msgno TYPE symsgno
|
||||||
|
iv_arg1 TYPE string DEFAULT ''
|
||||||
|
iv_arg2 TYPE string DEFAULT ''.
|
||||||
|
METHODS if_message~get_text REDEFINITION.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
DATA mv_msgno TYPE symsgno.
|
||||||
|
DATA mv_arg1 TYPE string.
|
||||||
|
DATA mv_arg2 TYPE string.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}cx_booking IMPLEMENTATION.
|
||||||
|
METHOD constructor.
|
||||||
|
super->constructor( ).
|
||||||
|
mv_msgno = iv_msgno.
|
||||||
|
mv_arg1 = iv_arg1.
|
||||||
|
mv_arg2 = iv_arg2.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD if_message~get_text.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER mv_msgno WITH mv_arg1 mv_arg2 INTO result.
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
73
tasks_gen/train/G1902/reference/t41_test.clas.abap
Normal file
73
tasks_gen/train/G1902/reference/t41_test.clas.abap
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
CLASS {{p}}t41_test DEFINITION PUBLIC FINAL CREATE PUBLIC
|
||||||
|
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
METHODS msg_001 FOR TESTING.
|
||||||
|
METHODS msg_002 FOR TESTING.
|
||||||
|
METHODS msg_003 FOR TESTING.
|
||||||
|
METHODS msg_004 FOR TESTING.
|
||||||
|
METHODS msg_005 FOR TESTING.
|
||||||
|
METHODS exception_two_args FOR TESTING.
|
||||||
|
METHODS exception_one_arg FOR TESTING.
|
||||||
|
METHODS exception_is_static_check FOR TESTING.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}t41_test IMPLEMENTATION.
|
||||||
|
METHOD msg_001.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '001' WITH 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals( exp = 'Course ABAP101 is fully booked' act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_002.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '002' WITH 'P-100' 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Participant P-100 is already booked for course ABAP101'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_003.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '003' WITH 'ABAP101' '3' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Course ABAP101 has only 3 free seats left'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_004.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '004' WITH 'ABAP101' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Booking for course ABAP101 is confirmed'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD msg_005.
|
||||||
|
DATA lv_text TYPE string.
|
||||||
|
MESSAGE ID '{{P}}MSG' TYPE 'E' NUMBER '005' WITH 'ABAP101' '20250301' INTO lv_text.
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Course ABAP101 was cancelled on 20250301'
|
||||||
|
act = lv_text ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_two_args.
|
||||||
|
DATA(lo_cx) = NEW {{p}}cx_booking( iv_msgno = '002' iv_arg1 = 'P-100' iv_arg2 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Participant P-100 is already booked for course ABAP101'
|
||||||
|
act = lo_cx->get_text( ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_one_arg.
|
||||||
|
DATA(lo_cx) = NEW {{p}}cx_booking( iv_msgno = '004' iv_arg1 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_equals(
|
||||||
|
exp = 'Booking for course ABAP101 is confirmed'
|
||||||
|
act = lo_cx->get_text( ) ).
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD exception_is_static_check.
|
||||||
|
DATA lo_cx TYPE REF TO cx_static_check.
|
||||||
|
lo_cx = NEW {{p}}cx_booking( iv_msgno = '001' iv_arg1 = 'ABAP101' ).
|
||||||
|
cl_abap_unit_assert=>assert_bound( lo_cx ).
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
19
tasks_gen/train/G1902/seed/cx_legacy.clas.abap
Normal file
19
tasks_gen/train/G1902/seed/cx_legacy.clas.abap
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
CLASS {{p}}cx_legacy DEFINITION PUBLIC INHERITING FROM cx_static_check FINAL CREATE PUBLIC.
|
||||||
|
PUBLIC SECTION.
|
||||||
|
METHODS constructor IMPORTING iv_text TYPE string.
|
||||||
|
METHODS if_message~get_text REDEFINITION.
|
||||||
|
PRIVATE SECTION.
|
||||||
|
DATA mv_text TYPE string.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
|
||||||
|
CLASS {{p}}cx_legacy IMPLEMENTATION.
|
||||||
|
METHOD constructor.
|
||||||
|
super->constructor( ).
|
||||||
|
mv_text = iv_text.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
|
METHOD if_message~get_text.
|
||||||
|
result = mv_text.
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
69
tasks_gen/train/G1902/spec.md
Normal file
69
tasks_gen/train/G1902/spec.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# 1. Goal
|
||||||
|
|
||||||
|
Create the message class and the class-based exception for the training course
|
||||||
|
booking service. The booking service reports every problem with a message from
|
||||||
|
the message class. The exception class carries the text of such a message.
|
||||||
|
|
||||||
|
# 2. Open questions
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
# 3. Context
|
||||||
|
|
||||||
|
- All objects are in package $TMP.
|
||||||
|
- The booking service books participants into training courses.
|
||||||
|
- The old exception class {{P}}CX_LEGACY exists. It takes a hard-coded text and
|
||||||
|
does not use a message class. The new exception class replaces it.
|
||||||
|
- All texts of the booking service come from one message class.
|
||||||
|
|
||||||
|
# 4. Contract
|
||||||
|
|
||||||
|
- Create the message class {{P}}MSG with the messages of section 5.
|
||||||
|
- Create the exception class {{P}}CX_BOOKING:
|
||||||
|
- It is a class-based exception and inherits from CX_STATIC_CHECK.
|
||||||
|
- The constructor has these parameters:
|
||||||
|
- iv_msgno TYPE symsgno
|
||||||
|
- iv_arg1 TYPE string, default ''
|
||||||
|
- iv_arg2 TYPE string, default ''
|
||||||
|
- The method GET_TEXT of the interface IF_MESSAGE returns the text of the
|
||||||
|
message with the number iv_msgno from {{P}}MSG. The placeholder &1 is
|
||||||
|
replaced by iv_arg1 and the placeholder &2 is replaced by iv_arg2.
|
||||||
|
|
||||||
|
# 5. Business rules
|
||||||
|
|
||||||
|
Message class {{P}}MSG:
|
||||||
|
|
||||||
|
1. Message 001, error message, text "Course &1 is fully booked". The booking
|
||||||
|
service uses it when a course has no free seat.
|
||||||
|
2. Message 002, error message, text "Participant &1 is already booked for
|
||||||
|
course &2". The booking service uses it for a double booking.
|
||||||
|
3. Message 003, warning message, text "Course &1 has only &2 free seats left".
|
||||||
|
The booking service uses it when a course has few free seats left.
|
||||||
|
4. Message 004, information message, text "Booking for course &1 is
|
||||||
|
confirmed". The booking service uses it for a successful booking.
|
||||||
|
5. Message 005, warning message, text "Course &1 was cancelled on &2". The
|
||||||
|
booking service uses it for a cancelled course.
|
||||||
|
|
||||||
|
Exception class {{P}}CX_BOOKING:
|
||||||
|
|
||||||
|
6. The constructor stores the message number and the two placeholders.
|
||||||
|
7. GET_TEXT returns the text of the message with the number iv_msgno from
|
||||||
|
{{P}}MSG. The placeholder &1 is replaced by iv_arg1 and the placeholder &2 is
|
||||||
|
replaced by iv_arg2.
|
||||||
|
|
||||||
|
# 6. Constraints
|
||||||
|
|
||||||
|
- Release target: SAP_BASIS 816 (ABAP Platform 2025).
|
||||||
|
- Coding standards: Clean ABAP. Keep every method below 40 statements. Do not
|
||||||
|
produce ATC findings of priority 1 or 2.
|
||||||
|
- Out of scope: database tables, CDS views, reports, dynpro, and the old
|
||||||
|
exception class {{P}}CX_LEGACY.
|
||||||
|
|
||||||
|
# 7. Acceptance
|
||||||
|
|
||||||
|
- The message class {{P}}MSG is active. It contains the messages 001 to 005 with
|
||||||
|
the exact texts of section 5.
|
||||||
|
- The exception class {{P}}CX_BOOKING is active and has no syntax error.
|
||||||
|
- The hidden tests pass.
|
||||||
|
- Write ABAP Unit tests for the messages and the exception class in a global
|
||||||
|
test class.
|
||||||
103
tasks_gen/train/G1902/task.json
Normal file
103
tasks_gen/train/G1902/task.json
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"id": "G1902",
|
||||||
|
"category": "D",
|
||||||
|
"object_type": "MSAG",
|
||||||
|
"difficulty": 2,
|
||||||
|
"release_target": "v816",
|
||||||
|
"expected_outcome": "implement",
|
||||||
|
"budget": {
|
||||||
|
"max_tool_calls": 60,
|
||||||
|
"max_activations": 15
|
||||||
|
},
|
||||||
|
"seed": [
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}CX_LEGACY",
|
||||||
|
"file": "seed/cx_legacy.clas.abap",
|
||||||
|
"description": "Old exception class with a hard-coded text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contract": [
|
||||||
|
{
|
||||||
|
"type": "MSAG",
|
||||||
|
"name": "{{P}}MSG",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"msgno": "001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "004"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "005"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}CX_BOOKING",
|
||||||
|
"description": "Class-based exception (CX_STATIC_CHECK). Constructor IMPORTING iv_msgno TYPE symsgno, iv_arg1 TYPE string DEFAULT '', iv_arg2 TYPE string DEFAULT ''. GET_TEXT of IF_MESSAGE returns the text of message iv_msgno from {{P}}MSG with &1 = iv_arg1 and &2 = iv_arg2."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"out_of_scope": [
|
||||||
|
"{{P}}CX_LEGACY"
|
||||||
|
],
|
||||||
|
"hidden_tests": [
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}T41_HIDDEN",
|
||||||
|
"file": "hidden/t41_hidden.clas.abap",
|
||||||
|
"description": "T41 hidden tests"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reference": [
|
||||||
|
{
|
||||||
|
"type": "MSAG",
|
||||||
|
"name": "{{P}}MSG",
|
||||||
|
"description": "Booking messages",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"msgno": "001",
|
||||||
|
"text": "Course &1 is fully booked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "002",
|
||||||
|
"text": "Participant &1 is already booked for course &2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "003",
|
||||||
|
"text": "Course &1 has only &2 free seats left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "004",
|
||||||
|
"text": "Booking for course &1 is confirmed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"msgno": "005",
|
||||||
|
"text": "Course &1 was cancelled on &2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}CX_BOOKING",
|
||||||
|
"file": "reference/cx_booking.clas.abap",
|
||||||
|
"description": "Booking exception class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CLAS",
|
||||||
|
"name": "{{P}}T41_TEST",
|
||||||
|
"file": "reference/t41_test.clas.abap",
|
||||||
|
"description": "T41 own tests"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"craft_checks": [
|
||||||
|
"method_length"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user