337 lines
11 KiB
Python
337 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Comprehensive test for Issues #26 & #28: Iterator implementation for collections
|
|
|
|
This test covers both UICollection and UIEntityCollection iterator implementations,
|
|
testing all aspects of the Python sequence protocol.
|
|
|
|
Issues:
|
|
- #26: Iterator support for UIEntityCollection
|
|
- #28: Iterator support for UICollection
|
|
"""
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import sys
|
|
import gc
|
|
|
|
def test_sequence_protocol(collection, name, expected_types=None):
|
|
"""Test all sequence protocol operations on a collection"""
|
|
print(f"\n=== Testing {name} ===")
|
|
|
|
tests_passed = 0
|
|
tests_total = 0
|
|
|
|
# Test 1: len()
|
|
tests_total += 1
|
|
try:
|
|
length = len(collection)
|
|
print(f"✓ len() works: {length} items")
|
|
tests_passed += 1
|
|
except Exception as e:
|
|
print(f"✗ len() failed: {e}")
|
|
return tests_passed, tests_total
|
|
|
|
# Test 2: Basic iteration
|
|
tests_total += 1
|
|
try:
|
|
items = []
|
|
types = []
|
|
for item in collection:
|
|
items.append(item)
|
|
types.append(type(item).__name__)
|
|
print(f"✓ Iteration works: found {len(items)} items")
|
|
print(f" Types: {types}")
|
|
if expected_types and types != expected_types:
|
|
print(f" WARNING: Expected types {expected_types}")
|
|
tests_passed += 1
|
|
except Exception as e:
|
|
print(f"✗ Iteration failed (Issue #26/#28): {e}")
|
|
|
|
# Test 3: Indexing (positive)
|
|
tests_total += 1
|
|
try:
|
|
if length > 0:
|
|
first = collection[0]
|
|
last = collection[length-1]
|
|
print(f"✓ Positive indexing works: [0]={type(first).__name__}, [{length-1}]={type(last).__name__}")
|
|
tests_passed += 1
|
|
else:
|
|
print(" Skipping indexing test - empty collection")
|
|
except Exception as e:
|
|
print(f"✗ Positive indexing failed: {e}")
|
|
|
|
# Test 4: Negative indexing
|
|
tests_total += 1
|
|
try:
|
|
if length > 0:
|
|
last = collection[-1]
|
|
first = collection[-length]
|
|
print(f"✓ Negative indexing works: [-1]={type(last).__name__}, [-{length}]={type(first).__name__}")
|
|
tests_passed += 1
|
|
else:
|
|
print(" Skipping negative indexing test - empty collection")
|
|
except Exception as e:
|
|
print(f"✗ Negative indexing failed: {e}")
|
|
|
|
# Test 5: Out of bounds indexing
|
|
tests_total += 1
|
|
try:
|
|
_ = collection[length + 10]
|
|
print(f"✗ Out of bounds indexing should raise IndexError but didn't")
|
|
except IndexError:
|
|
print(f"✓ Out of bounds indexing correctly raises IndexError")
|
|
tests_passed += 1
|
|
except Exception as e:
|
|
print(f"✗ Out of bounds indexing raised wrong exception: {type(e).__name__}: {e}")
|
|
|
|
# Test 6: Slicing
|
|
tests_total += 1
|
|
try:
|
|
if length >= 2:
|
|
slice_result = collection[0:2]
|
|
print(f"✓ Slicing works: [0:2] returned {len(slice_result)} items")
|
|
tests_passed += 1
|
|
else:
|
|
print(" Skipping slicing test - not enough items")
|
|
except NotImplementedError:
|
|
print(f"✗ Slicing not implemented")
|
|
except Exception as e:
|
|
print(f"✗ Slicing failed: {e}")
|
|
|
|
# Test 7: Contains operator
|
|
tests_total += 1
|
|
try:
|
|
if length > 0:
|
|
first_item = collection[0]
|
|
if first_item in collection:
|
|
print(f"✓ 'in' operator works")
|
|
tests_passed += 1
|
|
else:
|
|
print(f"✗ 'in' operator returned False for existing item")
|
|
else:
|
|
print(" Skipping 'in' operator test - empty collection")
|
|
except NotImplementedError:
|
|
print(f"✗ 'in' operator not implemented")
|
|
except Exception as e:
|
|
print(f"✗ 'in' operator failed: {e}")
|
|
|
|
# Test 8: Multiple iterations
|
|
tests_total += 1
|
|
try:
|
|
count1 = sum(1 for _ in collection)
|
|
count2 = sum(1 for _ in collection)
|
|
if count1 == count2 == length:
|
|
print(f"✓ Multiple iterations work correctly")
|
|
tests_passed += 1
|
|
else:
|
|
print(f"✗ Multiple iterations inconsistent: {count1} vs {count2} vs {length}")
|
|
except Exception as e:
|
|
print(f"✗ Multiple iterations failed: {e}")
|
|
|
|
# Test 9: Iterator state independence
|
|
tests_total += 1
|
|
try:
|
|
iter1 = iter(collection)
|
|
iter2 = iter(collection)
|
|
|
|
# Advance iter1
|
|
next(iter1)
|
|
|
|
# iter2 should still be at the beginning
|
|
item1_from_iter2 = next(iter2)
|
|
item1_from_collection = collection[0]
|
|
|
|
if type(item1_from_iter2).__name__ == type(item1_from_collection).__name__:
|
|
print(f"✓ Iterator state independence maintained")
|
|
tests_passed += 1
|
|
else:
|
|
print(f"✗ Iterator states are not independent")
|
|
except Exception as e:
|
|
print(f"✗ Iterator state test failed: {e}")
|
|
|
|
# Test 10: List conversion
|
|
tests_total += 1
|
|
try:
|
|
as_list = list(collection)
|
|
if len(as_list) == length:
|
|
print(f"✓ list() conversion works: {len(as_list)} items")
|
|
tests_passed += 1
|
|
else:
|
|
print(f"✗ list() conversion wrong length: {len(as_list)} vs {length}")
|
|
except Exception as e:
|
|
print(f"✗ list() conversion failed: {e}")
|
|
|
|
return tests_passed, tests_total
|
|
|
|
def test_modification_during_iteration(collection, name):
|
|
"""Test collection modification during iteration"""
|
|
print(f"\n=== Testing {name} Modification During Iteration ===")
|
|
|
|
# This is a tricky case - some implementations might crash
|
|
# or behave unexpectedly when the collection is modified during iteration
|
|
|
|
if len(collection) < 2:
|
|
print(" Skipping - need at least 2 items")
|
|
return
|
|
|
|
try:
|
|
count = 0
|
|
for i, item in enumerate(collection):
|
|
count += 1
|
|
if i == 0 and hasattr(collection, 'remove'):
|
|
# Try to remove an item during iteration
|
|
# This might raise an exception or cause undefined behavior
|
|
pass # Don't actually modify to avoid breaking the test
|
|
print(f"✓ Iteration completed without modification: {count} items")
|
|
except Exception as e:
|
|
print(f" Note: Iteration with modification would fail: {e}")
|
|
|
|
def run_comprehensive_test():
|
|
"""Run comprehensive iterator tests for both collection types"""
|
|
print("=== Testing Collection Iterator Implementation (Issues #26 & #28) ===")
|
|
|
|
total_passed = 0
|
|
total_tests = 0
|
|
|
|
# Test UICollection
|
|
print("\n--- Testing UICollection ---")
|
|
|
|
# Create UI elements
|
|
scene_ui = mcrfpy.sceneUI("test")
|
|
|
|
# Add various UI elements
|
|
frame = mcrfpy.Frame(10, 10, 200, 150,
|
|
fill_color=mcrfpy.Color(100, 100, 200),
|
|
outline_color=mcrfpy.Color(255, 255, 255))
|
|
caption = mcrfpy.Caption(mcrfpy.Vector(220, 10),
|
|
text="Test Caption",
|
|
fill_color=mcrfpy.Color(255, 255, 0))
|
|
|
|
scene_ui.append(frame)
|
|
scene_ui.append(caption)
|
|
|
|
# Test UICollection
|
|
passed, total = test_sequence_protocol(scene_ui, "UICollection",
|
|
expected_types=["Frame", "Caption"])
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
test_modification_during_iteration(scene_ui, "UICollection")
|
|
|
|
# Test UICollection with children
|
|
print("\n--- Testing UICollection Children (Nested) ---")
|
|
child_caption = mcrfpy.Caption(mcrfpy.Vector(10, 10),
|
|
text="Child",
|
|
fill_color=mcrfpy.Color(200, 200, 200))
|
|
frame.children.append(child_caption)
|
|
|
|
passed, total = test_sequence_protocol(frame.children, "Frame.children",
|
|
expected_types=["Caption"])
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
# Test UIEntityCollection
|
|
print("\n--- Testing UIEntityCollection ---")
|
|
|
|
# Create a grid with entities
|
|
grid = mcrfpy.Grid(30, 30)
|
|
grid.x = 10
|
|
grid.y = 200
|
|
grid.w = 600
|
|
grid.h = 400
|
|
scene_ui.append(grid)
|
|
|
|
# Add various entities
|
|
entity1 = mcrfpy.Entity(5, 5)
|
|
entity2 = mcrfpy.Entity(10, 10)
|
|
entity3 = mcrfpy.Entity(15, 15)
|
|
|
|
grid.entities.append(entity1)
|
|
grid.entities.append(entity2)
|
|
grid.entities.append(entity3)
|
|
|
|
passed, total = test_sequence_protocol(grid.entities, "UIEntityCollection",
|
|
expected_types=["Entity", "Entity", "Entity"])
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
test_modification_during_iteration(grid.entities, "UIEntityCollection")
|
|
|
|
# Test empty collections
|
|
print("\n--- Testing Empty Collections ---")
|
|
empty_grid = mcrfpy.Grid(10, 10)
|
|
|
|
passed, total = test_sequence_protocol(empty_grid.entities, "Empty UIEntityCollection")
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
empty_frame = mcrfpy.Frame(0, 0, 50, 50)
|
|
passed, total = test_sequence_protocol(empty_frame.children, "Empty UICollection")
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
# Test large collection
|
|
print("\n--- Testing Large Collection ---")
|
|
large_grid = mcrfpy.Grid(50, 50)
|
|
for i in range(100):
|
|
large_grid.entities.append(mcrfpy.Entity(i % 50, i // 50))
|
|
|
|
print(f"Created large collection with {len(large_grid.entities)} entities")
|
|
|
|
# Just test basic iteration performance
|
|
import time
|
|
start = time.time()
|
|
count = sum(1 for _ in large_grid.entities)
|
|
elapsed = time.time() - start
|
|
print(f"✓ Large collection iteration: {count} items in {elapsed:.3f}s")
|
|
|
|
# Edge case: Single item collection
|
|
print("\n--- Testing Single Item Collection ---")
|
|
single_grid = mcrfpy.Grid(5, 5)
|
|
single_grid.entities.append(mcrfpy.Entity(1, 1))
|
|
|
|
passed, total = test_sequence_protocol(single_grid.entities, "Single Item UIEntityCollection")
|
|
total_passed += passed
|
|
total_tests += total
|
|
|
|
# Take screenshot
|
|
automation.screenshot("/tmp/issue_26_28_iterator_test.png")
|
|
|
|
# Summary
|
|
print(f"\n=== SUMMARY ===")
|
|
print(f"Total tests passed: {total_passed}/{total_tests}")
|
|
|
|
if total_passed < total_tests:
|
|
print("\nIssues found:")
|
|
print("- Issue #26: UIEntityCollection may not fully implement iterator protocol")
|
|
print("- Issue #28: UICollection may not fully implement iterator protocol")
|
|
print("\nThe iterator implementation should support:")
|
|
print("1. Forward iteration with 'for item in collection'")
|
|
print("2. Multiple independent iterators")
|
|
print("3. Proper cleanup when iteration completes")
|
|
print("4. Integration with Python's sequence protocol")
|
|
else:
|
|
print("\nAll iterator tests passed!")
|
|
|
|
return total_passed == total_tests
|
|
|
|
def run_test(runtime):
|
|
"""Timer callback to run the test"""
|
|
try:
|
|
success = run_comprehensive_test()
|
|
print("\nOverall result: " + ("PASS" if success else "FAIL"))
|
|
except Exception as e:
|
|
print(f"\nTest error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
print("\nOverall result: FAIL")
|
|
|
|
sys.exit(0)
|
|
|
|
# Set up the test scene
|
|
mcrfpy.createScene("test")
|
|
mcrfpy.setScene("test")
|
|
|
|
# Schedule test to run after game loop starts
|
|
mcrfpy.setTimer("test", run_test, 100) |