SilverSight/tests/test_avm_python.py
allaun f6cbddcbf2 feat(tests): Python AVM port rewrite + test harness, Go test harness
Python port rewritten to match spec:
- Added Q0_16, PUSH_Q0, PUSH_BOOL as separate opcodes
- Added V6 comparison (lt_q16_v6)
- Added floor division (Lean Int.ediv)
- Added stack depth limit (AVM_MAX_STACK = 1024)
- Added type checking in exec_prim
- All 10 tests passing

Go AVM port: added test_avm_test.go with 8 test cases

Milestone: Python → , Go → 🔄
2026-06-30 17:56:09 -05:00

95 lines
3.6 KiB
Python

"""
AVM ISA v1 — Cross-Validated Test Harness (Python)
"""
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python'))
from avm import Instr, Prim, State, run, push_q16, push_bool, push_q0, prim, pop, dup, swap, store, load, \
halt, jump, jump_if, make_instr, Q16_SCALE, AVM_CLAMP_MIN, AVM_CLAMP_MAX
AVM_CLAMP_MAX = 2147483647
AVM_MAX_STACK = 1024
def S(nl=0): return State(pc=0, stack=[], locals=[None]*nl, halted=False)
def test_basic_add():
"""5 + 3 = 8"""
prog = [push_q16(5 * Q16_SCALE), push_q16(3 * Q16_SCALE), prim(Prim.ADD_SAT_Q16), halt()]
s = run(S(), prog, 100)
assert s.halted
assert s.stack[0].val == 8 * Q16_SCALE, f"Got {s.stack[0].val}"
print(" ✅ basic_add: 5 + 3 = 8")
def test_div_q16():
"""3 / 5 = 0.6 in Q16_16"""
prog = [push_q16(3 * Q16_SCALE), push_q16(5 * Q16_SCALE), prim(Prim.DIV_SAT_Q16), halt()]
s = run(S(), prog, 100)
expected = (3 * Q16_SCALE) // 5 # floor(0.6 * 65536) = 39321
assert s.stack[0].val == expected, f"Got {s.stack[0].val}, expected {expected}"
print(" ✅ div_q16: 3/5 = 0.6")
def test_saturation():
"""max-1 + 2 → max"""
prog = [push_q16(AVM_CLAMP_MAX - 1), push_q16(2), prim(Prim.ADD_SAT_Q16), halt()]
s = run(S(), prog, 100)
assert s.stack[0].val == AVM_CLAMP_MAX
print(" ✅ saturation: max-1 + 2 = max")
def test_v6_comparison():
cases = [(-5, -3, True), (-3, -5, False), (5, 3, False), (3, 5, True), (-1, 2, True)]
for a, b, exp in cases:
prog = [push_q16(a * Q16_SCALE), push_q16(b * Q16_SCALE), prim(Prim.LT_Q16), halt()]
s = run(S(), prog, 100)
assert s.stack[0].val == exp, f"ltQ16({a},{b}): exp {exp}, got {s.stack[0].val}"
print(" ✅ v6_comparison: 5 cases pass")
def test_type_mismatch():
prog = [push_bool(True), push_q16(Q16_SCALE), prim(Prim.ADD_SAT_Q16)]
try:
run(S(), prog, 100)
return "❌ type_mismatch: should fail"
except (TypeError, ValueError):
print(" ✅ type_mismatch: rejected")
def test_stack_overflow():
prog = [push_q16(0)] * (AVM_MAX_STACK + 1)
try:
run(S(), prog, 10000)
print(" ❌ stack_overflow: should fail")
except ValueError:
print(" ✅ stack_overflow: rejected")
def test_division_by_zero():
prog = [push_q16(Q16_SCALE), push_q16(0), prim(Prim.DIV_SAT_Q16)]
try:
run(S(), prog, 100)
print(" ❌ div_by_zero: should fail")
except (ValueError, ZeroDivisionError):
print(" ✅ div_by_zero: rejected")
def test_control_flow():
prog = [push_bool(True), jump_if(4), push_q16(0), halt(), push_q16(Q16_SCALE), halt()]
s = run(S(), prog, 100)
assert s.stack[0].val == Q16_SCALE
print(" ✅ control_flow: jump_if true")
def test_locals():
prog = [push_q16(42 * Q16_SCALE), store(0), load(0), halt()]
s = run(S(1), prog, 100)
assert s.stack[0].val == 42 * Q16_SCALE
print(" ✅ locals: store+load")
def test_mul_div_rt():
prog = [push_q16(5*Q16_SCALE), push_q16(3*Q16_SCALE), prim(Prim.MUL_SAT_Q16),
push_q16(3*Q16_SCALE), prim(Prim.DIV_SAT_Q16), halt()]
s = run(S(), prog, 100)
assert s.stack[0].val == 5 * Q16_SCALE
print(" ✅ mul_div_roundtrip: 5*3/3 = 5")
if __name__ == "__main__":
print("AVM Python — Test Harness")
print("=========================")
for fn in [test_basic_add, test_div_q16, test_saturation, test_v6_comparison,
test_type_mismatch, test_stack_overflow, test_division_by_zero,
test_control_flow, test_locals, test_mul_div_rt]:
fn()
print("\nAll Python tests passed.")