229 lines
8.4 KiB
Python
229 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple MBAP test harness.
|
|
- Loads `data/raw/code/mbap.json`
|
|
- For each entry with `code` and `test_list`, runs a heuristic mock AVAP executor
|
|
- Compares returned `result` against each test's expected value and prints a summary
|
|
|
|
Note: This is a heuristic mock executor — it supports many common AVAP idioms but
|
|
is not a full AVAP interpreter. It aims to be useful for quick verification.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
import hashlib
|
|
import random
|
|
import string
|
|
from pathlib import Path
|
|
|
|
MBAP_PATH = Path(__file__).resolve().parents[1] / "data" / "raw" / "code" / "mbap.json"
|
|
|
|
|
|
def load_mbap(path: Path):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def transform_avap_to_python(code: str) -> str:
|
|
lines = code.splitlines()
|
|
out_lines = []
|
|
for raw in lines:
|
|
line = raw.strip()
|
|
if not line:
|
|
continue
|
|
# remove addParam(...) calls
|
|
if re.match(r'addParam\(', line):
|
|
continue
|
|
# getListLen(var, lenvar) -> lenvar = len(var)
|
|
m = re.match(r'getListLen\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append(f"{m.group(2).strip()} = len({m.group(1).strip()})")
|
|
continue
|
|
# itemFromList(list, idx, var) -> var = list[idx]
|
|
m = re.match(r'itemFromList\(([^,]+),\s*([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append(f"{m.group(3).strip()} = {m.group(1).strip()}[{m.group(2).strip()}]")
|
|
continue
|
|
# AddVariableToJSON(idx, value, result) -> result.append(value)
|
|
m = re.match(r'AddVariableToJSON\(([^,]+),\s*([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append(f"{m.group(3).strip()}.append({m.group(2).strip()})")
|
|
continue
|
|
# startLoop(i, s, e) -> for i in range(s, e+1):
|
|
m = re.match(r'startLoop\(([^,]+),\s*([^,]+),\s*([^,]+)\)', line)
|
|
if m:
|
|
out_lines.append(f"for {m.group(1).strip()} in range({m.group(2).strip()}, {m.group(3).strip()}+1):")
|
|
continue
|
|
# simple else/end tokens
|
|
if line.startswith('else()'):
|
|
out_lines.append('else:')
|
|
continue
|
|
if line.startswith('endLoop') or line == 'end()':
|
|
continue
|
|
# if(a, b, "op")
|
|
m = re.match(r'if\(([^,]*),\s*([^,]*),\s*"([^"]+)"\)', line)
|
|
if m:
|
|
a = m.group(1).strip()
|
|
b = m.group(2).strip()
|
|
op = m.group(3).strip()
|
|
if op in ('==', '!=', '>', '<', '>=', '<='):
|
|
out_lines.append(f"if {a} {op} {b}:")
|
|
else:
|
|
# treat as raw expression
|
|
out_lines.append(f"if {op}:")
|
|
continue
|
|
# addResult(x) -> output = x
|
|
m = re.match(r'addResult\(([^)]+)\)', line)
|
|
if m:
|
|
out_lines.append(f"output = {m.group(1).strip()}")
|
|
continue
|
|
# encodeSHA256(src, dst)
|
|
m = re.match(r'encodeSHA256\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append("import hashlib")
|
|
out_lines.append(f"{m.group(2)} = hashlib.sha256(str({m.group(1)}).encode()).hexdigest()")
|
|
continue
|
|
# encodeMD5
|
|
m = re.match(r'encodeMD5\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append("import hashlib")
|
|
out_lines.append(f"{m.group(2)} = hashlib.md5(str({m.group(1)}).encode()).hexdigest()")
|
|
continue
|
|
# randomString(len, token)
|
|
m = re.match(r'randomString\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append(f"{m.group(2)} = ''.join(__random.choice(__letters) for _ in range(int({m.group(1)})))")
|
|
continue
|
|
# getRegex(text, pattern, matches) -> use python re.findall
|
|
m = re.match(r'getRegex\(([^,]+),\s*"([^"]+)",\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
|
|
if m:
|
|
out_lines.append(f"{m.group(3)} = re.findall(r'{m.group(2)}', str({m.group(1)}))")
|
|
continue
|
|
# RequestGet/Post -> mock
|
|
if line.startswith('RequestGet(') or line.startswith('RequestPost('):
|
|
out_lines.append("response = {'_mock': 'response'}")
|
|
out_lines.append("output = response")
|
|
continue
|
|
# function foo(...) { -> def foo(...):
|
|
m = re.match(r'function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*\{', line)
|
|
if m:
|
|
args = m.group(2).strip()
|
|
out_lines.append(f"def {m.group(1)}({args}):")
|
|
continue
|
|
# return(...) -> return ...
|
|
m = re.match(r'return\(([^)]+)\)', line)
|
|
if m:
|
|
out_lines.append(f"return {m.group(1).strip()}")
|
|
continue
|
|
# simple replacements to make many lines valid python-ish
|
|
line = line.replace('&&', 'and').replace('||', 'or')
|
|
# remove trailing semicolons if any
|
|
if line.endswith(';'):
|
|
line = line[:-1]
|
|
out_lines.append(line)
|
|
# Ensure we always expose 'output' variable at the end if not set
|
|
py = '\n'.join(out_lines)
|
|
guard = "\nif 'output' not in locals():\n output = locals().get('result', None)\n"
|
|
return py + guard
|
|
|
|
|
|
def mock_execute(code: str, inputs: dict):
|
|
"""Heuristic mock executor:
|
|
- Creates a restricted locals dict with provided `inputs`
|
|
- Transforms AVAP-ish code into Python-ish code
|
|
- Executes it and returns {'result': output} or {'error': msg}
|
|
"""
|
|
# prepare locals
|
|
globs = {"__builtins__": {}}
|
|
locals_: dict = {}
|
|
# expose helpers
|
|
locals_['re'] = re
|
|
locals_['hashlib'] = hashlib
|
|
locals_['__random'] = random
|
|
locals_['__letters'] = string.ascii_letters + string.digits
|
|
# copy input params into locals
|
|
if isinstance(inputs, dict):
|
|
for k, v in inputs.items():
|
|
locals_[k] = v
|
|
# transform
|
|
py = transform_avap_to_python(code)
|
|
try:
|
|
exec(py, globs, locals_)
|
|
output = locals_.get('output', None)
|
|
return {'result': output}
|
|
except Exception as e:
|
|
return {'error': str(e), 'transformed': py}
|
|
|
|
|
|
def canonical_expected(test_item):
|
|
"""Try to extract expected value from a test item.
|
|
Accepts several common shapes: {'input':..., 'expected':...},
|
|
{'in':..., 'out':...}, or simple dicts.
|
|
"""
|
|
if isinstance(test_item, dict):
|
|
if 'expected' in test_item:
|
|
return test_item['expected']
|
|
if 'output' in test_item:
|
|
return test_item['output']
|
|
if 'result' in test_item:
|
|
return test_item['result']
|
|
# some datasets embed expected as the last item
|
|
if 'expected_result' in test_item:
|
|
return test_item['expected_result']
|
|
return None
|
|
|
|
|
|
def canonical_input(test_item):
|
|
if isinstance(test_item, dict):
|
|
if 'input' in test_item:
|
|
return test_item['input']
|
|
if 'in' in test_item:
|
|
return test_item['in']
|
|
# if dict and contains params keys other than expected, assume it's the input itself
|
|
# heuristics: if contains keys other than 'expected'/'output' treat as params
|
|
keys = set(test_item.keys())
|
|
if not keys.intersection({'expected','output','result','description'}):
|
|
return test_item
|
|
return {}
|
|
|
|
|
|
def run_all(path: Path):
|
|
data = load_mbap(path)
|
|
total = 0
|
|
passed = 0
|
|
for entry in data:
|
|
task_id = entry.get('task_id')
|
|
code = entry.get('code')
|
|
tests = entry.get('test_list') or []
|
|
if not code:
|
|
continue
|
|
print(f"Task {task_id}: processing {len(tests)} tests")
|
|
for ti, test_item in enumerate(tests):
|
|
total += 1
|
|
inp = canonical_input(test_item)
|
|
expected = canonical_expected(test_item)
|
|
res = mock_execute(code, inp)
|
|
if 'error' in res:
|
|
print(f" test #{ti+1}: ERROR -> {res['error']}")
|
|
# optionally show transformed code for debugging
|
|
# print(res.get('transformed'))
|
|
continue
|
|
got = res.get('result')
|
|
ok = (expected is None) or (got == expected)
|
|
if ok:
|
|
passed += 1
|
|
status = 'PASS'
|
|
else:
|
|
status = 'FAIL'
|
|
print(f" test #{ti+1}: {status} | expected={expected!r} got={got!r}")
|
|
print(f"\nSummary: passed {passed}/{total} tests")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
p = MBAP_PATH
|
|
if not p.exists():
|
|
print(f"mbap.json not found at {p}")
|
|
sys.exit(1)
|
|
run_all(p)
|