aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/tests
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2025-07-31 08:55:29 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2025-07-31 15:26:25 +0200
commitd580aae898e10e7b4491f2692f320d7706899c1e (patch)
tree630e156fb6624b1f684bedb563f349c30239c2f7 /sources/pyside6/tests
parent160884d98fc8fee21b1c7725f5706d1fae47ccb4 (diff)
libpyside: Fix corruption connecting several signals to one non-QObject receiver
The deletion of the weak ref in ~TrackingMethodDynamicSlot() did not reliably stop the notification from being triggered, causing corruption later on since the address of the dynamic slot was used as user data. Use the Python receiver object as user data instead (which is only used for map look up) to delete the connection to protect against multiple invocation. Remove the deletion of the weakref since it is removed in the notification. Remove class TrackingMethodDynamicSlot as it does not really have any functionality any more. The comment about releasing the weakref in case DynamicSlot outlives Python is apparently not an issue since otherwise the deletion of the function in ~MethodDynamicSlot() would have caused issues. Amends 33bd61d13d8d9e3794b6049891be62f3351313d9. Pick-to: 6.9 6.8 Fixes: PYSIDE-3148 Task-number: PYSIDE-2810 Change-Id: Idc07d0774afaf99df93185c90e975291a42ffeaf Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
Diffstat (limited to 'sources/pyside6/tests')
-rw-r--r--sources/pyside6/tests/signals/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/signals/nonqobject_receivers_test.py71
2 files changed, 72 insertions, 0 deletions
diff --git a/sources/pyside6/tests/signals/CMakeLists.txt b/sources/pyside6/tests/signals/CMakeLists.txt
index dd8f4a016..094caaaf6 100644
--- a/sources/pyside6/tests/signals/CMakeLists.txt
+++ b/sources/pyside6/tests/signals/CMakeLists.txt
@@ -21,6 +21,7 @@ PYSIDE_TEST(qobject_callable_connect_test.py)
PYSIDE_TEST(qobject_destroyed_test.py)
PYSIDE_TEST(qobject_receivers_test.py)
PYSIDE_TEST(qobject_sender_test.py)
+PYSIDE_TEST(nonqobject_receivers_test.py)
PYSIDE_TEST(ref01_test.py)
PYSIDE_TEST(ref02_test.py)
PYSIDE_TEST(ref03_test.py)
diff --git a/sources/pyside6/tests/signals/nonqobject_receivers_test.py b/sources/pyside6/tests/signals/nonqobject_receivers_test.py
new file mode 100644
index 000000000..4bb47e5df
--- /dev/null
+++ b/sources/pyside6/tests/signals/nonqobject_receivers_test.py
@@ -0,0 +1,71 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test case for non-QObject.receivers'''
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths # noqa: F401
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication # noqa: F401
+
+from PySide6.QtGui import QAction # noqa: F401
+
+
+receiver_instances = 0
+
+
+class Receiver:
+
+ def __init__(self):
+ global receiver_instances
+ receiver_instances += 1
+ self.slot1Triggered = 0
+ self.slot2Triggered = 0
+
+ def __del__(self):
+ global receiver_instances
+ receiver_instances -= 1
+
+ def slot1(self):
+ self.slot1Triggered += 1
+
+ def slot2(self):
+ self.slot2Triggered += 1
+
+
+class TestQObjectReceivers(UsesQApplication):
+ '''Test case for non-QObject.receivers'''
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testBasic(self):
+ '''The test verifies that connections to methods of a non-QObject work
+ (TrackingMethodDynamicSlot). Also, despite being stored in the signal manager,
+ making connections should not increase references on the receiver or prevent
+ the receivers from being deleted, which is achieved using weak reference
+ tracking notifications.
+ 2 connections are used to trigger a corruption caused by multiple weak reference
+ notifications (PYSIDE-3148).'''
+ action1 = QAction("a1", qApp) # noqa: F821
+ action2 = QAction("a2", qApp) # noqa: F821
+ receiver = Receiver()
+ self.assertEqual(receiver_instances, 1)
+ base_ref_count = sys.getrefcount(receiver)
+ action1.triggered.connect(receiver.slot1)
+ action2.triggered.connect(receiver.slot2)
+ self.assertEqual(sys.getrefcount(receiver), base_ref_count)
+ action1.trigger()
+ action2.trigger()
+ self.assertEqual(receiver.slot1Triggered, 1)
+ self.assertEqual(receiver.slot2Triggered, 1)
+ receiver = 0
+ self.assertEqual(receiver_instances, 0)
+
+
+if __name__ == '__main__':
+ unittest.main()