Tasmota/lib/libesp32/berry_animation/src/tests/comet_animation_test.be

322 lines
12 KiB
Plaintext

# Comet Animation Test Suite
# Comprehensive tests for the comet class following parameterized class specification
#
# Command to run:
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/comet_animation_test.be
import animation
print("=== Comet Animation Test Suite ===")
var test_count = 0
var pass_count = 0
def assert_test(condition, message)
test_count += 1
if condition
pass_count += 1
print(f"✓ PASS: {message}")
else
print(f"✗ FAIL: {message}")
end
end
def assert_equals(actual, expected, message)
assert_test(actual == expected, f"{message} (expected: {expected}, actual: {actual})")
end
def assert_not_nil(value, message)
assert_test(value != nil, f"{message} (value should not be nil)")
end
def assert_true(condition, message)
assert_test(condition == true, message)
end
def assert_false(condition, message)
assert_test(condition == false, message)
end
# Create LED strip and animation engine following specification
var strip = global.Leds(30) # Use global.Leds() for testing as per specification
var engine = animation.create_engine(strip)
print("Created LED strip and animation engine")
# Test 1: Basic Construction
print("\n--- Test 1: Basic Construction ---")
var comet = animation.comet(engine)
assert_not_nil(comet, "Comet animation should be created")
assert_equals(comet.engine, engine, "Animation should have correct engine reference")
# Test default values
assert_equals(comet.color, 0x00000000, "Default color should be transparent")
assert_equals(comet.tail_length, 5, "Default tail length should be 5")
assert_equals(comet.speed, 2560, "Default speed should be 2560")
assert_equals(comet.direction, 1, "Default direction should be 1 (forward)")
assert_equals(comet.wrap_around, 1, "Default wrap around should be enabled")
assert_equals(comet.fade_factor, 179, "Default fade factor should be 179")
# Test parameter assignment using virtual members
comet.color = 0xFFFF0000
comet.tail_length = 8
comet.speed = 5120
comet.direction = -1
comet.wrap_around = 0
comet.fade_factor = 150
comet.priority = 15
assert_equals(comet.color, 0xFFFF0000, "Color should be set correctly")
assert_equals(comet.tail_length, 8, "Tail length should be set correctly")
assert_equals(comet.speed, 5120, "Speed should be set correctly")
assert_equals(comet.direction, -1, "Direction should be set correctly")
assert_equals(comet.wrap_around, 0, "Wrap around should be disabled")
assert_equals(comet.fade_factor, 150, "Fade factor should be set correctly")
assert_equals(comet.priority, 15, "Priority should be set correctly")
# Test 2: Multiple Comet Animations
print("\n--- Test 2: Multiple Comet Animations ---")
var comet2 = animation.comet(engine)
comet2.color = 0xFF00FF00
comet2.tail_length = 8
comet2.speed = 3840
assert_not_nil(comet2, "Second comet should be created")
assert_equals(comet2.tail_length, 8, "Second comet tail length should be correct")
assert_equals(comet2.speed, 3840, "Second comet speed should be correct")
var comet3 = animation.comet(engine)
comet3.color = 0xFF0000FF
comet3.tail_length = 6
comet3.speed = 3072
assert_not_nil(comet3, "Third comet should be created")
assert_equals(comet3.tail_length, 6, "Third comet tail length should be correct")
# Test 3: Parameter Validation
print("\n--- Test 3: Parameter Validation ---")
# Valid parameters using virtual member assignment
comet.tail_length = 10
assert_equals(comet.tail_length, 10, "Valid tail length should be accepted")
comet.speed = 1408
assert_equals(comet.speed, 1408, "Valid speed should be accepted")
comet.direction = -1
assert_equals(comet.direction, -1, "Valid direction should be accepted")
comet.fade_factor = 128
assert_equals(comet.fade_factor, 128, "Valid fade factor should be accepted")
# Test parameter validation with invalid values
try
comet.tail_length = 0 # Should fail validation (min is 1)
assert_test(false, "Should have failed validation for tail_length = 0")
except "value_error"
assert_test(true, "Parameter validation correctly rejected tail_length = 0")
end
try
comet.tail_length = 100 # Should fail validation (max is 50)
assert_test(false, "Should have failed validation for tail_length = 100")
except "value_error"
assert_test(true, "Parameter validation correctly rejected tail_length = 100")
end
try
comet.direction = 0 # Should fail validation (enum is [-1, 1])
assert_test(false, "Should have failed validation for direction = 0")
except "value_error"
assert_test(true, "Parameter validation correctly rejected direction = 0")
end
try
comet.fade_factor = 300 # Should fail validation (max is 255)
assert_test(false, "Should have failed validation for fade_factor = 300")
except "value_error"
assert_test(true, "Parameter validation correctly rejected fade_factor = 300")
end
# Test 4: Position Updates
print("\n--- Test 4: Position Updates ---")
# Create comet for position testing
var pos_comet = animation.comet(engine)
pos_comet.color = 0xFFFFFFFF
pos_comet.tail_length = 3
pos_comet.speed = 2560 # 10 pixels/sec (10 * 256)
# Use engine time for testing
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
engine.time_ms = 1000
var start_time = engine.time_ms
pos_comet.start_time = start_time # Set start_time manually for direct testing
pos_comet.start(start_time)
pos_comet.update(start_time)
engine.time_ms = start_time + 1000 # 1 second later
pos_comet.update(engine.time_ms)
# After 1 second at 10 pixels/sec, should have moved ~10 pixels (10 * 256 = 2560 subpixels)
var expected_pos = 2560 # 10 pixels in subpixels
assert_test(pos_comet.head_position >= (expected_pos - 256) && pos_comet.head_position <= (expected_pos + 256),
f"Position should be around {expected_pos} subpixels after 1 second (actual: {pos_comet.head_position})")
# Test 5: Direction Changes
print("\n--- Test 5: Direction Changes ---")
var dir_comet = animation.comet(engine)
dir_comet.color = 0xFFFFFFFF
dir_comet.tail_length = 3
dir_comet.speed = 2560 # 10 pixels/sec
dir_comet.direction = -1 # Backward
engine.time_ms = 2000
start_time = engine.time_ms
dir_comet.start_time = start_time # Set start_time manually for direct testing
dir_comet.start(start_time)
dir_comet.update(start_time)
var initial_pos = dir_comet.head_position
engine.time_ms = start_time + 500 # 0.5 seconds later
dir_comet.update(engine.time_ms)
# Should have moved backward (position should decrease)
assert_test(dir_comet.head_position < initial_pos,
f"Position should decrease with backward direction (initial: {initial_pos}, current: {dir_comet.head_position})")
# Test 6: Wrap Around vs Bounce
print("\n--- Test 6: Wrap Around vs Bounce ---")
# Create smaller strip for faster testing
var small_strip = global.Leds(10)
var small_engine = animation.create_engine(small_strip)
# Test wrap around
var wrap_comet = animation.comet(small_engine)
wrap_comet.color = 0xFFFFFFFF
wrap_comet.tail_length = 3
wrap_comet.speed = 25600 # Very fast (100 pixels/sec)
wrap_comet.wrap_around = 1 # Enable wrapping
small_engine.time_ms = 3000
start_time = small_engine.time_ms
wrap_comet.start_time = start_time # Set start_time manually for direct testing
wrap_comet.start(start_time)
wrap_comet.update(start_time)
small_engine.time_ms = start_time + 2000 # 2 seconds - should wrap multiple times
wrap_comet.update(small_engine.time_ms)
var strip_length_subpixels = 10 * 256
assert_test(wrap_comet.head_position >= 0 && wrap_comet.head_position < strip_length_subpixels,
f"Wrapped position should be within strip bounds (position: {wrap_comet.head_position})")
# Test bounce
var bounce_comet = animation.comet(small_engine)
bounce_comet.color = 0xFFFFFFFF
bounce_comet.tail_length = 3
bounce_comet.speed = 25600 # Very fast
bounce_comet.wrap_around = 0 # Disable wrapping (enable bouncing)
small_engine.time_ms = 4000
start_time = small_engine.time_ms
bounce_comet.start_time = start_time # Set start_time manually for direct testing
bounce_comet.start(start_time)
bounce_comet.update(small_engine.time_ms)
small_engine.time_ms = start_time + 200 # Should hit the end and bounce
bounce_comet.update(small_engine.time_ms)
# Direction should have changed due to bouncing
assert_test(bounce_comet.direction == -1,
f"Direction should change to -1 after bouncing (direction: {bounce_comet.direction})")
# Test 7: Frame Buffer Rendering
print("\n--- Test 7: Frame Buffer Rendering ---")
var frame = animation.frame_buffer(10)
var render_comet = animation.comet(small_engine)
render_comet.color = 0xFFFF0000 # Red
render_comet.tail_length = 3
render_comet.speed = 256 # Slow (1 pixel/sec)
small_engine.time_ms = 5000
render_comet.start_time = small_engine.time_ms # Set start_time manually for direct testing
render_comet.start(small_engine.time_ms)
render_comet.update(small_engine.time_ms)
# Clear frame and render
frame.clear()
var rendered = render_comet.render(frame, small_engine.time_ms, small_engine.strip_length)
assert_true(rendered, "Render should return true when successful")
# Check that pixels were set (comet should be at position 0 with tail)
var head_color = frame.get_pixel_color(0) # Head at position 0
assert_test(head_color != 0, "Head pixel should have color")
# Check tail pixels have lower brightness (tail wraps around to end of strip)
var tail_color = frame.get_pixel_color(9) # Tail pixel
assert_test(tail_color != 0, "Tail pixel should have some color")
# Extract alpha components to compare transparency (alpha-based fading)
var head_alpha = (head_color >> 24) & 0xFF
var tail_alpha = (tail_color >> 24) & 0xFF
assert_test(head_alpha > tail_alpha, f"Head should be less transparent than tail (head alpha: {head_alpha}, tail alpha: {tail_alpha})")
# Test 8: Color Provider Integration
print("\n--- Test 8: Color Provider Integration ---")
# Test with solid color provider
var solid_provider = animation.color_provider(engine)
solid_provider.color = 0xFF00FFFF
var provider_comet = animation.comet(engine)
provider_comet.color = solid_provider
provider_comet.tail_length = 4
provider_comet.speed = 1280
assert_not_nil(provider_comet, "Comet with color provider should be created")
engine.time_ms = 6000
provider_comet.start_time = engine.time_ms # Set start_time manually for direct testing
provider_comet.start(engine.time_ms)
provider_comet.update(engine.time_ms)
# Test that the color can be resolved properly through virtual member access
var resolved_color = provider_comet.color
assert_test(resolved_color != 0, "Color should be resolved from provider")
assert_equals(resolved_color, 0xFF00FFFF, "Resolved color should match provider color")
# Test 9: Engine Integration
print("\n--- Test 9: Engine Integration ---")
var engine_comet = animation.comet(engine)
engine_comet.color = 0xFFFFFFFF
engine_comet.tail_length = 5
engine_comet.speed = 2560
# Test adding to engine
engine.add(engine_comet)
assert_test(true, "Animation should be added to engine successfully")
# Test strip length from engine
var strip_length = engine_comet.engine.get_strip_length()
assert_equals(strip_length, 30, "Strip length should come from engine")
# Test engine time usage
engine.time_ms = 7000
engine_comet.start_time = engine.time_ms # Set start_time manually for direct testing
engine_comet.start(engine.time_ms)
engine_comet.update(engine.time_ms)
assert_equals(engine_comet.start_time, 7000, "Animation should use engine time for start")
# Test Results
print(f"\n=== Test Results ===")
print(f"Tests run: {test_count}")
print(f"Tests passed: {pass_count}")
print(f"Tests failed: {test_count - pass_count}")
print(f"Success rate: {(pass_count * 100) / test_count:.1f}%")
if pass_count == test_count
print("🎉 All tests passed!")
else
print("❌ Some tests failed. Please review the implementation.")
raise "test_failed"
end
print("=== Comet Animation Test Suite Complete ===")