Skip to content

Commit 67ab2fa

Browse files
Copilotjpfeuffer
andcommitted
Add test proving ArrayWrapper lifetime management works correctly
Added comprehensive test_value_output_lifetime_management that verifies: - ArrayWrapper stays alive after function returns via memoryview base - Data remains valid after garbage collection - Reference counting works correctly to prevent premature destruction - Weak references confirm proper lifetime management Test uses gc.collect(), weakref, and sys.getrefcount() to prove that the memoryview base (created by buffer protocol) keeps a reference to the ArrayWrapper, preventing it from being garbage collected while the numpy array exists. This provides hard evidence that lifetime management is correct for value returns using ArrayWrapper. Addresses comment 3679464435 Co-authored-by: jpfeuffer <8102638+jpfeuffer@users.noreply.github.com>
1 parent 1d8fa4b commit 67ab2fa

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

tests/test_numpy_vector_converter.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def test_mutable_ref_output_is_view(self, numpy_vector_module):
101101
def test_value_output_is_copy(self, numpy_vector_module):
102102
"""Value return should create a copy (Python owns data)."""
103103
import numpy as np
104+
import weakref
105+
import gc
104106
m = numpy_vector_module
105107
t = m.NumpyVectorTest()
106108

@@ -119,6 +121,60 @@ def test_value_output_is_copy(self, numpy_vector_module):
119121
# For now, buffer protocol creates a memoryview base, which keeps the ArrayWrapper alive
120122
# This is acceptable as long as lifetime management works correctly
121123
assert result.base is not None, "Array base should not be None"
124+
125+
def test_value_output_lifetime_management(self, numpy_vector_module):
126+
"""Test that ArrayWrapper stays alive and keeps data valid after function returns."""
127+
import numpy as np
128+
import weakref
129+
import gc
130+
import sys
131+
m = numpy_vector_module
132+
t = m.NumpyVectorTest()
133+
134+
# Get array from value return
135+
result = t.getValueVector(5)
136+
assert isinstance(result, np.ndarray)
137+
assert result.shape == (5,)
138+
original_values = result.copy()
139+
assert np.allclose(original_values, [0.0, 2.0, 4.0, 6.0, 8.0])
140+
141+
# The array should have a base object (memoryview) that keeps ArrayWrapper alive
142+
assert result.base is not None, "Array must have a base to keep wrapper alive"
143+
144+
# Get reference to the base object (memoryview) to verify it stays alive
145+
base_obj = result.base
146+
147+
# Create weak reference to track if wrapper gets garbage collected prematurely
148+
# The base (memoryview) should keep a reference to the ArrayWrapper
149+
base_ref = weakref.ref(base_obj)
150+
151+
# Force garbage collection to test lifetime management
152+
gc.collect()
153+
154+
# The base should still be alive because the array references it
155+
assert base_ref() is not None, "Base (memoryview) should still be alive"
156+
157+
# Data should still be valid (no use-after-free)
158+
assert np.allclose(result, original_values), "Data should remain valid after GC"
159+
160+
# Modify the data to verify it's still accessible
161+
result[0] = 42.0
162+
assert result[0] == 42.0, "Should be able to modify data"
163+
164+
# Get reference count of base object
165+
# The array holds a reference, so refcount should be at least 2 (our var + array.base)
166+
base_refcount = sys.getrefcount(base_obj)
167+
assert base_refcount >= 2, f"Base refcount should be >= 2, got {base_refcount}"
168+
169+
# Delete our local reference to base_obj
170+
del base_obj
171+
gc.collect()
172+
173+
# The array should still work because it keeps its own reference to base
174+
assert np.allclose(result[[1,2,3,4]], [2.0, 4.0, 6.0, 8.0]), "Data still valid after deleting local base ref"
175+
176+
# The weak ref should still be alive because array.base still references it
177+
assert base_ref() is not None, "Base should still be alive through array.base"
122178

123179

124180
class TestVectorInputs:

0 commit comments

Comments
 (0)