Files
celery/sexp_effects/test_interpreter.py
giles bb458aa924 Replace batch DAG system with streaming architecture
- Remove legacy_tasks.py, hybrid_state.py, render.py
- Remove old task modules (analyze, execute, execute_sexp, orchestrate)
- Add streaming interpreter from test repo
- Add sexp_effects with primitives and video effects
- Add streaming Celery task with CID-based asset resolution
- Support both CID and friendly name references for assets
- Add .dockerignore to prevent local clones from conflicting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 19:10:11 +00:00

237 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
Test the S-expression effect interpreter.
"""
import numpy as np
import sys
from pathlib import Path
# Add parent to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from sexp_effects import (
get_interpreter,
load_effects_dir,
run_effect,
list_effects,
parse,
)
def test_parser():
"""Test S-expression parser."""
print("Testing parser...")
# Simple expressions
assert parse("42") == 42
assert parse("3.14") == 3.14
assert parse('"hello"') == "hello"
assert parse("true") == True
# Lists
assert parse("(+ 1 2)")[0].name == "+"
assert parse("(+ 1 2)")[1] == 1
# Nested
expr = parse("(define x (+ 1 2))")
assert expr[0].name == "define"
print(" Parser OK")
def test_interpreter_basics():
"""Test basic interpreter operations."""
print("Testing interpreter basics...")
interp = get_interpreter()
# Math
assert interp.eval(parse("(+ 1 2)")) == 3
assert interp.eval(parse("(* 3 4)")) == 12
assert interp.eval(parse("(- 10 3)")) == 7
# Comparison
assert interp.eval(parse("(< 1 2)")) == True
assert interp.eval(parse("(> 1 2)")) == False
# Let binding
assert interp.eval(parse("(let ((x 5)) x)")) == 5
assert interp.eval(parse("(let ((x 5) (y 3)) (+ x y))")) == 8
# Lambda
result = interp.eval(parse("((lambda (x) (* x 2)) 5)"))
assert result == 10
# If
assert interp.eval(parse("(if true 1 2)")) == 1
assert interp.eval(parse("(if false 1 2)")) == 2
print(" Interpreter basics OK")
def test_primitives():
"""Test image primitives."""
print("Testing primitives...")
interp = get_interpreter()
# Create test image
img = np.zeros((100, 100, 3), dtype=np.uint8)
img[50, 50] = [255, 128, 64]
interp.global_env.set('test_img', img)
# Width/height
assert interp.eval(parse("(width test_img)")) == 100
assert interp.eval(parse("(height test_img)")) == 100
# Pixel
pixel = interp.eval(parse("(pixel test_img 50 50)"))
assert pixel == [255, 128, 64]
# RGB
color = interp.eval(parse("(rgb 100 150 200)"))
assert color == [100, 150, 200]
# Luminance
lum = interp.eval(parse("(luminance (rgb 100 100 100))"))
assert abs(lum - 100) < 1
print(" Primitives OK")
def test_effect_loading():
"""Test loading effects from .sexp files."""
print("Testing effect loading...")
# Load all effects
effects_dir = Path(__file__).parent / "effects"
load_effects_dir(str(effects_dir))
effects = list_effects()
print(f" Loaded {len(effects)} effects: {', '.join(sorted(effects))}")
assert len(effects) > 0
print(" Effect loading OK")
def test_effect_execution():
"""Test running effects on images."""
print("Testing effect execution...")
# Create test image
img = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
# Load effects
effects_dir = Path(__file__).parent / "effects"
load_effects_dir(str(effects_dir))
# Test each effect
effects = list_effects()
passed = 0
failed = []
for name in sorted(effects):
try:
result, state = run_effect(name, img.copy(), {'_time': 0.5}, {})
assert isinstance(result, np.ndarray)
assert result.shape == img.shape
passed += 1
print(f" {name}: OK")
except Exception as e:
failed.append((name, str(e)))
print(f" {name}: FAILED - {e}")
print(f" Passed: {passed}/{len(effects)}")
if failed:
print(f" Failed: {[f[0] for f in failed]}")
return passed, failed
def test_ascii_fx_zone():
"""Test ascii_fx_zone effect with zone expressions."""
print("Testing ascii_fx_zone...")
interp = get_interpreter()
# Load the effect
effects_dir = Path(__file__).parent / "effects"
load_effects_dir(str(effects_dir))
# Create gradient test frame
frame = np.zeros((120, 160, 3), dtype=np.uint8)
for x in range(160):
frame[:, x] = int(x / 160 * 255)
frame = np.stack([frame[:,:,0]]*3, axis=2)
# Test 1: Basic without expressions
result, _ = run_effect('ascii_fx_zone', frame, {'cols': 20}, {})
assert result.shape == frame.shape
print(" Basic run: OK")
# Test 2: With zone-lum expression
expr = parse('(* zone-lum 180)')
result, _ = run_effect('ascii_fx_zone', frame, {
'cols': 20,
'char_hue': expr
}, {})
assert result.shape == frame.shape
print(" Zone-lum expression: OK")
# Test 3: With multiple expressions
scale_expr = parse('(+ 0.5 (* zone-lum 0.5))')
rot_expr = parse('(* zone-row-norm 30)')
result, _ = run_effect('ascii_fx_zone', frame, {
'cols': 20,
'char_scale': scale_expr,
'char_rotation': rot_expr
}, {})
assert result.shape == frame.shape
print(" Multiple expressions: OK")
# Test 4: With numeric literals
result, _ = run_effect('ascii_fx_zone', frame, {
'cols': 20,
'char_hue': 90,
'char_scale': 1.2
}, {})
assert result.shape == frame.shape
print(" Numeric literals: OK")
# Test 5: Zone position expressions
col_expr = parse('(* zone-col-norm 360)')
result, _ = run_effect('ascii_fx_zone', frame, {
'cols': 20,
'char_hue': col_expr
}, {})
assert result.shape == frame.shape
print(" Zone position expression: OK")
print(" ascii_fx_zone OK")
def main():
print("=" * 60)
print("S-Expression Effect Interpreter Tests")
print("=" * 60)
test_parser()
test_interpreter_basics()
test_primitives()
test_effect_loading()
test_ascii_fx_zone()
passed, failed = test_effect_execution()
print("=" * 60)
if not failed:
print("All tests passed!")
else:
print(f"Tests completed with {len(failed)} failures")
print("=" * 60)
if __name__ == "__main__":
main()