McRogueFace/tests/unit/shader_test.py
2026-01-25 21:04:01 -05:00

422 lines
13 KiB
Python

#!/usr/bin/env python3
"""Unit tests for the Shader system (Issue #106)
Tests cover:
- Shader creation and compilation
- Static uniforms (float, vec2, vec3, vec4)
- PropertyBinding for dynamic uniform values
- CallableBinding for computed uniform values
- Shader assignment to various drawable types
- Dynamic flag propagation
"""
import mcrfpy
import sys
def test_shader_creation():
"""Test basic shader creation"""
print("Testing shader creation...")
# Valid shader
shader = mcrfpy.Shader('''
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''')
assert shader is not None, "Shader should be created"
assert shader.is_valid, "Shader should be valid"
assert shader.dynamic == False, "Shader should not be dynamic by default"
# Dynamic shader
dynamic_shader = mcrfpy.Shader('''
uniform sampler2D texture;
uniform float time;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''', dynamic=True)
assert dynamic_shader.dynamic == True, "Shader should be dynamic when specified"
print(" PASS: Basic shader creation works")
def test_shader_source():
"""Test that shader source is stored correctly"""
print("Testing shader source storage...")
source = '''uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}'''
shader = mcrfpy.Shader(source)
assert source in shader.source, "Shader source should be stored"
print(" PASS: Shader source is stored")
def test_static_uniforms():
"""Test static uniform values"""
print("Testing static uniforms...")
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
# Test float uniform
frame.uniforms['intensity'] = 0.5
assert abs(frame.uniforms['intensity'] - 0.5) < 0.001, "Float uniform should match"
# Test vec2 uniform
frame.uniforms['offset'] = (10.0, 20.0)
val = frame.uniforms['offset']
assert len(val) == 2, "Vec2 should have 2 components"
assert abs(val[0] - 10.0) < 0.001, "Vec2.x should match"
assert abs(val[1] - 20.0) < 0.001, "Vec2.y should match"
# Test vec3 uniform
frame.uniforms['color_rgb'] = (1.0, 0.5, 0.0)
val = frame.uniforms['color_rgb']
assert len(val) == 3, "Vec3 should have 3 components"
# Test vec4 uniform
frame.uniforms['color'] = (1.0, 0.5, 0.0, 1.0)
val = frame.uniforms['color']
assert len(val) == 4, "Vec4 should have 4 components"
print(" PASS: Static uniforms work")
def test_uniform_keys():
"""Test uniform collection keys/values/items"""
print("Testing uniform collection methods...")
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame.uniforms['a'] = 1.0
frame.uniforms['b'] = 2.0
frame.uniforms['c'] = 3.0
keys = frame.uniforms.keys()
assert 'a' in keys, "Key 'a' should be present"
assert 'b' in keys, "Key 'b' should be present"
assert 'c' in keys, "Key 'c' should be present"
assert len(keys) == 3, "Should have 3 keys"
# Test 'in' operator
assert 'a' in frame.uniforms, "'in' operator should work"
assert 'nonexistent' not in frame.uniforms, "'not in' should work"
# Test deletion
del frame.uniforms['b']
assert 'b' not in frame.uniforms, "Deleted key should be gone"
assert len(frame.uniforms.keys()) == 2, "Should have 2 keys after deletion"
print(" PASS: Uniform collection methods work")
def test_property_binding():
"""Test PropertyBinding for dynamic uniform values"""
print("Testing PropertyBinding...")
# Create source and target frames
source_frame = mcrfpy.Frame(pos=(100, 200), size=(50, 50))
target_frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
# Create binding to source frame's x position
binding = mcrfpy.PropertyBinding(source_frame, 'x')
assert binding is not None, "PropertyBinding should be created"
assert binding.property == 'x', "Property name should be stored"
assert abs(binding.value - 100.0) < 0.001, "Initial value should be 100"
assert binding.is_valid == True, "Binding should be valid"
# Assign binding to uniform
target_frame.uniforms['source_x'] = binding
# Check that value tracks changes
source_frame.x = 300
assert abs(binding.value - 300.0) < 0.001, "Binding should track changes"
print(" PASS: PropertyBinding works")
def test_callable_binding():
"""Test CallableBinding for computed uniform values"""
print("Testing CallableBinding...")
counter = [0] # Use list for closure
def compute_value():
counter[0] += 1
return counter[0] * 0.1
binding = mcrfpy.CallableBinding(compute_value)
assert binding is not None, "CallableBinding should be created"
assert binding.is_valid == True, "Binding should be valid"
# Each access should call the function
v1 = binding.value
v2 = binding.value
v3 = binding.value
assert abs(v1 - 0.1) < 0.001, "First call should return 0.1"
assert abs(v2 - 0.2) < 0.001, "Second call should return 0.2"
assert abs(v3 - 0.3) < 0.001, "Third call should return 0.3"
# Assign to uniform
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame.uniforms['computed'] = binding
print(" PASS: CallableBinding works")
def test_shader_on_frame():
"""Test shader assignment to Frame"""
print("Testing shader on Frame...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
uniform float intensity;
void main() {
vec4 color = texture2D(texture, gl_TexCoord[0].xy);
color.rgb *= intensity;
gl_FragColor = color;
}
''')
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
assert frame.shader is None, "Shader should be None initially"
frame.shader = shader
assert frame.shader is not None, "Shader should be assigned"
frame.uniforms['intensity'] = 0.8
assert abs(frame.uniforms['intensity'] - 0.8) < 0.001, "Uniform should be set"
# Test shader removal
frame.shader = None
assert frame.shader is None, "Shader should be removable"
print(" PASS: Shader on Frame works")
def test_shader_on_sprite():
"""Test shader assignment to Sprite"""
print("Testing shader on Sprite...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''')
sprite = mcrfpy.Sprite(pos=(0, 0))
assert sprite.shader is None, "Shader should be None initially"
sprite.shader = shader
assert sprite.shader is not None, "Shader should be assigned"
sprite.uniforms['test'] = 1.0
assert abs(sprite.uniforms['test'] - 1.0) < 0.001, "Uniform should be set"
print(" PASS: Shader on Sprite works")
def test_shader_on_caption():
"""Test shader assignment to Caption"""
print("Testing shader on Caption...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''')
caption = mcrfpy.Caption(text="Test", pos=(0, 0))
assert caption.shader is None, "Shader should be None initially"
caption.shader = shader
assert caption.shader is not None, "Shader should be assigned"
caption.uniforms['test'] = 1.0
assert abs(caption.uniforms['test'] - 1.0) < 0.001, "Uniform should be set"
print(" PASS: Shader on Caption works")
def test_shader_on_grid():
"""Test shader assignment to Grid"""
print("Testing shader on Grid...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''')
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(200, 200))
assert grid.shader is None, "Shader should be None initially"
grid.shader = shader
assert grid.shader is not None, "Shader should be assigned"
grid.uniforms['test'] = 1.0
assert abs(grid.uniforms['test'] - 1.0) < 0.001, "Uniform should be set"
print(" PASS: Shader on Grid works")
def test_shader_on_entity():
"""Test shader assignment to Entity"""
print("Testing shader on Entity...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy);
}
''')
entity = mcrfpy.Entity()
assert entity.shader is None, "Shader should be None initially"
entity.shader = shader
assert entity.shader is not None, "Shader should be assigned"
entity.uniforms['test'] = 1.0
assert abs(entity.uniforms['test'] - 1.0) < 0.001, "Uniform should be set"
print(" PASS: Shader on Entity works")
def test_shared_shader():
"""Test that multiple drawables can share the same shader"""
print("Testing shared shader...")
shader = mcrfpy.Shader('''
uniform sampler2D texture;
uniform float intensity;
void main() {
vec4 color = texture2D(texture, gl_TexCoord[0].xy);
color.rgb *= intensity;
gl_FragColor = color;
}
''')
frame1 = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
frame2 = mcrfpy.Frame(pos=(100, 0), size=(100, 100))
# Assign same shader to both
frame1.shader = shader
frame2.shader = shader
# But different uniform values
frame1.uniforms['intensity'] = 0.5
frame2.uniforms['intensity'] = 1.0
assert abs(frame1.uniforms['intensity'] - 0.5) < 0.001, "Frame1 intensity should be 0.5"
assert abs(frame2.uniforms['intensity'] - 1.0) < 0.001, "Frame2 intensity should be 1.0"
print(" PASS: Shared shader with different uniforms works")
def test_shader_animation_properties():
"""Test that shader uniforms can be animated via the animation system"""
print("Testing shader animation properties...")
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
# Set initial uniform value
frame.uniforms['intensity'] = 0.0
# Test animate() method with shader.X property syntax
# This uses hasProperty/setProperty internally
try:
frame.animate('shader.intensity', 1.0, 0.5, mcrfpy.Easing.LINEAR)
animation_works = True
except Exception as e:
animation_works = False
print(f" Animation error: {e}")
assert animation_works, "Animating shader uniforms should work"
# Test with different drawable types
sprite = mcrfpy.Sprite(pos=(0, 0))
sprite.uniforms['glow'] = 0.0
try:
sprite.animate('shader.glow', 2.0, 1.0, mcrfpy.Easing.EASE_IN)
sprite_animation_works = True
except Exception as e:
sprite_animation_works = False
print(f" Sprite animation error: {e}")
assert sprite_animation_works, "Animating Sprite shader uniforms should work"
# Test Caption
caption = mcrfpy.Caption(text="Test", pos=(0, 0))
caption.uniforms['alpha'] = 1.0
try:
caption.animate('shader.alpha', 0.0, 0.5, mcrfpy.Easing.EASE_OUT)
caption_animation_works = True
except Exception as e:
caption_animation_works = False
print(f" Caption animation error: {e}")
assert caption_animation_works, "Animating Caption shader uniforms should work"
# Test Grid
grid = mcrfpy.Grid(grid_size=(5, 5), pos=(0, 0), size=(100, 100))
grid.uniforms['zoom_effect'] = 1.0
try:
grid.animate('shader.zoom_effect', 2.0, 1.0, mcrfpy.Easing.LINEAR)
grid_animation_works = True
except Exception as e:
grid_animation_works = False
print(f" Grid animation error: {e}")
assert grid_animation_works, "Animating Grid shader uniforms should work"
print(" PASS: Shader animation properties work")
def run_all_tests():
"""Run all shader tests"""
print("=" * 50)
print("Shader System Unit Tests")
print("=" * 50)
print()
try:
test_shader_creation()
test_shader_source()
test_static_uniforms()
test_uniform_keys()
test_property_binding()
test_callable_binding()
test_shader_on_frame()
test_shader_on_sprite()
test_shader_on_caption()
test_shader_on_grid()
test_shader_on_entity()
test_shared_shader()
test_shader_animation_properties()
print()
print("=" * 50)
print("ALL TESTS PASSED")
print("=" * 50)
sys.exit(0)
except AssertionError as e:
print(f" FAIL: {e}")
sys.exit(1)
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
run_all_tests()