Covers two previously untested bug families from the memory safety audit: - #265: GridPointState references after entity grid transfer - #267, #275: Reference count leaks in collection/property access loops Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.5 KiB
Python
98 lines
2.5 KiB
Python
"""Regression test: Python object reference count leaks.
|
|
|
|
Issues #267, #275: Accessing certain properties (scene.children, entity
|
|
collections) would leak references, causing memory growth over time.
|
|
|
|
Fix: Proper Py_DECREF in collection accessors and property getters.
|
|
|
|
Note: sys.gettotalrefcount() is only available in debug Python builds.
|
|
This test uses a weaker proxy: create many objects, verify they're
|
|
collectible, and check that repeated property access doesn't accumulate
|
|
uncollectable objects.
|
|
"""
|
|
import mcrfpy
|
|
import sys
|
|
import gc
|
|
|
|
PASS = True
|
|
|
|
def check(name, condition):
|
|
global PASS
|
|
if not condition:
|
|
print(f"FAIL: {name}")
|
|
PASS = False
|
|
else:
|
|
print(f" ok: {name}")
|
|
|
|
# Test 1: scene.children access doesn't leak
|
|
scene = mcrfpy.Scene("refcount_test")
|
|
mcrfpy.current_scene = scene
|
|
|
|
for i in range(100):
|
|
children = scene.children
|
|
_ = len(children)
|
|
|
|
gc.collect()
|
|
check("scene.children access loop completed", True)
|
|
|
|
# Test 2: grid.entities access doesn't leak
|
|
grid = mcrfpy.Grid(grid_size=(10, 10))
|
|
scene.children.append(grid)
|
|
for i in range(5):
|
|
mcrfpy.Entity(grid_pos=(i % 10, 0), grid=grid)
|
|
|
|
for i in range(100):
|
|
entities = grid.entities
|
|
_ = len(entities)
|
|
|
|
gc.collect()
|
|
check("grid.entities access loop completed", True)
|
|
|
|
# Test 3: Repeated entity property access
|
|
entity = mcrfpy.Entity(grid_pos=(5, 5), grid=grid)
|
|
for i in range(100):
|
|
_ = entity.grid_pos
|
|
_ = entity.sprite_index
|
|
|
|
gc.collect()
|
|
check("entity property access loop completed", True)
|
|
|
|
# Test 4: Collection iteration doesn't leak
|
|
for i in range(50):
|
|
for child in scene.children:
|
|
pass
|
|
for ent in grid.entities:
|
|
pass
|
|
|
|
gc.collect()
|
|
check("collection iteration loop completed", True)
|
|
|
|
# Test 5: Creating and destroying objects in a loop
|
|
for i in range(50):
|
|
temp_scene = mcrfpy.Scene(f"temp_{i}")
|
|
f = mcrfpy.Frame(pos=(0, 0), size=(10, 10))
|
|
temp_scene.children.append(f)
|
|
del f
|
|
|
|
gc.collect()
|
|
check("create/destroy cycle completed", True)
|
|
|
|
# Test 6: If pydebug is available, do actual refcount check
|
|
if hasattr(sys, 'gettotalrefcount'):
|
|
gc.collect()
|
|
before = sys.gettotalrefcount()
|
|
for i in range(1000):
|
|
_ = scene.children
|
|
_ = grid.entities
|
|
gc.collect()
|
|
after = sys.gettotalrefcount()
|
|
growth = after - before
|
|
check(f"refcount growth <= 50 (got {growth})", growth <= 50)
|
|
else:
|
|
print(" skip: sys.gettotalrefcount not available (not a debug build)")
|
|
|
|
if PASS:
|
|
print("PASS")
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|