Test suite modernization

This commit is contained in:
John McCardle 2026-02-09 08:15:18 -05:00
commit 52fdfd0347
141 changed files with 9947 additions and 4665 deletions

View file

@ -158,10 +158,16 @@ def test_edge_cases():
print(" Edge cases: PASS")
return True
def run_test(timer, runtime):
"""Timer callback to run tests after scene is active"""
results = []
# Main
if __name__ == "__main__":
print("=" * 60)
print("Issue #123: Grid Sub-grid Chunk System Test")
print("=" * 60)
test = mcrfpy.Scene("test")
mcrfpy.current_scene = test
results = []
results.append(test_small_grid())
results.append(test_large_grid())
results.append(test_very_large_grid())
@ -174,15 +180,3 @@ def run_test(timer, runtime):
else:
print("\n=== SOME TESTS FAILED ===")
sys.exit(1)
# Main
if __name__ == "__main__":
print("=" * 60)
print("Issue #123: Grid Sub-grid Chunk System Test")
print("=" * 60)
test = mcrfpy.Scene("test")
test.activate()
# Run tests after scene is active
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)

View file

@ -2,7 +2,7 @@
"""
Regression test for issue #146: compute_fov() returns None
The compute_fov() method had O(n²) performance because it built a Python list
The compute_fov() method had O(n^2) performance because it built a Python list
of all visible cells by iterating the entire grid. The fix removes this
list-building and returns None instead. Users should use is_in_fov() to query
visibility.
@ -14,101 +14,96 @@ import mcrfpy
import sys
import time
def run_test(timer, runtime):
print("=" * 60)
print("Issue #146 Regression Test: compute_fov() returns None")
print("=" * 60)
print("=" * 60)
print("Issue #146 Regression Test: compute_fov() returns None")
print("=" * 60)
# Create a test grid
test = mcrfpy.Scene("test")
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Create a test scene and grid
test = mcrfpy.Scene("test")
mcrfpy.current_scene = test
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(pos=(0,0), size=(400,300), grid_size=(50, 50), texture=texture)
ui.append(grid)
grid = mcrfpy.Grid(pos=(0,0), size=(400,300), grid_size=(50, 50), texture=texture)
ui.append(grid)
# Set walkability for center area
for y in range(50):
for x in range(50):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
# Set walkability for center area
for y in range(50):
for x in range(50):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
# Add some walls to test blocking
for i in range(10, 20):
grid.at(i, 25).transparent = False
grid.at(i, 25).walkable = False
# Add some walls to test blocking
for i in range(10, 20):
grid.at(i, 25).transparent = False
grid.at(i, 25).walkable = False
print("\n--- Test 1: compute_fov() returns None ---")
result = grid.compute_fov(25, 25, radius=10)
if result is None:
print(" PASS: compute_fov() returned None")
else:
print(f" FAIL: compute_fov() returned {type(result).__name__} instead of None")
sys.exit(1)
print("\n--- Test 1: compute_fov() returns None ---")
result = grid.compute_fov(25, 25, radius=10)
if result is None:
print(" PASS: compute_fov() returned None")
else:
print(f" FAIL: compute_fov() returned {type(result).__name__} instead of None")
sys.exit(1)
print("\n--- Test 2: is_in_fov() works after compute_fov() ---")
# Center should be visible
if grid.is_in_fov(25, 25):
print(" PASS: Center (25,25) is in FOV")
else:
print(" FAIL: Center should be in FOV")
sys.exit(1)
print("\n--- Test 2: is_in_fov() works after compute_fov() ---")
# Center should be visible
if grid.is_in_fov(25, 25):
print(" PASS: Center (25,25) is in FOV")
else:
print(" FAIL: Center should be in FOV")
sys.exit(1)
# Cell within radius should be visible
if grid.is_in_fov(20, 25):
print(" PASS: Cell (20,25) within radius is in FOV")
else:
print(" FAIL: Cell (20,25) should be in FOV")
sys.exit(1)
# Cell within radius should be visible
if grid.is_in_fov(20, 25):
print(" PASS: Cell (20,25) within radius is in FOV")
else:
print(" FAIL: Cell (20,25) should be in FOV")
sys.exit(1)
# Cell behind wall should NOT be visible
if not grid.is_in_fov(15, 30):
print(" PASS: Cell (15,30) behind wall is NOT in FOV")
else:
print(" FAIL: Cell behind wall should not be in FOV")
sys.exit(1)
# Cell behind wall should NOT be visible
if not grid.is_in_fov(15, 30):
print(" PASS: Cell (15,30) behind wall is NOT in FOV")
else:
print(" FAIL: Cell behind wall should not be in FOV")
sys.exit(1)
# Cell outside radius should NOT be visible
if not grid.is_in_fov(0, 0):
print(" PASS: Cell (0,0) outside radius is NOT in FOV")
else:
print(" FAIL: Cell outside radius should not be in FOV")
sys.exit(1)
# Cell outside radius should NOT be visible
if not grid.is_in_fov(0, 0):
print(" PASS: Cell (0,0) outside radius is NOT in FOV")
else:
print(" FAIL: Cell outside radius should not be in FOV")
sys.exit(1)
print("\n--- Test 3: Performance sanity check ---")
# Create larger grid for timing
grid_large = mcrfpy.Grid(pos=(0,0), size=(400,300), grid_size=(200, 200), texture=texture)
for y in range(0, 200, 5): # Sample for speed
for x in range(200):
cell = grid_large.at(x, y)
cell.walkable = True
cell.transparent = True
print("\n--- Test 3: Performance sanity check ---")
# Create larger grid for timing
grid_large = mcrfpy.Grid(pos=(0,0), size=(400,300), grid_size=(200, 200), texture=texture)
for y in range(0, 200, 5): # Sample for speed
for x in range(200):
cell = grid_large.at(x, y)
cell.walkable = True
cell.transparent = True
# Time compute_fov (should be fast now - no list building)
times = []
for i in range(5):
t0 = time.perf_counter()
grid_large.compute_fov(100, 100, radius=15)
elapsed = (time.perf_counter() - t0) * 1000
times.append(elapsed)
# Time compute_fov (should be fast now - no list building)
times = []
for i in range(5):
t0 = time.perf_counter()
grid_large.compute_fov(100, 100, radius=15)
elapsed = (time.perf_counter() - t0) * 1000
times.append(elapsed)
avg_time = sum(times) / len(times)
print(f" compute_fov() on 200x200 grid: {avg_time:.3f}ms avg")
avg_time = sum(times) / len(times)
print(f" compute_fov() on 200x200 grid: {avg_time:.3f}ms avg")
# Should be under 1ms without list building (was ~4ms with list on 200x200)
if avg_time < 2.0:
print(f" PASS: compute_fov() is fast (<2ms)")
else:
print(f" WARNING: compute_fov() took {avg_time:.3f}ms (expected <2ms)")
# Not a hard failure, just a warning
# Should be under 1ms without list building (was ~4ms with list on 200x200)
if avg_time < 2.0:
print(f" PASS: compute_fov() is fast (<2ms)")
else:
print(f" WARNING: compute_fov() took {avg_time:.3f}ms (expected <2ms)")
# Not a hard failure, just a warning
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
sys.exit(0)
# Initialize and run
init = mcrfpy.Scene("init")
init.activate()
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
sys.exit(0)

View file

@ -11,183 +11,178 @@ Tests:
import mcrfpy
import sys
def run_test(timer, runtime):
print("=" * 60)
print("Issue #147 Regression Test: Dynamic Layer System for Grid")
print("=" * 60)
print("=" * 60)
print("Issue #147 Regression Test: Dynamic Layer System for Grid")
print("=" * 60)
# Create test scene
test = mcrfpy.Scene("test")
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Create test scene
test = mcrfpy.Scene("test")
mcrfpy.current_scene = test
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Create grid with explicit empty layers (#150 migration)
grid = mcrfpy.Grid(pos=(50, 50), size=(400, 300), grid_size=(20, 15), texture=texture, layers={})
ui.append(grid)
# Create grid with explicit empty layers (#150 migration)
grid = mcrfpy.Grid(pos=(50, 50), size=(400, 300), grid_size=(20, 15), texture=texture, layers={})
ui.append(grid)
print("\n--- Test 1: Initial state (no layers) ---")
if len(grid.layers) == 0:
print(" PASS: Grid starts with no layers (layers={})")
else:
print(f" FAIL: Expected 0 layers, got {len(grid.layers)}")
sys.exit(1)
print("\n--- Test 1: Initial state (no layers) ---")
if len(grid.layers) == 0:
print(" PASS: Grid starts with no layers (layers={})")
else:
print(f" FAIL: Expected 0 layers, got {len(grid.layers)}")
sys.exit(1)
print("\n--- Test 2: Add ColorLayer ---")
color_layer = grid.add_layer("color", z_index=-1)
print(f" Created: {color_layer}")
if color_layer is not None:
print(" PASS: ColorLayer created")
else:
print(" FAIL: ColorLayer creation returned None")
sys.exit(1)
print("\n--- Test 2: Add ColorLayer ---")
color_layer = grid.add_layer("color", z_index=-1)
print(f" Created: {color_layer}")
if color_layer is not None:
print(" PASS: ColorLayer created")
else:
print(" FAIL: ColorLayer creation returned None")
sys.exit(1)
# Test ColorLayer properties
if color_layer.z_index == -1:
print(" PASS: ColorLayer z_index is -1")
else:
print(f" FAIL: Expected z_index -1, got {color_layer.z_index}")
sys.exit(1)
# Test ColorLayer properties
if color_layer.z_index == -1:
print(" PASS: ColorLayer z_index is -1")
else:
print(f" FAIL: Expected z_index -1, got {color_layer.z_index}")
sys.exit(1)
if color_layer.visible:
print(" PASS: ColorLayer is visible by default")
else:
print(" FAIL: ColorLayer should be visible by default")
sys.exit(1)
if color_layer.visible:
print(" PASS: ColorLayer is visible by default")
else:
print(" FAIL: ColorLayer should be visible by default")
sys.exit(1)
grid_size = color_layer.grid_size
if grid_size == (20, 15):
print(f" PASS: ColorLayer grid_size is {grid_size}")
else:
print(f" FAIL: Expected (20, 15), got {grid_size}")
sys.exit(1)
grid_size = color_layer.grid_size
if grid_size == (20, 15):
print(f" PASS: ColorLayer grid_size is {grid_size}")
else:
print(f" FAIL: Expected (20, 15), got {grid_size}")
sys.exit(1)
print("\n--- Test 3: ColorLayer cell access ---")
# Set a color
color_layer.set(5, 5, mcrfpy.Color(255, 0, 0, 128))
color = color_layer.at(5, 5)
if color.r == 255 and color.g == 0 and color.b == 0 and color.a == 128:
print(f" PASS: Color at (5,5) is {color.r}, {color.g}, {color.b}, {color.a}")
else:
print(f" FAIL: Color mismatch")
sys.exit(1)
print("\n--- Test 3: ColorLayer cell access ---")
# Set a color
color_layer.set(5, 5, mcrfpy.Color(255, 0, 0, 128))
color = color_layer.at(5, 5)
if color.r == 255 and color.g == 0 and color.b == 0 and color.a == 128:
print(f" PASS: Color at (5,5) is {color.r}, {color.g}, {color.b}, {color.a}")
else:
print(f" FAIL: Color mismatch")
sys.exit(1)
# Fill entire layer
color_layer.fill(mcrfpy.Color(0, 0, 255, 64))
color = color_layer.at(0, 0)
if color.b == 255 and color.a == 64:
print(" PASS: ColorLayer fill works")
else:
print(" FAIL: ColorLayer fill did not work")
sys.exit(1)
# Fill entire layer
color_layer.fill(mcrfpy.Color(0, 0, 255, 64))
color = color_layer.at(0, 0)
if color.b == 255 and color.a == 64:
print(" PASS: ColorLayer fill works")
else:
print(" FAIL: ColorLayer fill did not work")
sys.exit(1)
print("\n--- Test 4: Add TileLayer ---")
tile_layer = grid.add_layer("tile", z_index=-2, texture=texture)
print(f" Created: {tile_layer}")
if tile_layer is not None:
print(" PASS: TileLayer created")
else:
print(" FAIL: TileLayer creation returned None")
sys.exit(1)
print("\n--- Test 4: Add TileLayer ---")
tile_layer = grid.add_layer("tile", z_index=-2, texture=texture)
print(f" Created: {tile_layer}")
if tile_layer is not None:
print(" PASS: TileLayer created")
else:
print(" FAIL: TileLayer creation returned None")
sys.exit(1)
if tile_layer.z_index == -2:
print(" PASS: TileLayer z_index is -2")
else:
print(f" FAIL: Expected z_index -2, got {tile_layer.z_index}")
sys.exit(1)
if tile_layer.z_index == -2:
print(" PASS: TileLayer z_index is -2")
else:
print(f" FAIL: Expected z_index -2, got {tile_layer.z_index}")
sys.exit(1)
print("\n--- Test 5: TileLayer cell access ---")
# Set a tile
tile_layer.set(3, 3, 42)
tile = tile_layer.at(3, 3)
if tile == 42:
print(f" PASS: Tile at (3,3) is {tile}")
else:
print(f" FAIL: Expected 42, got {tile}")
sys.exit(1)
print("\n--- Test 5: TileLayer cell access ---")
# Set a tile
tile_layer.set(3, 3, 42)
tile = tile_layer.at(3, 3)
if tile == 42:
print(f" PASS: Tile at (3,3) is {tile}")
else:
print(f" FAIL: Expected 42, got {tile}")
sys.exit(1)
# Fill entire layer
tile_layer.fill(10)
tile = tile_layer.at(0, 0)
if tile == 10:
print(" PASS: TileLayer fill works")
else:
print(" FAIL: TileLayer fill did not work")
sys.exit(1)
# Fill entire layer
tile_layer.fill(10)
tile = tile_layer.at(0, 0)
if tile == 10:
print(" PASS: TileLayer fill works")
else:
print(" FAIL: TileLayer fill did not work")
sys.exit(1)
print("\n--- Test 6: Layer ordering ---")
layers = grid.layers
if len(layers) == 2:
print(f" PASS: Grid has 2 layers")
else:
print(f" FAIL: Expected 2 layers, got {len(layers)}")
sys.exit(1)
print("\n--- Test 6: Layer ordering ---")
layers = grid.layers
if len(layers) == 2:
print(f" PASS: Grid has 2 layers")
else:
print(f" FAIL: Expected 2 layers, got {len(layers)}")
sys.exit(1)
# Layers should be sorted by z_index
if layers[0].z_index <= layers[1].z_index:
print(f" PASS: Layers sorted by z_index ({layers[0].z_index}, {layers[1].z_index})")
else:
print(f" FAIL: Layers not sorted")
sys.exit(1)
# Layers should be sorted by z_index
if layers[0].z_index <= layers[1].z_index:
print(f" PASS: Layers sorted by z_index ({layers[0].z_index}, {layers[1].z_index})")
else:
print(f" FAIL: Layers not sorted")
sys.exit(1)
print("\n--- Test 7: Get layer by z_index ---")
layer = grid.layer(-1)
if layer is not None and layer.z_index == -1:
print(" PASS: grid.layer(-1) returns ColorLayer")
else:
print(" FAIL: Could not get layer by z_index")
sys.exit(1)
print("\n--- Test 7: Get layer by z_index ---")
layer = grid.layer(-1)
if layer is not None and layer.z_index == -1:
print(" PASS: grid.layer(-1) returns ColorLayer")
else:
print(" FAIL: Could not get layer by z_index")
sys.exit(1)
layer = grid.layer(-2)
if layer is not None and layer.z_index == -2:
print(" PASS: grid.layer(-2) returns TileLayer")
else:
print(" FAIL: Could not get layer by z_index")
sys.exit(1)
layer = grid.layer(-2)
if layer is not None and layer.z_index == -2:
print(" PASS: grid.layer(-2) returns TileLayer")
else:
print(" FAIL: Could not get layer by z_index")
sys.exit(1)
layer = grid.layer(999)
if layer is None:
print(" PASS: grid.layer(999) returns None for non-existent layer")
else:
print(" FAIL: Should return None for non-existent layer")
sys.exit(1)
layer = grid.layer(999)
if layer is None:
print(" PASS: grid.layer(999) returns None for non-existent layer")
else:
print(" FAIL: Should return None for non-existent layer")
sys.exit(1)
print("\n--- Test 8: Layer above entities (z_index >= 0) ---")
fog_layer = grid.add_layer("color", z_index=1)
if fog_layer.z_index == 1:
print(" PASS: Created layer with z_index=1 (above entities)")
else:
print(" FAIL: Layer z_index incorrect")
sys.exit(1)
print("\n--- Test 8: Layer above entities (z_index >= 0) ---")
fog_layer = grid.add_layer("color", z_index=1)
if fog_layer.z_index == 1:
print(" PASS: Created layer with z_index=1 (above entities)")
else:
print(" FAIL: Layer z_index incorrect")
sys.exit(1)
# Set fog
fog_layer.fill(mcrfpy.Color(0, 0, 0, 128))
print(" PASS: Fog layer filled")
# Set fog
fog_layer.fill(mcrfpy.Color(0, 0, 0, 128))
print(" PASS: Fog layer filled")
print("\n--- Test 9: Remove layer ---")
initial_count = len(grid.layers)
grid.remove_layer(fog_layer)
final_count = len(grid.layers)
if final_count == initial_count - 1:
print(f" PASS: Layer removed ({initial_count} -> {final_count})")
else:
print(f" FAIL: Layer count didn't decrease ({initial_count} -> {final_count})")
sys.exit(1)
print("\n--- Test 9: Remove layer ---")
initial_count = len(grid.layers)
grid.remove_layer(fog_layer)
final_count = len(grid.layers)
if final_count == initial_count - 1:
print(f" PASS: Layer removed ({initial_count} -> {final_count})")
else:
print(f" FAIL: Layer count didn't decrease ({initial_count} -> {final_count})")
sys.exit(1)
print("\n--- Test 10: Layer visibility toggle ---")
color_layer.visible = False
if not color_layer.visible:
print(" PASS: Layer visibility can be toggled")
else:
print(" FAIL: Layer visibility toggle failed")
sys.exit(1)
color_layer.visible = True
print("\n--- Test 10: Layer visibility toggle ---")
color_layer.visible = False
if not color_layer.visible:
print(" PASS: Layer visibility can be toggled")
else:
print(" FAIL: Layer visibility toggle failed")
sys.exit(1)
color_layer.visible = True
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
sys.exit(0)
# Initialize and run
init = mcrfpy.Scene("init")
init.activate()
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
sys.exit(0)

View file

@ -14,144 +14,134 @@ import mcrfpy
import sys
import time
def run_test(timer, runtime):
print("=" * 60)
print("Issue #148 Regression Test: Layer Dirty Flags and Caching")
print("=" * 60)
print("=" * 60)
print("Issue #148 Regression Test: Layer Dirty Flags and Caching")
print("=" * 60)
# Create test scene
test = mcrfpy.Scene("test")
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Create test scene
test = mcrfpy.Scene("test")
mcrfpy.current_scene = test
ui = test.children
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Create grid with larger size for performance testing
grid = mcrfpy.Grid(pos=(50, 50), size=(500, 400), grid_size=(50, 40), texture=texture)
ui.append(grid)
test.activate()
# Create grid with larger size for performance testing
grid = mcrfpy.Grid(pos=(50, 50), size=(500, 400), grid_size=(50, 40), texture=texture)
ui.append(grid)
print("\n--- Test 1: Layer creation (starts dirty) ---")
color_layer = grid.add_layer("color", z_index=-1)
# The layer should be dirty initially
# We can't directly check dirty flag from Python, but we verify the system works
print(" ColorLayer created successfully")
print("\n--- Test 1: Layer creation (starts dirty) ---")
color_layer = grid.add_layer("color", z_index=-1)
# The layer should be dirty initially
# We can't directly check dirty flag from Python, but we verify the system works
print(" ColorLayer created successfully")
tile_layer = grid.add_layer("tile", z_index=-2, texture=texture)
print(" TileLayer created successfully")
print(" PASS: Layers created")
tile_layer = grid.add_layer("tile", z_index=-2, texture=texture)
print(" TileLayer created successfully")
print(" PASS: Layers created")
print("\n--- Test 2: Fill operations work ---")
# Fill with some data
color_layer.fill(mcrfpy.Color(128, 0, 128, 64))
print(" ColorLayer filled with purple overlay")
print("\n--- Test 2: Fill operations work ---")
# Fill with some data
color_layer.fill(mcrfpy.Color(128, 0, 128, 64))
print(" ColorLayer filled with purple overlay")
tile_layer.fill(5) # Fill with tile index 5
print(" TileLayer filled with tile index 5")
print(" PASS: Fill operations completed")
tile_layer.fill(5) # Fill with tile index 5
print(" TileLayer filled with tile index 5")
print(" PASS: Fill operations completed")
print("\n--- Test 3: Cell set operations work ---")
# Set individual cells
color_layer.set(10, 10, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(11, 10, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(10, 11, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(11, 11, mcrfpy.Color(255, 255, 0, 128))
print(" Set 4 cells in ColorLayer to yellow")
print("\n--- Test 3: Cell set operations work ---")
# Set individual cells
color_layer.set(10, 10, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(11, 10, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(10, 11, mcrfpy.Color(255, 255, 0, 128))
color_layer.set(11, 11, mcrfpy.Color(255, 255, 0, 128))
print(" Set 4 cells in ColorLayer to yellow")
tile_layer.set(15, 15, 10)
tile_layer.set(16, 15, 11)
tile_layer.set(15, 16, 10)
tile_layer.set(16, 16, 11)
print(" Set 4 cells in TileLayer to different tiles")
print(" PASS: Cell set operations completed")
tile_layer.set(15, 15, 10)
tile_layer.set(16, 15, 11)
tile_layer.set(15, 16, 10)
tile_layer.set(16, 16, 11)
print(" Set 4 cells in TileLayer to different tiles")
print(" PASS: Cell set operations completed")
print("\n--- Test 4: Texture change on TileLayer ---")
# Create a second texture and assign it
texture2 = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
tile_layer.texture = texture2
print(" Changed TileLayer texture")
print("\n--- Test 4: Texture change on TileLayer ---")
# Create a second texture and assign it
texture2 = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
tile_layer.texture = texture2
print(" Changed TileLayer texture")
# Set back to original
tile_layer.texture = texture
print(" Restored original texture")
print(" PASS: Texture changes work")
# Set back to original
tile_layer.texture = texture
print(" Restored original texture")
print(" PASS: Texture changes work")
print("\n--- Test 5: Viewport changes (should use cached texture) ---")
# Pan around - these should NOT cause layer re-renders (just blit different region)
original_center = grid.center
print(f" Original center: {original_center}")
print("\n--- Test 5: Viewport changes (should use cached texture) ---")
# Pan around - these should NOT cause layer re-renders (just blit different region)
original_center = grid.center
print(f" Original center: {original_center}")
# Perform multiple viewport changes
for i in range(10):
grid.center = (100 + i * 20, 80 + i * 10)
print(" Performed 10 center changes")
# Perform multiple viewport changes
for i in range(10):
grid.center = (100 + i * 20, 80 + i * 10)
print(" Performed 10 center changes")
# Zoom changes
original_zoom = grid.zoom
for z in [1.0, 0.8, 1.2, 0.5, 1.5, 1.0]:
grid.zoom = z
print(" Performed 6 zoom changes")
# Zoom changes
original_zoom = grid.zoom
for z in [1.0, 0.8, 1.2, 0.5, 1.5, 1.0]:
grid.zoom = z
print(" Performed 6 zoom changes")
# Restore
grid.center = original_center
grid.zoom = original_zoom
print(" PASS: Viewport changes completed without crashing")
# Restore
grid.center = original_center
grid.zoom = original_zoom
print(" PASS: Viewport changes completed without crashing")
print("\n--- Test 6: Performance benchmark ---")
# Create a large layer for performance testing
perf_grid = mcrfpy.Grid(pos=(50, 50), size=(600, 500), grid_size=(100, 80), texture=texture)
ui.append(perf_grid)
perf_layer = perf_grid.add_layer("tile", z_index=-1, texture=texture)
print("\n--- Test 6: Performance benchmark ---")
# Create a large layer for performance testing
perf_grid = mcrfpy.Grid(pos=(50, 50), size=(600, 500), grid_size=(100, 80), texture=texture)
ui.append(perf_grid)
perf_layer = perf_grid.add_layer("tile", z_index=-1, texture=texture)
# Fill with data
perf_layer.fill(1)
# Fill with data
perf_layer.fill(1)
# First render will be slow (cache miss)
start = time.time()
test.activate() # Force render
first_render = time.time() - start
print(f" First render (cache build): {first_render*1000:.2f}ms")
# Render a frame to build cache
mcrfpy.step(0.01)
# Subsequent viewport changes should be fast (cache hit)
# We simulate by changing center multiple times
start = time.time()
for i in range(5):
perf_grid.center = (200 + i * 10, 160 + i * 8)
viewport_changes = time.time() - start
print(f" 5 viewport changes: {viewport_changes*1000:.2f}ms")
# Subsequent viewport changes should be fast (cache hit)
start = time.time()
for i in range(5):
perf_grid.center = (200 + i * 10, 160 + i * 8)
viewport_changes = time.time() - start
print(f" 5 viewport changes: {viewport_changes*1000:.2f}ms")
print(" PASS: Performance benchmark completed")
print(" PASS: Performance benchmark completed")
print("\n--- Test 7: Layer visibility toggle ---")
color_layer.visible = False
print(" ColorLayer hidden")
color_layer.visible = True
print(" ColorLayer shown")
print(" PASS: Visibility toggle works")
print("\n--- Test 7: Layer visibility toggle ---")
color_layer.visible = False
print(" ColorLayer hidden")
color_layer.visible = True
print(" ColorLayer shown")
print(" PASS: Visibility toggle works")
print("\n--- Test 8: Large grid stress test ---")
# Test with maximum size grid to ensure texture caching works
stress_grid = mcrfpy.Grid(pos=(10, 10), size=(200, 150), grid_size=(200, 150), texture=texture)
ui.append(stress_grid)
stress_layer = stress_grid.add_layer("color", z_index=-1)
print("\n--- Test 8: Large grid stress test ---")
# Test with maximum size grid to ensure texture caching works
stress_grid = mcrfpy.Grid(pos=(10, 10), size=(200, 150), grid_size=(200, 150), texture=texture)
ui.append(stress_grid)
stress_layer = stress_grid.add_layer("color", z_index=-1)
# This would be 30,000 cells - should handle via caching
stress_layer.fill(mcrfpy.Color(0, 100, 200, 100))
# This would be 30,000 cells - should handle via caching
stress_layer.fill(mcrfpy.Color(0, 100, 200, 100))
# Set a few specific cells
for x in range(10):
for y in range(10):
stress_layer.set(x, y, mcrfpy.Color(255, 0, 0, 200))
# Set a few specific cells
for x in range(10):
for y in range(10):
stress_layer.set(x, y, mcrfpy.Color(255, 0, 0, 200))
print(" Created 200x150 grid with 30,000 cells")
print(" PASS: Large grid handled successfully")
print(" Created 200x150 grid with 30,000 cells")
print(" PASS: Large grid handled successfully")
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
print("\nNote: Dirty flag behavior is internal - tests verify API works")
print("Actual caching benefits are measured by render performance.")
sys.exit(0)
# Initialize and run
init = mcrfpy.Scene("init")
init.activate()
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
print("\n" + "=" * 60)
print("All tests PASSED")
print("=" * 60)
print("\nNote: Dirty flag behavior is internal - tests verify API works")
print("Actual caching benefits are measured by render performance.")
sys.exit(0)

View file

@ -66,10 +66,10 @@ def test_entity_positions():
if entity.grid_x != 4 or entity.grid_y != 6:
errors.append(f"After setting pos, grid_x/y: expected (4, 6), got ({entity.grid_x}, {entity.grid_y})")
# Test 8: repr should show grid_x/grid_y
# Test 8: repr should show position info
repr_str = repr(entity)
if "grid_x=" not in repr_str or "grid_y=" not in repr_str:
errors.append(f"repr should contain grid_x/grid_y: {repr_str}")
if "draw_pos=" not in repr_str:
errors.append(f"repr should contain draw_pos: {repr_str}")
return errors

View file

@ -10,7 +10,7 @@ import sys
print("Starting test...")
# Create a simple grid without texture (should work in headless mode)
grid = mcrfpy.Grid(grid_x=10, grid_y=8)
grid = mcrfpy.Grid(grid_w=10, grid_h=8)
print(f"Created grid: {grid}")
# Test various grid positions

View file

@ -27,8 +27,7 @@ def test_colorlayer_docs():
"at(x, y)",
"set(x, y",
"fill(",
"Grid.add_layer",
"visible",
"add_layer",
"Example",
]
@ -66,8 +65,7 @@ def test_tilelayer_docs():
"fill(",
"-1", # Special value for no tile
"sprite",
"Grid.add_layer",
"visible",
"add_layer",
"Example",
]

View file

@ -1,84 +0,0 @@
#!/usr/bin/env python3
"""
Test for Issue #37: Windows scripts subdirectory not checked for .py files
This test checks if the game can find and load scripts/game.py from different working directories.
On Windows, this often fails because fopen uses relative paths without resolving them.
"""
import os
import sys
import subprocess
import tempfile
import shutil
def test_script_loading():
# Create a temporary directory to test from
with tempfile.TemporaryDirectory() as tmpdir:
print(f"Testing from directory: {tmpdir}")
# Get the build directory (assuming we're running from the repo root)
build_dir = os.path.abspath("build")
mcrogueface_exe = os.path.join(build_dir, "mcrogueface")
if os.name == "nt": # Windows
mcrogueface_exe += ".exe"
# Create a simple test script that the game should load
test_script = """
import mcrfpy
print("TEST SCRIPT LOADED SUCCESSFULLY")
test_scene = mcrfpy.Scene("test_scene")
"""
# Save the original game.py
game_py_path = os.path.join(build_dir, "scripts", "game.py")
game_py_backup = game_py_path + ".backup"
if os.path.exists(game_py_path):
shutil.copy(game_py_path, game_py_backup)
try:
# Replace game.py with our test script
os.makedirs(os.path.dirname(game_py_path), exist_ok=True)
with open(game_py_path, "w") as f:
f.write(test_script)
# Test 1: Run from build directory (should work)
print("\nTest 1: Running from build directory...")
result = subprocess.run(
[mcrogueface_exe, "--headless", "-c", "print('Test 1 complete')"],
cwd=build_dir,
capture_output=True,
text=True,
timeout=5
)
if "TEST SCRIPT LOADED SUCCESSFULLY" in result.stdout:
print("✓ Test 1 PASSED: Script loaded from build directory")
else:
print("✗ Test 1 FAILED: Script not loaded from build directory")
print(f"stdout: {result.stdout}")
print(f"stderr: {result.stderr}")
# Test 2: Run from temporary directory (often fails on Windows)
print("\nTest 2: Running from different working directory...")
result = subprocess.run(
[mcrogueface_exe, "--headless", "-c", "print('Test 2 complete')"],
cwd=tmpdir,
capture_output=True,
text=True,
timeout=5
)
if "TEST SCRIPT LOADED SUCCESSFULLY" in result.stdout:
print("✓ Test 2 PASSED: Script loaded from different directory")
else:
print("✗ Test 2 FAILED: Script not loaded from different directory")
print(f"stdout: {result.stdout}")
print(f"stderr: {result.stderr}")
print("\nThis is the bug described in Issue #37!")
finally:
# Restore original game.py
if os.path.exists(game_py_backup):
shutil.move(game_py_backup, game_py_path)
if __name__ == "__main__":
test_script_loading()

View file

@ -17,72 +17,68 @@ class CustomEntity(mcrfpy.Entity):
def custom_method(self):
return "Custom method called"
def run_test(timer, runtime):
"""Test that derived entity classes maintain their type in collections"""
try:
# Create a grid
grid = mcrfpy.Grid(grid_size=(10, 10))
# Create instances of base and derived entities
base_entity = mcrfpy.Entity((1, 1))
custom_entity = CustomEntity((2, 2))
# Add them to the grid's entity collection
grid.entities.append(base_entity)
grid.entities.append(custom_entity)
# Retrieve them back
retrieved_base = grid.entities[0]
retrieved_custom = grid.entities[1]
print(f"Base entity type: {type(retrieved_base)}")
print(f"Custom entity type: {type(retrieved_custom)}")
# Test 1: Check if base entity is correct type
if type(retrieved_base).__name__ == "Entity":
print("✓ Test 1 PASSED: Base entity maintains correct type")
else:
print("✗ Test 1 FAILED: Base entity has wrong type")
# Test 2: Check if custom entity maintains its derived type
if type(retrieved_custom).__name__ == "CustomEntity":
print("✓ Test 2 PASSED: Derived entity maintains correct type")
# Test 3: Check if custom attributes are preserved
try:
attr = retrieved_custom.custom_attribute
method_result = retrieved_custom.custom_method()
print(f"✓ Test 3 PASSED: Custom attributes preserved - {attr}, {method_result}")
except AttributeError as e:
print(f"✗ Test 3 FAILED: Custom attributes lost - {e}")
else:
print("✗ Test 2 FAILED: Derived entity type lost!")
print("This is the bug described in Issue #76!")
# Try to access custom attributes anyway
try:
attr = retrieved_custom.custom_attribute
print(f" - Has custom_attribute: {attr} (but wrong type)")
except AttributeError:
print(" - Lost custom_attribute")
# Test 4: Check iteration
print("\nTesting iteration:")
for i, entity in enumerate(grid.entities):
print(f" Entity {i}: {type(entity).__name__}")
print("\nTest complete")
except Exception as e:
print(f"Test error: {e}")
import traceback
traceback.print_exc()
sys.exit(0)
# Set up the test scene
test = mcrfpy.Scene("test")
test.activate()
mcrfpy.current_scene = test
# Schedule test to run after game loop starts
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
# Run the test
try:
# Create a grid
grid = mcrfpy.Grid(grid_size=(10, 10))
# Create instances of base and derived entities
base_entity = mcrfpy.Entity((1, 1))
custom_entity = CustomEntity((2, 2))
# Add them to the grid's entity collection
grid.entities.append(base_entity)
grid.entities.append(custom_entity)
# Retrieve them back
retrieved_base = grid.entities[0]
retrieved_custom = grid.entities[1]
print(f"Base entity type: {type(retrieved_base)}")
print(f"Custom entity type: {type(retrieved_custom)}")
# Test 1: Check if base entity is correct type
if type(retrieved_base).__name__ == "Entity":
print("PASS: Test 1 - Base entity maintains correct type")
else:
print("FAIL: Test 1 - Base entity has wrong type")
# Test 2: Check if custom entity maintains its derived type
if type(retrieved_custom).__name__ == "CustomEntity":
print("PASS: Test 2 - Derived entity maintains correct type")
# Test 3: Check if custom attributes are preserved
try:
attr = retrieved_custom.custom_attribute
method_result = retrieved_custom.custom_method()
print(f"PASS: Test 3 - Custom attributes preserved - {attr}, {method_result}")
except AttributeError as e:
print(f"FAIL: Test 3 - Custom attributes lost - {e}")
else:
print("FAIL: Test 2 - Derived entity type lost!")
print("This is the bug described in Issue #76!")
# Try to access custom attributes anyway
try:
attr = retrieved_custom.custom_attribute
print(f" - Has custom_attribute: {attr} (but wrong type)")
except AttributeError:
print(" - Lost custom_attribute")
# Test 4: Check iteration
print("\nTesting iteration:")
for i, entity in enumerate(grid.entities):
print(f" Entity {i}: {type(entity).__name__}")
print("\nTest complete")
except Exception as e:
print(f"Test error: {e}")
import traceback
traceback.print_exc()
sys.exit(0)

View file

@ -11,160 +11,155 @@ import sys
def test_color_properties():
"""Test Color r, g, b, a property access and modification"""
print("=== Testing Color r, g, b, a Properties (Issue #79) ===\n")
tests_passed = 0
tests_total = 0
# Test 1: Create color and check properties
print("--- Test 1: Basic property access ---")
color1 = mcrfpy.Color(255, 128, 64, 32)
tests_total += 1
if color1.r == 255:
print("PASS: color.r returns correct value (255)")
print("PASS: color.r returns correct value (255)")
tests_passed += 1
else:
print(f"FAIL: color.r returned {color1.r} instead of 255")
print(f"FAIL: color.r returned {color1.r} instead of 255")
tests_total += 1
if color1.g == 128:
print("PASS: color.g returns correct value (128)")
print("PASS: color.g returns correct value (128)")
tests_passed += 1
else:
print(f"FAIL: color.g returned {color1.g} instead of 128")
print(f"FAIL: color.g returned {color1.g} instead of 128")
tests_total += 1
if color1.b == 64:
print("PASS: color.b returns correct value (64)")
print("PASS: color.b returns correct value (64)")
tests_passed += 1
else:
print(f"FAIL: color.b returned {color1.b} instead of 64")
print(f"FAIL: color.b returned {color1.b} instead of 64")
tests_total += 1
if color1.a == 32:
print("PASS: color.a returns correct value (32)")
print("PASS: color.a returns correct value (32)")
tests_passed += 1
else:
print(f"FAIL: color.a returned {color1.a} instead of 32")
print(f"FAIL: color.a returned {color1.a} instead of 32")
# Test 2: Modify properties
print("\n--- Test 2: Property modification ---")
color1.r = 200
color1.g = 100
color1.b = 50
color1.a = 25
tests_total += 1
if color1.r == 200:
print("PASS: color.r set successfully")
print("PASS: color.r set successfully")
tests_passed += 1
else:
print(f"FAIL: color.r is {color1.r} after setting to 200")
print(f"FAIL: color.r is {color1.r} after setting to 200")
tests_total += 1
if color1.g == 100:
print("PASS: color.g set successfully")
print("PASS: color.g set successfully")
tests_passed += 1
else:
print(f"FAIL: color.g is {color1.g} after setting to 100")
print(f"FAIL: color.g is {color1.g} after setting to 100")
tests_total += 1
if color1.b == 50:
print("PASS: color.b set successfully")
print("PASS: color.b set successfully")
tests_passed += 1
else:
print(f"FAIL: color.b is {color1.b} after setting to 50")
print(f"FAIL: color.b is {color1.b} after setting to 50")
tests_total += 1
if color1.a == 25:
print("PASS: color.a set successfully")
print("PASS: color.a set successfully")
tests_passed += 1
else:
print(f"FAIL: color.a is {color1.a} after setting to 25")
print(f"FAIL: color.a is {color1.a} after setting to 25")
# Test 3: Boundary values
print("\n--- Test 3: Boundary value tests ---")
color2 = mcrfpy.Color(0, 0, 0, 0)
tests_total += 1
if color2.r == 0 and color2.g == 0 and color2.b == 0 and color2.a == 0:
print("PASS: Minimum values (0) work correctly")
print("PASS: Minimum values (0) work correctly")
tests_passed += 1
else:
print("FAIL: Minimum values not working")
print("FAIL: Minimum values not working")
color3 = mcrfpy.Color(255, 255, 255, 255)
tests_total += 1
if color3.r == 255 and color3.g == 255 and color3.b == 255 and color3.a == 255:
print("PASS: Maximum values (255) work correctly")
print("PASS: Maximum values (255) work correctly")
tests_passed += 1
else:
print("FAIL: Maximum values not working")
print("FAIL: Maximum values not working")
# Test 4: Invalid value handling
print("\n--- Test 4: Invalid value handling ---")
tests_total += 1
try:
color3.r = 256 # Out of range
print("FAIL: Should have raised ValueError for value > 255")
print("FAIL: Should have raised ValueError for value > 255")
except ValueError as e:
print(f"PASS: Correctly raised ValueError: {e}")
print(f"PASS: Correctly raised ValueError: {e}")
tests_passed += 1
tests_total += 1
try:
color3.g = -1 # Out of range
print("FAIL: Should have raised ValueError for value < 0")
print("FAIL: Should have raised ValueError for value < 0")
except ValueError as e:
print(f"PASS: Correctly raised ValueError: {e}")
print(f"PASS: Correctly raised ValueError: {e}")
tests_passed += 1
tests_total += 1
try:
color3.b = "red" # Wrong type
print("FAIL: Should have raised TypeError for string value")
print("FAIL: Should have raised TypeError for string value")
except TypeError as e:
print(f"PASS: Correctly raised TypeError: {e}")
print(f"PASS: Correctly raised TypeError: {e}")
tests_passed += 1
# Test 5: Verify __repr__ shows correct values
print("\n--- Test 5: String representation ---")
color4 = mcrfpy.Color(10, 20, 30, 40)
repr_str = repr(color4)
tests_total += 1
if "(10, 20, 30, 40)" in repr_str:
print(f"PASS: __repr__ shows correct values: {repr_str}")
print(f"PASS: __repr__ shows correct values: {repr_str}")
tests_passed += 1
else:
print(f"FAIL: __repr__ incorrect: {repr_str}")
print(f"FAIL: __repr__ incorrect: {repr_str}")
# Summary
print(f"\n=== SUMMARY ===")
print(f"Tests passed: {tests_passed}/{tests_total}")
if tests_passed == tests_total:
print("\nIssue #79 FIXED: Color properties now work correctly!")
else:
print("\nIssue #79: Some tests failed")
return tests_passed == tests_total
def run_test(timer, runtime):
"""Timer callback to run the test"""
try:
success = test_color_properties()
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)
return tests_passed == tests_total
# Set up the test scene
test = mcrfpy.Scene("test")
test.activate()
mcrfpy.current_scene = test
# Schedule test to run after game loop starts
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
try:
success = test_color_properties()
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)

View file

@ -12,213 +12,208 @@ import sys
def test_texture_properties():
"""Test Texture properties"""
print("=== Testing Texture Properties ===")
tests_passed = 0
tests_total = 0
# Create a texture
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
# Test 1: sprite_width property
tests_total += 1
try:
width = texture.sprite_width
if width == 16:
print(f"PASS: sprite_width = {width}")
print(f"PASS: sprite_width = {width}")
tests_passed += 1
else:
print(f"FAIL: sprite_width = {width}, expected 16")
print(f"FAIL: sprite_width = {width}, expected 16")
except AttributeError as e:
print(f"FAIL: sprite_width not accessible: {e}")
print(f"FAIL: sprite_width not accessible: {e}")
# Test 2: sprite_height property
tests_total += 1
try:
height = texture.sprite_height
if height == 16:
print(f"PASS: sprite_height = {height}")
print(f"PASS: sprite_height = {height}")
tests_passed += 1
else:
print(f"FAIL: sprite_height = {height}, expected 16")
print(f"FAIL: sprite_height = {height}, expected 16")
except AttributeError as e:
print(f"FAIL: sprite_height not accessible: {e}")
print(f"FAIL: sprite_height not accessible: {e}")
# Test 3: sheet_width property
tests_total += 1
try:
sheet_w = texture.sheet_width
if isinstance(sheet_w, int) and sheet_w > 0:
print(f"PASS: sheet_width = {sheet_w}")
print(f"PASS: sheet_width = {sheet_w}")
tests_passed += 1
else:
print(f"FAIL: sheet_width invalid: {sheet_w}")
print(f"FAIL: sheet_width invalid: {sheet_w}")
except AttributeError as e:
print(f"FAIL: sheet_width not accessible: {e}")
print(f"FAIL: sheet_width not accessible: {e}")
# Test 4: sheet_height property
tests_total += 1
try:
sheet_h = texture.sheet_height
if isinstance(sheet_h, int) and sheet_h > 0:
print(f"PASS: sheet_height = {sheet_h}")
print(f"PASS: sheet_height = {sheet_h}")
tests_passed += 1
else:
print(f"FAIL: sheet_height invalid: {sheet_h}")
print(f"FAIL: sheet_height invalid: {sheet_h}")
except AttributeError as e:
print(f"FAIL: sheet_height not accessible: {e}")
print(f"FAIL: sheet_height not accessible: {e}")
# Test 5: sprite_count property
tests_total += 1
try:
count = texture.sprite_count
expected = texture.sheet_width * texture.sheet_height
if count == expected:
print(f"PASS: sprite_count = {count} (sheet_width * sheet_height)")
print(f"PASS: sprite_count = {count} (sheet_width * sheet_height)")
tests_passed += 1
else:
print(f"FAIL: sprite_count = {count}, expected {expected}")
print(f"FAIL: sprite_count = {count}, expected {expected}")
except AttributeError as e:
print(f"FAIL: sprite_count not accessible: {e}")
print(f"FAIL: sprite_count not accessible: {e}")
# Test 6: source property
tests_total += 1
try:
source = texture.source
if "kenney_tinydungeon.png" in source:
print(f"PASS: source = '{source}'")
print(f"PASS: source = '{source}'")
tests_passed += 1
else:
print(f"FAIL: source unexpected: '{source}'")
print(f"FAIL: source unexpected: '{source}'")
except AttributeError as e:
print(f"FAIL: source not accessible: {e}")
print(f"FAIL: source not accessible: {e}")
# Test 7: Properties are read-only
tests_total += 1
try:
texture.sprite_width = 32 # Should fail
print("FAIL: sprite_width should be read-only")
print("FAIL: sprite_width should be read-only")
except AttributeError as e:
print(f"PASS: sprite_width is read-only: {e}")
print(f"PASS: sprite_width is read-only: {e}")
tests_passed += 1
return tests_passed, tests_total
def test_font_properties():
"""Test Font properties"""
print("\n=== Testing Font Properties ===")
tests_passed = 0
tests_total = 0
# Create a font
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
# Test 1: family property
tests_total += 1
try:
family = font.family
if isinstance(family, str) and len(family) > 0:
print(f"PASS: family = '{family}'")
print(f"PASS: family = '{family}'")
tests_passed += 1
else:
print(f"FAIL: family invalid: '{family}'")
print(f"FAIL: family invalid: '{family}'")
except AttributeError as e:
print(f"FAIL: family not accessible: {e}")
print(f"FAIL: family not accessible: {e}")
# Test 2: source property
tests_total += 1
try:
source = font.source
if "JetbrainsMono.ttf" in source:
print(f"PASS: source = '{source}'")
print(f"PASS: source = '{source}'")
tests_passed += 1
else:
print(f"FAIL: source unexpected: '{source}'")
print(f"FAIL: source unexpected: '{source}'")
except AttributeError as e:
print(f"FAIL: source not accessible: {e}")
print(f"FAIL: source not accessible: {e}")
# Test 3: Properties are read-only
tests_total += 1
try:
font.family = "Arial" # Should fail
print("FAIL: family should be read-only")
print("FAIL: family should be read-only")
except AttributeError as e:
print(f"PASS: family is read-only: {e}")
print(f"PASS: family is read-only: {e}")
tests_passed += 1
return tests_passed, tests_total
def test_property_introspection():
"""Test that properties appear in dir()"""
print("\n=== Testing Property Introspection ===")
tests_passed = 0
tests_total = 0
# Test Texture properties in dir()
tests_total += 1
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
texture_props = dir(texture)
expected_texture_props = ['sprite_width', 'sprite_height', 'sheet_width', 'sheet_height', 'sprite_count', 'source']
missing = [p for p in expected_texture_props if p not in texture_props]
if not missing:
print("PASS: All Texture properties appear in dir()")
print("PASS: All Texture properties appear in dir()")
tests_passed += 1
else:
print(f"FAIL: Missing Texture properties in dir(): {missing}")
print(f"FAIL: Missing Texture properties in dir(): {missing}")
# Test Font properties in dir()
tests_total += 1
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
font_props = dir(font)
expected_font_props = ['family', 'source']
missing = [p for p in expected_font_props if p not in font_props]
if not missing:
print("PASS: All Font properties appear in dir()")
print("PASS: All Font properties appear in dir()")
tests_passed += 1
else:
print(f"✗ FAIL: Missing Font properties in dir(): {missing}")
return tests_passed, tests_total
print(f"FAIL: Missing Font properties in dir(): {missing}")
def run_test(timer, runtime):
"""Timer callback to run the test"""
try:
print("=== Testing Texture and Font Properties (Issue #99) ===\n")
texture_passed, texture_total = test_texture_properties()
font_passed, font_total = test_font_properties()
intro_passed, intro_total = test_property_introspection()
total_passed = texture_passed + font_passed + intro_passed
total_tests = texture_total + font_total + intro_total
print(f"\n=== SUMMARY ===")
print(f"Texture tests: {texture_passed}/{texture_total}")
print(f"Font tests: {font_passed}/{font_total}")
print(f"Introspection tests: {intro_passed}/{intro_total}")
print(f"Total tests passed: {total_passed}/{total_tests}")
if total_passed == total_tests:
print("\nIssue #99 FIXED: Texture and Font properties exposed successfully!")
print("\nOverall result: PASS")
else:
print("\nIssue #99: Some tests failed")
print("\nOverall result: FAIL")
except Exception as e:
print(f"\nTest error: {e}")
import traceback
traceback.print_exc()
print("\nOverall result: FAIL")
sys.exit(0)
return tests_passed, tests_total
# Set up the test scene
test = mcrfpy.Scene("test")
test.activate()
mcrfpy.current_scene = test
# Schedule test to run after game loop starts
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
try:
print("=== Testing Texture and Font Properties (Issue #99) ===\n")
texture_passed, texture_total = test_texture_properties()
font_passed, font_total = test_font_properties()
intro_passed, intro_total = test_property_introspection()
total_passed = texture_passed + font_passed + intro_passed
total_tests = texture_total + font_total + intro_total
print(f"\n=== SUMMARY ===")
print(f"Texture tests: {texture_passed}/{texture_total}")
print(f"Font tests: {font_passed}/{font_total}")
print(f"Introspection tests: {intro_passed}/{intro_total}")
print(f"Total tests passed: {total_passed}/{total_tests}")
if total_passed == total_tests:
print("\nIssue #99 FIXED: Texture and Font properties exposed successfully!")
print("\nOverall result: PASS")
else:
print("\nIssue #99: Some tests failed")
print("\nOverall result: FAIL")
except Exception as e:
print(f"\nTest error: {e}")
import traceback
traceback.print_exc()
print("\nOverall result: FAIL")
sys.exit(0)

View file

@ -1,67 +0,0 @@
#!/usr/bin/env python3
"""
Minimal test for Issue #9: RenderTexture resize
"""
import mcrfpy
from mcrfpy import automation
import sys
def run_test(timer, runtime):
"""Test RenderTexture resizing"""
print("Testing Issue #9: RenderTexture resize (minimal)")
try:
# Create a grid
print("Creating grid...")
grid = mcrfpy.Grid(30, 30)
grid.x = 10
grid.y = 10
grid.w = 300
grid.h = 300
# Add to scene
scene_ui = test.children
scene_ui.append(grid)
# Test accessing grid points
print("Testing grid.at()...")
point = grid.at(5, 5)
print(f"Got grid point: {point}")
# Test color creation
print("Testing Color creation...")
red = mcrfpy.Color(255, 0, 0, 255)
print(f"Created color: {red}")
# Set color
print("Setting grid point color...")
point.color = red
print("Taking screenshot before resize...")
automation.screenshot("/tmp/issue_9_minimal_before.png")
# Resize grid
print("Resizing grid to 2500x2500...")
grid.w = 2500
grid.h = 2500
print("Taking screenshot after resize...")
automation.screenshot("/tmp/issue_9_minimal_after.png")
print("\nTest complete - check screenshots")
print("If RenderTexture is recreated properly, grid should render correctly at large size")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(0)
# Create and set scene
test = mcrfpy.Scene("test")
test.activate()
# Schedule test
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)

View file

@ -28,202 +28,193 @@ def add_border_markers(grid, grid_width, grid_height):
# Red border on top
for x in range(grid_width):
grid.at(x, 0).color = mcrfpy.Color(255, 0, 0, 255)
# Green border on right
for y in range(grid_height):
grid.at(grid_width-1, y).color = mcrfpy.Color(0, 255, 0, 255)
# Blue border on bottom
for x in range(grid_width):
grid.at(x, grid_height-1).color = mcrfpy.Color(0, 0, 255, 255)
# Yellow border on left
for y in range(grid_height):
grid.at(0, y).color = mcrfpy.Color(255, 255, 0, 255)
def test_rendertexture_resize():
"""Test RenderTexture behavior with various grid sizes"""
print("=== Testing UIGrid RenderTexture Resize (Issue #9) ===\n")
scene_ui = test.children
# Test 1: Small grid (should work fine)
print("--- Test 1: Small Grid (400x300) ---")
grid1 = mcrfpy.Grid(20, 15) # 20x15 tiles
grid1.x = 10
grid1.y = 10
grid1.w = 400
grid1.h = 300
scene_ui.append(grid1)
create_checkerboard_pattern(grid1, 20, 15)
add_border_markers(grid1, 20, 15)
automation.screenshot("/tmp/issue_9_small_grid.png")
print("✓ Small grid created and rendered")
# Test 2: Medium grid at 1920x1080 limit
print("\n--- Test 2: Medium Grid at 1920x1080 Limit ---")
grid2 = mcrfpy.Grid(64, 36) # 64x36 tiles at 30px each = 1920x1080
grid2.x = 10
grid2.y = 320
grid2.w = 1920
grid2.h = 1080
scene_ui.append(grid2)
create_checkerboard_pattern(grid2, 64, 36, 4)
add_border_markers(grid2, 64, 36)
automation.screenshot("/tmp/issue_9_limit_grid.png")
print("✓ Grid at RenderTexture limit created")
# Test 3: Resize grid1 beyond limits
print("\n--- Test 3: Resizing Small Grid Beyond 1920x1080 ---")
print("Original size: 400x300")
grid1.w = 2400
grid1.h = 1400
print(f"Resized to: {grid1.w}x{grid1.h}")
# The content should still be visible but may be clipped
automation.screenshot("/tmp/issue_9_resized_beyond_limit.png")
print("✗ EXPECTED ISSUE: Grid resized beyond RenderTexture limits")
print(" Content beyond 1920x1080 will be clipped!")
# Test 4: Create large grid from start
print("\n--- Test 4: Large Grid from Start (2400x1400) ---")
# Clear previous grids
while len(scene_ui) > 0:
scene_ui.remove(0)
grid3 = mcrfpy.Grid(80, 50) # Large tile count
grid3.x = 10
grid3.y = 10
grid3.w = 2400
grid3.h = 1400
scene_ui.append(grid3)
create_checkerboard_pattern(grid3, 80, 50, 5)
add_border_markers(grid3, 80, 50)
# Add markers at specific positions to test rendering
# Mark the center
center_x, center_y = 40, 25
for dx in range(-2, 3):
for dy in range(-2, 3):
grid3.at(center_x + dx, center_y + dy).color = mcrfpy.Color(255, 0, 255, 255) # Magenta
# Mark position at 1920 pixel boundary (64 tiles * 30 pixels/tile = 1920)
if 64 < 80: # Only if within grid bounds
for y in range(min(50, 10)):
grid3.at(64, y).color = mcrfpy.Color(255, 128, 0, 255) # Orange
automation.screenshot("/tmp/issue_9_large_grid.png")
print("✗ EXPECTED ISSUE: Large grid created")
print(" Content beyond 1920x1080 will not render!")
print(" Look for missing orange line at x=1920 boundary")
# Test 5: Dynamic resize test
print("\n--- Test 5: Dynamic Resize Test ---")
scene_ui.remove(0)
grid4 = mcrfpy.Grid(100, 100)
grid4.x = 10
grid4.y = 10
scene_ui.append(grid4)
sizes = [(500, 500), (1000, 1000), (1500, 1500), (2000, 2000), (2500, 2500)]
for i, (w, h) in enumerate(sizes):
grid4.w = w
grid4.h = h
# Add pattern at current size
visible_tiles_x = min(100, w // 30)
visible_tiles_y = min(100, h // 30)
# Clear and create new pattern
for x in range(visible_tiles_x):
for y in range(visible_tiles_y):
if x == visible_tiles_x - 1 or y == visible_tiles_y - 1:
# Edge markers
grid4.at(x, y).color = mcrfpy.Color(255, 255, 0, 255)
elif (x + y) % 10 == 0:
# Diagonal lines
grid4.at(x, y).color = mcrfpy.Color(0, 255, 255, 255)
automation.screenshot(f"/tmp/issue_9_resize_{w}x{h}.png")
if w > 1920 or h > 1080:
print(f"✗ Size {w}x{h}: Content clipped at 1920x1080")
else:
print(f"✓ Size {w}x{h}: Rendered correctly")
# Test 6: Verify exact clipping boundary
print("\n--- Test 6: Exact Clipping Boundary Test ---")
scene_ui.remove(0)
grid5 = mcrfpy.Grid(70, 40)
grid5.x = 0
grid5.y = 0
grid5.w = 2100 # 70 * 30 = 2100 pixels
grid5.h = 1200 # 40 * 30 = 1200 pixels
scene_ui.append(grid5)
# Create a pattern that shows the boundary clearly
for x in range(70):
for y in range(40):
pixel_x = x * 30
pixel_y = y * 30
if pixel_x == 1920 - 30: # Last tile before boundary
grid5.at(x, y).color = mcrfpy.Color(255, 0, 0, 255) # Red
elif pixel_x == 1920: # First tile after boundary
grid5.at(x, y).color = mcrfpy.Color(0, 255, 0, 255) # Green
elif pixel_y == 1080 - 30: # Last row before boundary
grid5.at(x, y).color = mcrfpy.Color(0, 0, 255, 255) # Blue
elif pixel_y == 1080: # First row after boundary
grid5.at(x, y).color = mcrfpy.Color(255, 255, 0, 255) # Yellow
else:
# Normal checkerboard
if (x + y) % 2 == 0:
grid5.at(x, y).color = mcrfpy.Color(200, 200, 200, 255)
automation.screenshot("/tmp/issue_9_boundary_test.png")
print("Screenshot saved showing clipping boundary")
print("- Red tiles: Last visible column (x=1890-1919)")
print("- Green tiles: First clipped column (x=1920+)")
print("- Blue tiles: Last visible row (y=1050-1079)")
print("- Yellow tiles: First clipped row (y=1080+)")
# Summary
print("\n=== SUMMARY ===")
print("Issue #9: UIGrid uses a hardcoded RenderTexture size of 1920x1080")
print("Problems demonstrated:")
print("1. Grids larger than 1920x1080 are clipped")
print("2. Resizing grids doesn't recreate the RenderTexture")
print("3. Content beyond the boundary is not rendered")
print("\nThe fix should:")
print("1. Recreate RenderTexture when grid size changes")
print("2. Use the actual grid dimensions instead of hardcoded values")
print("3. Consider memory limits for very large grids")
print(f"\nScreenshots saved to /tmp/issue_9_*.png")
def run_test(timer, runtime):
"""Timer callback to run the test"""
try:
test_rendertexture_resize()
print("\nTest complete - check screenshots for visual verification")
except Exception as e:
print(f"\nTest error: {e}")
import traceback
traceback.print_exc()
sys.exit(0)
# Set up the test scene
test = mcrfpy.Scene("test")
test.activate()
mcrfpy.current_scene = test
# Schedule test to run after game loop starts
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
print("=== Testing UIGrid RenderTexture Resize (Issue #9) ===\n")
scene_ui = test.children
# Test 1: Small grid (should work fine)
print("--- Test 1: Small Grid (400x300) ---")
grid1 = mcrfpy.Grid(20, 15) # 20x15 tiles
grid1.x = 10
grid1.y = 10
grid1.w = 400
grid1.h = 300
scene_ui.append(grid1)
create_checkerboard_pattern(grid1, 20, 15)
add_border_markers(grid1, 20, 15)
mcrfpy.step(0.01)
automation.screenshot("/tmp/issue_9_small_grid.png")
print("PASS: Small grid created and rendered")
# Test 2: Medium grid at 1920x1080 limit
print("\n--- Test 2: Medium Grid at 1920x1080 Limit ---")
grid2 = mcrfpy.Grid(64, 36) # 64x36 tiles at 30px each = 1920x1080
grid2.x = 10
grid2.y = 320
grid2.w = 1920
grid2.h = 1080
scene_ui.append(grid2)
create_checkerboard_pattern(grid2, 64, 36, 4)
add_border_markers(grid2, 64, 36)
mcrfpy.step(0.01)
automation.screenshot("/tmp/issue_9_limit_grid.png")
print("PASS: Grid at RenderTexture limit created")
# Test 3: Resize grid1 beyond limits
print("\n--- Test 3: Resizing Small Grid Beyond 1920x1080 ---")
print("Original size: 400x300")
grid1.w = 2400
grid1.h = 1400
print(f"Resized to: {grid1.w}x{grid1.h}")
# The content should still be visible but may be clipped
mcrfpy.step(0.01)
automation.screenshot("/tmp/issue_9_resized_beyond_limit.png")
print("EXPECTED ISSUE: Grid resized beyond RenderTexture limits")
print(" Content beyond 1920x1080 will be clipped!")
# Test 4: Create large grid from start
print("\n--- Test 4: Large Grid from Start (2400x1400) ---")
# Clear previous grids
while len(scene_ui) > 0:
scene_ui.remove(0)
grid3 = mcrfpy.Grid(80, 50) # Large tile count
grid3.x = 10
grid3.y = 10
grid3.w = 2400
grid3.h = 1400
scene_ui.append(grid3)
create_checkerboard_pattern(grid3, 80, 50, 5)
add_border_markers(grid3, 80, 50)
# Add markers at specific positions to test rendering
# Mark the center
center_x, center_y = 40, 25
for dx in range(-2, 3):
for dy in range(-2, 3):
grid3.at(center_x + dx, center_y + dy).color = mcrfpy.Color(255, 0, 255, 255) # Magenta
# Mark position at 1920 pixel boundary (64 tiles * 30 pixels/tile = 1920)
if 64 < 80: # Only if within grid bounds
for y in range(min(50, 10)):
grid3.at(64, y).color = mcrfpy.Color(255, 128, 0, 255) # Orange
mcrfpy.step(0.01)
automation.screenshot("/tmp/issue_9_large_grid.png")
print("EXPECTED ISSUE: Large grid created")
print(" Content beyond 1920x1080 will not render!")
print(" Look for missing orange line at x=1920 boundary")
# Test 5: Dynamic resize test
print("\n--- Test 5: Dynamic Resize Test ---")
scene_ui.remove(0)
grid4 = mcrfpy.Grid(100, 100)
grid4.x = 10
grid4.y = 10
scene_ui.append(grid4)
sizes = [(500, 500), (1000, 1000), (1500, 1500), (2000, 2000), (2500, 2500)]
for i, (w, h) in enumerate(sizes):
grid4.w = w
grid4.h = h
# Add pattern at current size
visible_tiles_x = min(100, w // 30)
visible_tiles_y = min(100, h // 30)
# Clear and create new pattern
for x in range(visible_tiles_x):
for y in range(visible_tiles_y):
if x == visible_tiles_x - 1 or y == visible_tiles_y - 1:
# Edge markers
grid4.at(x, y).color = mcrfpy.Color(255, 255, 0, 255)
elif (x + y) % 10 == 0:
# Diagonal lines
grid4.at(x, y).color = mcrfpy.Color(0, 255, 255, 255)
mcrfpy.step(0.01)
automation.screenshot(f"/tmp/issue_9_resize_{w}x{h}.png")
if w > 1920 or h > 1080:
print(f"FAIL: Size {w}x{h}: Content clipped at 1920x1080")
else:
print(f"PASS: Size {w}x{h}: Rendered correctly")
# Test 6: Verify exact clipping boundary
print("\n--- Test 6: Exact Clipping Boundary Test ---")
scene_ui.remove(0)
grid5 = mcrfpy.Grid(70, 40)
grid5.x = 0
grid5.y = 0
grid5.w = 2100 # 70 * 30 = 2100 pixels
grid5.h = 1200 # 40 * 30 = 1200 pixels
scene_ui.append(grid5)
# Create a pattern that shows the boundary clearly
for x in range(70):
for y in range(40):
pixel_x = x * 30
pixel_y = y * 30
if pixel_x == 1920 - 30: # Last tile before boundary
grid5.at(x, y).color = mcrfpy.Color(255, 0, 0, 255) # Red
elif pixel_x == 1920: # First tile after boundary
grid5.at(x, y).color = mcrfpy.Color(0, 255, 0, 255) # Green
elif pixel_y == 1080 - 30: # Last row before boundary
grid5.at(x, y).color = mcrfpy.Color(0, 0, 255, 255) # Blue
elif pixel_y == 1080: # First row after boundary
grid5.at(x, y).color = mcrfpy.Color(255, 255, 0, 255) # Yellow
else:
# Normal checkerboard
if (x + y) % 2 == 0:
grid5.at(x, y).color = mcrfpy.Color(200, 200, 200, 255)
mcrfpy.step(0.01)
automation.screenshot("/tmp/issue_9_boundary_test.png")
print("Screenshot saved showing clipping boundary")
print("- Red tiles: Last visible column (x=1890-1919)")
print("- Green tiles: First clipped column (x=1920+)")
print("- Blue tiles: Last visible row (y=1050-1079)")
print("- Yellow tiles: First clipped row (y=1080+)")
# Summary
print("\n=== SUMMARY ===")
print("Issue #9: UIGrid uses a hardcoded RenderTexture size of 1920x1080")
print("Problems demonstrated:")
print("1. Grids larger than 1920x1080 are clipped")
print("2. Resizing grids doesn't recreate the RenderTexture")
print("3. Content beyond the boundary is not rendered")
print("\nThe fix should:")
print("1. Recreate RenderTexture when grid size changes")
print("2. Use the actual grid dimensions instead of hardcoded values")
print("3. Consider memory limits for very large grids")
print(f"\nScreenshots saved to /tmp/issue_9_*.png")
print("\nTest complete - check screenshots for visual verification")
sys.exit(0)

View file

@ -1,89 +0,0 @@
#!/usr/bin/env python3
"""
Test for Issue #9: Recreate RenderTexture when UIGrid is resized
This test checks if resizing a UIGrid properly recreates its RenderTexture.
"""
import mcrfpy
from mcrfpy import automation
import sys
def run_test(timer, runtime):
"""Test that UIGrid properly handles resizing"""
try:
# Create a grid with initial size
grid = mcrfpy.Grid(20, 20)
grid.x = 50
grid.y = 50
grid.w = 200
grid.h = 200
# Add grid to scene
scene_ui = test.children
scene_ui.append(grid)
# Take initial screenshot
automation.screenshot("/tmp/grid_initial.png")
print("Initial grid created at 200x200")
# Add some visible content to the grid
for x in range(5):
for y in range(5):
grid.at(x, y).color = mcrfpy.Color(255, 0, 0, 255) # Red squares
automation.screenshot("/tmp/grid_with_content.png")
print("Added red squares to grid")
# Test 1: Resize the grid smaller
print("\nTest 1: Resizing grid to 100x100...")
grid.w = 100
grid.h = 100
automation.screenshot("/tmp/grid_resized_small.png")
# The grid should still render correctly
print("✓ Test 1: Grid resized to 100x100")
# Test 2: Resize the grid larger than initial
print("\nTest 2: Resizing grid to 400x400...")
grid.w = 400
grid.h = 400
automation.screenshot("/tmp/grid_resized_large.png")
# Add content at the edges to test if render texture is big enough
for x in range(15, 20):
for y in range(15, 20):
grid.at(x, y).color = mcrfpy.Color(0, 255, 0, 255) # Green squares
automation.screenshot("/tmp/grid_resized_with_edge_content.png")
print("✓ Test 2: Grid resized to 400x400 with edge content")
# Test 3: Resize beyond the hardcoded 1920x1080 limit
print("\nTest 3: Resizing grid beyond 1920x1080...")
grid.w = 2000
grid.h = 1200
automation.screenshot("/tmp/grid_resized_huge.png")
# This should fail with the current implementation
print("✗ Test 3: This likely shows rendering errors due to fixed RenderTexture size")
print("This is the bug described in Issue #9!")
print("\nScreenshots saved to /tmp/grid_*.png")
print("Check grid_resized_huge.png for rendering artifacts")
except Exception as e:
print(f"Test error: {e}")
import traceback
traceback.print_exc()
sys.exit(0)
# Set up the test scene
test = mcrfpy.Scene("test")
test.activate()
# Schedule test to run after game loop starts
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)

View file

@ -15,69 +15,45 @@ import sys
def demonstrate_solution():
"""Demonstrate how the solution should work"""
print("=== Type Preservation Solution Demonstration ===\n")
print("Current behavior (broken):")
print("1. Python creates derived object (e.g., MyFrame extends Frame)")
print("2. C++ stores only the shared_ptr<UIFrame>")
print("3. When retrieved, C++ creates a NEW PyUIFrameObject with type 'Frame'")
print("4. Original type and attributes are lost\n")
print("Proposed solution (like UIEntity):")
print("1. Add PyObject* self member to UIDrawable base class")
print("2. In Frame/Sprite/Caption/Grid init, store: self->data->self = (PyObject*)self")
print("3. In convertDrawableToPython, check if drawable->self exists")
print("4. If it exists, return the stored Python object (with INCREF)")
print("5. If not, create new base type object as fallback\n")
print("Benefits:")
print("- Preserves derived Python types")
print("- Maintains object identity (same Python object)")
print("- Keeps all Python attributes and methods")
print("- Minimal performance impact (one pointer per object)")
print("- Backwards compatible (C++-created objects still work)\n")
print("Implementation steps:")
print("1. Add 'PyObject* self = nullptr;' to UIDrawable class")
print("2. Update Frame/Sprite/Caption/Grid init methods to store self")
print("3. Update convertDrawableToPython in UICollection.cpp")
print("4. Handle reference counting properly (INCREF/DECREF)")
print("5. Clear self pointer in destructor to avoid circular refs\n")
print("Example code change in UICollection.cpp:")
print("""
static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
if (!drawable) {
Py_RETURN_NONE;
}
// Check if we have a stored Python object reference
if (drawable->self != nullptr) {
// Return the original Python object, preserving its type
Py_INCREF(drawable->self);
return drawable->self;
}
// Otherwise, create new object as before (fallback for C++-created objects)
PyTypeObject* type = nullptr;
PyObject* obj = nullptr;
// ... existing switch statement ...
}
""")
def run_test(timer, runtime):
"""Timer callback"""
try:
demonstrate_solution()
print("\nThis solution approach is proven to work in UIEntityCollection.")
print("It should be applied to UICollection for consistency.")
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
sys.exit(0)
# Set up scene and run
test = mcrfpy.Scene("test")
test.activate()
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
mcrfpy.current_scene = test
try:
demonstrate_solution()
print("\nThis solution approach is proven to work in UIEntityCollection.")
print("It should be applied to UICollection for consistency.")
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
sys.exit(0)