Add regression tests for vector NULL safety and uniform owner validity, closes #287
Covers #268 (sfVector2f_to_PyObject NULL propagation) and #272 (UniformCollection weak_ptr validity check). Combined with existing tests for #258-#278, this completes regression coverage for the full memory safety audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7d1066a5d5
commit
2b0267430d
2 changed files with 159 additions and 0 deletions
69
tests/regression/issue_268_vector_null_test.py
Normal file
69
tests/regression/issue_268_vector_null_test.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"""Regression test: sfVector2f_to_PyObject must handle allocation failure.
|
||||
|
||||
Issue #268: sfVector2f_to_PyObject in UIEntity.cpp could return NULL when
|
||||
tp_alloc fails, but callers didn't check. Under normal conditions this
|
||||
can't be triggered, but this test exercises all Vector-returning entity
|
||||
properties to ensure the code path works correctly and doesn't crash.
|
||||
|
||||
Fix: Callers now propagate NULL returns as Python exceptions.
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_entity_vector_properties():
|
||||
"""All Vector-returning properties must return valid Vector objects."""
|
||||
grid = mcrfpy.Grid(grid_size=(20, 20))
|
||||
entity = mcrfpy.Entity(grid_pos=(5, 5), grid=grid)
|
||||
|
||||
pos = entity.pos
|
||||
assert pos is not None, "entity.pos returned None"
|
||||
assert hasattr(pos, 'x') and hasattr(pos, 'y'), "pos missing x/y"
|
||||
|
||||
draw_pos = entity.draw_pos
|
||||
assert draw_pos is not None, "entity.draw_pos returned None"
|
||||
|
||||
cell_pos = entity.cell_pos
|
||||
assert cell_pos is not None, "entity.cell_pos returned None"
|
||||
assert cell_pos.x == 5 and cell_pos.y == 5, f"cell_pos wrong: {cell_pos.x}, {cell_pos.y}"
|
||||
|
||||
offset = entity.sprite_offset
|
||||
assert offset is not None, "entity.sprite_offset returned None"
|
||||
assert offset.x == 0.0 and offset.y == 0.0, "Default sprite_offset should be (0,0)"
|
||||
|
||||
print(" PASS: entity vector properties")
|
||||
|
||||
def test_entity_vector_setters():
|
||||
"""Vector property setters must accept tuples and Vectors."""
|
||||
grid = mcrfpy.Grid(grid_size=(20, 20))
|
||||
entity = mcrfpy.Entity(grid_pos=(3, 3), grid=grid)
|
||||
|
||||
entity.sprite_offset = (10.0, -5.0)
|
||||
assert entity.sprite_offset.x == 10.0, f"sprite_offset.x wrong: {entity.sprite_offset.x}"
|
||||
assert entity.sprite_offset.y == -5.0, f"sprite_offset.y wrong: {entity.sprite_offset.y}"
|
||||
|
||||
entity.draw_pos = mcrfpy.Vector(7.5, 8.5)
|
||||
assert entity.draw_pos.x == 7.5, f"draw_pos.x wrong: {entity.draw_pos.x}"
|
||||
|
||||
print(" PASS: entity vector setters")
|
||||
|
||||
def test_entity_no_grid_vector():
|
||||
"""Entities without a grid should handle vector properties gracefully."""
|
||||
entity = mcrfpy.Entity()
|
||||
|
||||
draw_pos = entity.draw_pos
|
||||
assert draw_pos is not None, "draw_pos should work without grid"
|
||||
|
||||
cell_pos = entity.cell_pos
|
||||
assert cell_pos is not None, "cell_pos should work without grid"
|
||||
|
||||
offset = entity.sprite_offset
|
||||
assert offset is not None, "sprite_offset should work without grid"
|
||||
|
||||
print(" PASS: entity no-grid vector properties")
|
||||
|
||||
print("Testing issue #268: sfVector2f_to_PyObject NULL safety...")
|
||||
test_entity_vector_properties()
|
||||
test_entity_vector_setters()
|
||||
test_entity_no_grid_vector()
|
||||
print("All #268 tests passed.")
|
||||
sys.exit(0)
|
||||
90
tests/regression/issue_272_uniform_owner_test.py
Normal file
90
tests/regression/issue_272_uniform_owner_test.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Regression test: UniformCollection must check owner validity.
|
||||
|
||||
Issue #272: PyUniformCollectionObject stored a raw pointer to UniformCollection
|
||||
but only checked non-NULL. If the owning UIDrawable was destroyed, the raw
|
||||
pointer dangled, causing use-after-free.
|
||||
|
||||
Fix: Added weak_ptr<void> owner field. All accessors now check owner.lock()
|
||||
before accessing the collection, raising RuntimeError if the owner is gone.
|
||||
"""
|
||||
import mcrfpy
|
||||
import gc
|
||||
import sys
|
||||
|
||||
def test_uniforms_basic_access():
|
||||
"""Uniform collection should work when owner is alive."""
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
u = frame.uniforms
|
||||
assert len(u) == 0, f"Expected empty uniforms, got {len(u)}"
|
||||
|
||||
u['brightness'] = 1.0
|
||||
assert len(u) == 1, f"Expected 1 uniform, got {len(u)}"
|
||||
assert 'brightness' in u, "brightness should be in uniforms"
|
||||
|
||||
del u['brightness']
|
||||
assert len(u) == 0, f"Expected 0 after delete, got {len(u)}"
|
||||
|
||||
print(" PASS: basic uniform access")
|
||||
|
||||
def test_uniforms_owner_destroyed():
|
||||
"""Accessing uniforms after owner destruction must fail gracefully."""
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
u = frame.uniforms
|
||||
u['test'] = 1.0
|
||||
|
||||
del frame
|
||||
gc.collect()
|
||||
|
||||
# len() and __contains__ return 0/False gracefully
|
||||
assert len(u) == 0, f"len should be 0 after owner destroyed, got {len(u)}"
|
||||
assert 'test' not in u, "'test' should not be found after owner destroyed"
|
||||
|
||||
# Subscript access and assignment raise RuntimeError
|
||||
errors = 0
|
||||
try:
|
||||
_ = u['test']
|
||||
except RuntimeError:
|
||||
errors += 1
|
||||
|
||||
try:
|
||||
u['new'] = 2.0
|
||||
except RuntimeError:
|
||||
errors += 1
|
||||
|
||||
assert errors == 2, f"Expected 2 RuntimeErrors from subscript ops, got {errors}"
|
||||
print(" PASS: uniforms after owner destroyed")
|
||||
|
||||
def test_uniforms_owner_in_scene():
|
||||
"""Uniforms should work while owner is in a scene's children."""
|
||||
scene = mcrfpy.Scene("uniform_test")
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
scene.children.append(frame)
|
||||
u = frame.uniforms
|
||||
u['value'] = 0.5
|
||||
assert len(u) == 1, "Uniform should be accessible while in scene"
|
||||
print(" PASS: uniforms with scene-owned frame")
|
||||
|
||||
def test_uniforms_multiple_values():
|
||||
"""Multiple uniform types should work correctly."""
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
u = frame.uniforms
|
||||
|
||||
u['f'] = 1.0
|
||||
u['v2'] = (1.0, 2.0)
|
||||
u['v3'] = (1.0, 2.0, 3.0)
|
||||
u['v4'] = (1.0, 2.0, 3.0, 4.0)
|
||||
|
||||
assert len(u) == 4, f"Expected 4 uniforms, got {len(u)}"
|
||||
|
||||
keys = list(u.keys())
|
||||
assert len(keys) == 4, f"Expected 4 keys, got {len(keys)}"
|
||||
|
||||
print(" PASS: multiple uniform types")
|
||||
|
||||
print("Testing issue #272: UniformCollection owner validity...")
|
||||
test_uniforms_basic_access()
|
||||
test_uniforms_owner_destroyed()
|
||||
test_uniforms_owner_in_scene()
|
||||
test_uniforms_multiple_values()
|
||||
print("All #272 tests passed.")
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue