I am running into a strange issue using pytest and mock: I am trying to create a call to __del__ by deleting an object using del .... According to the documentation, del only reduces the reference counter on the object that is being "deleted" and only actually deletes the object if nobody else is still holding a reference to it. It seems like if Mock throws an exception, that somehow leads to someone grabbing and keeping an extra reference to the object.
I put together a quick demo test to show the issue: The only difference between test_del_passes (which completes just fine) and test_del_fails (which fails with the last assertion, i.e. del del_test does not cause a call to del_test.__del__()) is that in the first one, test_fn returns a value, whereas in the second one, test_fn throws a TimeoutError. I tried deleting the test_fn object, or assigning TimeoutError to a variable instead and deleting that, but I simply can't find a way to get the second test to pass. So somewhere in the testing infra, someone is keeping an extra reference to del_test and I don't know who or why or how to get rid of it.
import unittest.mock as mock
class DelTest:
def __init__(self, flags, test_fn):
self.flags = flags
self.test_fn = test_fn
def __del__(self):
self.flags[0] = 1
def run(self):
try:
self.test_fn()
except TimeoutError:
pass
def test_del_passes():
flags = [0]
test_fn = mock.Mock(side_effect=[True])
del_test = DelTest(flags, test_fn)
del_test.run()
assert flags[0] == 0
del del_test
assert flags[0] == 1
def test_del_fails():
flags = [0]
test_fn = mock.Mock(side_effect=[TimeoutError()])
del_test = DelTest(flags, test_fn)
del_test.run()
assert flags[0] == 0
del del_test
assert flags[0] == 1
The frame keeps a reference to the object, which you can see invoking
gc.get_referrers(del_test):