Skip to content

Commit 8df9bb7

Browse files
committed
Add tests for _QThreadWorker.run dangling reference release
1 parent cc23033 commit 8df9bb7

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

tests/test_qthreadexec.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,35 @@
22
# © 2014 Mark Harviston <mark.harviston@gmail.com>
33
# © 2014 Arve Knudsen <arve.knudsen@gmail.com>
44
# BSD License
5+
import threading
6+
import weakref
7+
import logging
58

69
import pytest
10+
711
import qasync
812

913

14+
_TestObject = type("_TestObject", (object,), {})
15+
16+
17+
@pytest.fixture(autouse=True)
18+
def disable_executor_logging():
19+
"""
20+
When running under pytest, leftover LogRecord objects
21+
keep references to objects in the scope that logging was called in.
22+
To avoid issues with tests targeting stale references,
23+
we disable logging for QThreadExecutor and _QThreadWorker classes.
24+
"""
25+
for cls in (qasync.QThreadExecutor, qasync._QThreadWorker):
26+
logger_name = cls.__qualname__
27+
if cls.__module__ is not None:
28+
logger_name = f"{cls.__module__}.{logger_name}"
29+
logger = logging.getLogger(logger_name)
30+
logger.addHandler(logging.NullHandler())
31+
logger.propagate = False
32+
33+
1034
@pytest.fixture
1135
def executor(request):
1236
exe = qasync.QThreadExecutor(5)
@@ -48,3 +72,36 @@ def rec(a, *args, **kwargs):
4872
for f in fs:
4973
with pytest.raises(RecursionError):
5074
f.result()
75+
76+
77+
def test_no_stale_reference_as_argument(executor):
78+
test_obj = _TestObject()
79+
test_obj_collected = threading.Event()
80+
81+
# Reference to weakref has to be kept for callback to work
82+
_ = weakref.ref(test_obj, lambda *_: test_obj_collected.set())
83+
# Submit object as argument to the executor
84+
future = executor.submit(lambda *_: None, test_obj)
85+
del test_obj
86+
# Wait for future to resolve
87+
future.result()
88+
89+
collected = test_obj_collected.wait(timeout=1)
90+
assert (
91+
collected is True
92+
), "Stale reference to executor argument not collected within timeout."
93+
94+
95+
def test_no_stale_reference_as_result(executor):
96+
# Get object as result out of executor
97+
test_obj = executor.submit(lambda: _TestObject()).result()
98+
test_obj_collected = threading.Event()
99+
100+
# Reference to weakref has to be kept for callback to work
101+
_ = weakref.ref(test_obj, lambda *_: test_obj_collected.set())
102+
del test_obj
103+
104+
collected = test_obj_collected.wait(timeout=1)
105+
assert (
106+
collected is True
107+
), "Stale reference to executor result not collected within timeout."

0 commit comments

Comments
 (0)