aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/tests/signals
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/tests/signals')
-rw-r--r--sources/pyside6/tests/signals/CMakeLists.txt47
-rw-r--r--sources/pyside6/tests/signals/anonymous_slot_leak_test.py55
-rw-r--r--sources/pyside6/tests/signals/args_dont_match_test.py36
-rw-r--r--sources/pyside6/tests/signals/bug_189.py38
-rw-r--r--sources/pyside6/tests/signals/bug_311.py54
-rw-r--r--sources/pyside6/tests/signals/bug_312.py45
-rw-r--r--sources/pyside6/tests/signals/bug_319.py47
-rw-r--r--sources/pyside6/tests/signals/bug_79.py59
-rw-r--r--sources/pyside6/tests/signals/decorators_test.py95
-rw-r--r--sources/pyside6/tests/signals/disconnect_test.py70
-rw-r--r--sources/pyside6/tests/signals/invalid_callback_test.py41
-rw-r--r--sources/pyside6/tests/signals/lambda_gui_test.py50
-rw-r--r--sources/pyside6/tests/signals/lambda_test.py123
-rw-r--r--sources/pyside6/tests/signals/leaking_signal_test.py27
-rw-r--r--sources/pyside6/tests/signals/multiple_connections_gui_test.py60
-rw-r--r--sources/pyside6/tests/signals/multiple_connections_test.py90
-rw-r--r--sources/pyside6/tests/signals/pysignal_test.py203
-rw-r--r--sources/pyside6/tests/signals/qobject_callable_connect_test.py45
-rw-r--r--sources/pyside6/tests/signals/qobject_destroyed_test.py39
-rw-r--r--sources/pyside6/tests/signals/qobject_receivers_test.py65
-rw-r--r--sources/pyside6/tests/signals/qobject_sender_test.py117
-rw-r--r--sources/pyside6/tests/signals/ref01_test.py39
-rw-r--r--sources/pyside6/tests/signals/ref02_test.py60
-rw-r--r--sources/pyside6/tests/signals/ref03_test.py42
-rw-r--r--sources/pyside6/tests/signals/ref04_test.py58
-rw-r--r--sources/pyside6/tests/signals/ref05_test.py58
-rw-r--r--sources/pyside6/tests/signals/ref06_test.py61
-rw-r--r--sources/pyside6/tests/signals/segfault_proxyparent_test.py78
-rw-r--r--sources/pyside6/tests/signals/self_connect_test.py64
-rw-r--r--sources/pyside6/tests/signals/short_circuit_test.py85
-rw-r--r--sources/pyside6/tests/signals/signal2signal_connect_test.py121
-rw-r--r--sources/pyside6/tests/signals/signal_across_threads.py82
-rw-r--r--sources/pyside6/tests/signals/signal_autoconnect_test.py41
-rw-r--r--sources/pyside6/tests/signals/signal_connectiontype_support_test.py42
-rw-r--r--sources/pyside6/tests/signals/signal_emission_gui_test.py118
-rw-r--r--sources/pyside6/tests/signals/signal_emission_test.py142
-rw-r--r--sources/pyside6/tests/signals/signal_enum_test.py57
-rw-r--r--sources/pyside6/tests/signals/signal_func_test.py31
-rw-r--r--sources/pyside6/tests/signals/signal_manager_refcount_test.py36
-rw-r--r--sources/pyside6/tests/signals/signal_newenum_test.py50
-rw-r--r--sources/pyside6/tests/signals/signal_number_limit_test.py74
-rw-r--r--sources/pyside6/tests/signals/signal_object_test.py98
-rw-r--r--sources/pyside6/tests/signals/signal_signature_test.py105
-rw-r--r--sources/pyside6/tests/signals/signal_with_primitive_type_test.py38
-rw-r--r--sources/pyside6/tests/signals/signals.pyproject19
-rw-r--r--sources/pyside6/tests/signals/slot_reference_count_test.py70
-rw-r--r--sources/pyside6/tests/signals/static_metaobject_test.py83
47 files changed, 3158 insertions, 0 deletions
diff --git a/sources/pyside6/tests/signals/CMakeLists.txt b/sources/pyside6/tests/signals/CMakeLists.txt
new file mode 100644
index 000000000..ff342adc7
--- /dev/null
+++ b/sources/pyside6/tests/signals/CMakeLists.txt
@@ -0,0 +1,47 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+PYSIDE_TEST(args_dont_match_test.py)
+PYSIDE_TEST(bug_79.py)
+PYSIDE_TEST(bug_189.py)
+PYSIDE_TEST(bug_311.py)
+PYSIDE_TEST(bug_312.py)
+PYSIDE_TEST(bug_319.py)
+PYSIDE_TEST(decorators_test.py)
+PYSIDE_TEST(disconnect_test.py)
+PYSIDE_TEST(invalid_callback_test.py)
+PYSIDE_TEST(lambda_gui_test.py)
+PYSIDE_TEST(lambda_test.py)
+PYSIDE_TEST(leaking_signal_test.py)
+PYSIDE_TEST(multiple_connections_gui_test.py)
+PYSIDE_TEST(multiple_connections_test.py)
+PYSIDE_TEST(pysignal_test.py)
+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(ref01_test.py)
+PYSIDE_TEST(ref02_test.py)
+PYSIDE_TEST(ref03_test.py)
+PYSIDE_TEST(ref04_test.py)
+PYSIDE_TEST(ref05_test.py)
+PYSIDE_TEST(ref06_test.py)
+PYSIDE_TEST(segfault_proxyparent_test.py)
+PYSIDE_TEST(self_connect_test.py)
+PYSIDE_TEST(short_circuit_test.py)
+PYSIDE_TEST(signal2signal_connect_test.py)
+PYSIDE_TEST(signal_across_threads.py)
+PYSIDE_TEST(signal_autoconnect_test.py)
+PYSIDE_TEST(signal_connectiontype_support_test.py)
+PYSIDE_TEST(signal_emission_gui_test.py)
+PYSIDE_TEST(signal_emission_test.py)
+PYSIDE_TEST(signal_enum_test.py)
+PYSIDE_TEST(signal_func_test.py)
+PYSIDE_TEST(signal_manager_refcount_test.py)
+PYSIDE_TEST(signal_newenum_test.py)
+PYSIDE_TEST(signal_number_limit_test.py)
+PYSIDE_TEST(signal_object_test.py)
+PYSIDE_TEST(signal_signature_test.py)
+PYSIDE_TEST(signal_with_primitive_type_test.py)
+PYSIDE_TEST(slot_reference_count_test.py)
+PYSIDE_TEST(static_metaobject_test.py)
diff --git a/sources/pyside6/tests/signals/anonymous_slot_leak_test.py b/sources/pyside6/tests/signals/anonymous_slot_leak_test.py
new file mode 100644
index 000000000..560a08659
--- /dev/null
+++ b/sources/pyside6/tests/signals/anonymous_slot_leak_test.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import os
+import sys
+import unittest
+
+from functools import partial
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtWidgets import QWidget
+from helper.usesqapplication import UsesQApplication
+
+
+have_debug = hasattr(sys, "gettotalrefcount")
+
+
+class LeakerLambda():
+ def __init__(self, widget):
+ widget.windowIconChanged.connect(lambda *args: None)
+
+
+class LeakerFunctoolsPartial():
+ def __init__(self, widget):
+ widget.windowIconChanged.connect(partial(int, 0))
+
+
+class TestBugPYSIDE2299(UsesQApplication):
+ def leak(self, leaker):
+ widget = QWidget()
+
+ # Warm-up
+ leaker(widget)
+
+ refs_before = sys.gettotalrefcount()
+ for _ in range(1000):
+ leaker(widget)
+ refs_after = sys.gettotalrefcount()
+
+ self.assertAlmostEqual(refs_after - refs_before, 0, delta=10)
+
+ @unittest.skipUnless(have_debug, "You need a debug build")
+ def test_lambda(self):
+ self.leak(LeakerLambda)
+
+ @unittest.skipUnless(have_debug, "You need a debug build")
+ def test_functools_partial(self):
+ self.leak(LeakerFunctoolsPartial)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/args_dont_match_test.py b/sources/pyside6/tests/signals/args_dont_match_test.py
new file mode 100644
index 000000000..4f56be348
--- /dev/null
+++ b/sources/pyside6/tests/signals/args_dont_match_test.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Sender(QObject):
+ the_signal = Signal(int, int, int)
+
+
+class ArgsDontMatch(unittest.TestCase):
+
+ def callback(self, arg1):
+ self.ok = True
+
+ def testConnectSignalToSlotWithLessArgs(self):
+ self.ok = False
+ obj1 = Sender()
+ obj1.the_signal.connect(self.callback)
+ obj1.the_signal.emit(1, 2, 3)
+
+ self.assertTrue(self.ok)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/bug_189.py b/sources/pyside6/tests/signals/bug_189.py
new file mode 100644
index 000000000..1c013ddea
--- /dev/null
+++ b/sources/pyside6/tests/signals/bug_189.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QTimer
+from PySide6.QtWidgets import QSlider
+from helper.usesqapplication import UsesQApplication
+
+
+class TestBugPYSIDE189(UsesQApplication):
+
+ def testDisconnect(self):
+ # Disconnecting from a signal owned by a destroyed object
+ # should raise an exception, not segfault.
+ def onValueChanged(self, value):
+ pass
+
+ sld = QSlider()
+ sld.valueChanged.connect(onValueChanged)
+
+ sld.deleteLater()
+
+ QTimer.singleShot(0, self.app.quit)
+ self.app.exec()
+
+ self.assertRaises(RuntimeError, sld.valueChanged.disconnect, onValueChanged)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/bug_311.py b/sources/pyside6/tests/signals/bug_311.py
new file mode 100644
index 000000000..e27476172
--- /dev/null
+++ b/sources/pyside6/tests/signals/bug_311.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QDate, QObject, Signal
+from helper.usesqapplication import UsesQApplication
+
+
+class DerivedDate(QDate):
+ def __init__(self, y, m, d):
+ super().__init__(y, m, d)
+
+
+class Emitter(QObject):
+ dateSignal1 = Signal(QDate)
+ dateSignal2 = Signal(DerivedDate)
+ tupleSignal = Signal(tuple)
+
+
+class SignaltoSignalTest(UsesQApplication):
+ def myCb(self, dt):
+ self._dt = dt
+
+ def testBug(self):
+ e = Emitter()
+ d = DerivedDate(2010, 8, 24)
+ self._dt = None
+ e.dateSignal1.connect(self.myCb)
+ e.dateSignal1.emit(d)
+ self.assertEqual(self._dt, d)
+
+ self._dt = None
+ e.dateSignal2.connect(self.myCb)
+ e.dateSignal2.emit(d)
+ self.assertEqual(self._dt, d)
+
+ myTuple = (5, 6, 7)
+ self._dt = None
+ e.tupleSignal.connect(self.myCb)
+ e.tupleSignal.emit(myTuple)
+ self.assertEqual(myTuple, self._dt)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/bug_312.py b/sources/pyside6/tests/signals/bug_312.py
new file mode 100644
index 000000000..80d56a020
--- /dev/null
+++ b/sources/pyside6/tests/signals/bug_312.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+MAX_LOOPS = 5
+MAX_OBJECTS = 200
+
+
+class Sender(QObject):
+ fire = Signal()
+
+
+class MultipleSlots(unittest.TestCase):
+ def myCB(self):
+ self._count += 1
+
+ def testDisconnectCleanup(self):
+ for c in range(MAX_LOOPS):
+ self._count = 0
+ self._senders = []
+ for i in range(MAX_OBJECTS):
+ o = Sender()
+ o.fire.connect(lambda: self.myCB())
+ self._senders.append(o)
+ o.fire.emit()
+
+ self.assertEqual(self._count, MAX_OBJECTS)
+
+ # delete all senders will disconnect the signals
+ self._senders = []
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/bug_319.py b/sources/pyside6/tests/signals/bug_319.py
new file mode 100644
index 000000000..657733afb
--- /dev/null
+++ b/sources/pyside6/tests/signals/bug_319.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, Slot
+from helper.usesqapplication import UsesQApplication
+
+
+class Listener(QObject):
+ def __init__(self):
+ super().__init__(None)
+ self._phrase = []
+
+ @Slot(tuple)
+ def listen(self, words):
+ for w in words:
+ self._phrase.append(w)
+
+
+class Communicate(QObject):
+ # create a new signal on the fly and name it 'speak'
+ speak = Signal(tuple)
+
+
+class SignaltoSignalTest(UsesQApplication):
+ def testBug(self):
+ someone = Communicate()
+ someone2 = Listener()
+ # connect signal and slot
+ someone.speak.connect(someone2.listen)
+ # emit 'speak' signal
+ talk = ("one", "two", "three")
+ someone.speak.emit(talk)
+ self.assertEqual(someone2._phrase, list(talk))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/bug_79.py b/sources/pyside6/tests/signals/bug_79.py
new file mode 100644
index 000000000..77ac621d5
--- /dev/null
+++ b/sources/pyside6/tests/signals/bug_79.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import os
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtGui import QStandardItemModel
+from PySide6.QtWidgets import QApplication, QTreeView
+
+
+try:
+ from sys import gettotalrefcount
+ skiptest = False
+except ImportError:
+ skiptest = True
+
+
+class ConnectTest(unittest.TestCase):
+
+ def callback(self, o):
+ print("callback")
+ self._called = o
+
+ def testNoLeaks_ConnectAndDisconnect(self):
+ self._called = None
+ app = QApplication([]) # noqa: F841
+ o = QTreeView()
+ o.setModel(QStandardItemModel())
+ o.selectionModel().destroyed.connect(self.callback)
+ o.selectionModel().destroyed.disconnect(self.callback)
+ gc.collect()
+ # if this is no debug build, then we check at least that
+ # we do not crash any longer.
+ for idx in range(200):
+ # PYSIDE-2230: Warm-up is necessary before measuring, because
+ # the code changes the constant parts after some time.
+ o.selectionModel().destroyed.connect(self.callback)
+ o.selectionModel().destroyed.disconnect(self.callback)
+ if not skiptest:
+ total = gettotalrefcount()
+ for idx in range(1000):
+ o.selectionModel().destroyed.connect(self.callback)
+ o.selectionModel().destroyed.disconnect(self.callback)
+ gc.collect()
+ if not skiptest:
+ delta = gettotalrefcount() - total
+ print("delta total refcount =", delta)
+ self.assertTrue(abs(delta) < 10)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/decorators_test.py b/sources/pyside6/tests/signals/decorators_test.py
new file mode 100644
index 000000000..b29339ee4
--- /dev/null
+++ b/sources/pyside6/tests/signals/decorators_test.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Slot, Signal
+
+
+class Sender(QObject):
+ mySignal = Signal()
+
+
+class MyObject(QObject):
+ def __init__(self, parent=None):
+ QObject.__init__(self, parent)
+ self._slotCalledCount = 0
+
+ @Slot()
+ def mySlot(self):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+ @Slot(int)
+ @Slot('QString')
+ def mySlot2(self, arg0):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+ @Slot(name='mySlot3')
+ def foo(self):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+ @Slot(str, int)
+ def mySlot4(self, a, b):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+ @Slot(result=int)
+ def mySlot5(self):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+ @Slot(result=QObject)
+ def mySlot6(self):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+
+class StaticMetaObjectTest(unittest.TestCase):
+
+ def testSignalPropagation(self):
+ o = MyObject()
+ m = o.metaObject()
+ self.assertTrue(m.indexOfSlot('mySlot()') > 0)
+ self.assertTrue(m.indexOfSlot('mySlot2(int)') > 0)
+ self.assertTrue(m.indexOfSlot('mySlot2(QString)') > 0)
+ self.assertTrue(m.indexOfSlot('mySlot3()') > 0)
+ self.assertTrue(m.indexOfSlot('mySlot4(QString,int)') > 0)
+
+ def testEmission(self):
+ sender = Sender()
+ o = MyObject()
+ sender.mySignal.connect(o.mySlot)
+ sender.mySignal.emit()
+ self.assertTrue(o._slotCalledCount == 1)
+
+ def testResult(self):
+ o = MyObject()
+ mo = o.metaObject()
+ i = mo.indexOfSlot('mySlot5()')
+ m = mo.method(i)
+ self.assertEqual(m.typeName(), "int")
+
+ def testResultObject(self):
+ o = MyObject()
+ mo = o.metaObject()
+ i = mo.indexOfSlot('mySlot6()')
+ m = mo.method(i)
+ self.assertEqual(m.typeName(), "QObject*")
+
+
+class SlotWithoutArgs(unittest.TestCase):
+
+ def testError(self):
+ # It should be an error to call the slot without the
+ # arguments, as just @Slot would end up in a slot
+ # accepting argument functions
+ self.assertRaises(TypeError, Slot, lambda: 3)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/disconnect_test.py b/sources/pyside6/tests/signals/disconnect_test.py
new file mode 100644
index 000000000..ea3782a91
--- /dev/null
+++ b/sources/pyside6/tests/signals/disconnect_test.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(True)
+
+from PySide6.QtCore import QObject, Signal
+from testbinding import TestObject
+
+
+class Foo(QObject):
+ bar = Signal()
+
+
+class TestDisconnect(unittest.TestCase):
+ def theSlot1(self):
+ self.called1 = True
+
+ def theSlot2(self):
+ self.called2 = True
+
+ def testIt(self):
+ self.called1 = False
+ self.called2 = False
+ f = Foo()
+ f.bar.connect(self.theSlot1)
+ f.bar.connect(self.theSlot2)
+ f.bar.emit()
+ self.assertTrue(self.called1)
+ self.assertTrue(self.called2)
+
+ self.called1 = False
+ self.called2 = False
+ f.bar.disconnect()
+ f.bar.emit()
+ self.assertFalse(self.called1)
+ self.assertFalse(self.called2)
+
+ def testDuringCallback(self):
+ """ Test to see if the C++ object for a connection is accessed after the
+ method returns. This causes a segfault if the memory that was used by the
+ C++ object has been reused. """
+
+ self.called = False
+ obj = TestObject(0)
+
+ def callback():
+ obj.signalWithDefaultValue.disconnect(callback)
+
+ # Connect more callbacks to try to overwrite memory
+ for i in range(1000):
+ obj.signalWithDefaultValue.connect(lambda: None)
+
+ self.called = True
+
+ # A non-None return value is needed
+ return True
+ obj.signalWithDefaultValue.connect(callback)
+ obj.signalWithDefaultValue.emit()
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/invalid_callback_test.py b/sources/pyside6/tests/signals/invalid_callback_test.py
new file mode 100644
index 000000000..2788c1d1a
--- /dev/null
+++ b/sources/pyside6/tests/signals/invalid_callback_test.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test cases for passing invalid callbacks to QObject.connect'''
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject
+
+
+class InvalidCallback(unittest.TestCase):
+ '''Test case for passing an invalid callback to QObject.connect'''
+
+ def setUp(self):
+ # Acquire resources
+ self.obj = QObject()
+
+ def tearDown(self):
+ # Release resources
+ try:
+ del self.obj
+ except AttributeError:
+ pass
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testIntegerCb(self):
+ # Test passing an int as callback to QObject.connect
+ self.assertRaises(TypeError, self.obj.destroyed.connect, 42)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/lambda_gui_test.py b/sources/pyside6/tests/signals/lambda_gui_test.py
new file mode 100644
index 000000000..2123e7206
--- /dev/null
+++ b/sources/pyside6/tests/signals/lambda_gui_test.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Connecting lambda to gui signals'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtWidgets import QSpinBox, QPushButton
+
+from helper.usesqapplication import UsesQApplication
+
+
+class Control:
+ def __init__(self):
+ self.arg = False
+
+
+class QtWidgetsSigLambda(UsesQApplication):
+
+ def testButton(self):
+ # Connecting a lambda to a QPushButton.clicked()
+ obj = QPushButton('label')
+ ctr = Control()
+ func = lambda: setattr(ctr, 'arg', True) # noqa: E731
+ obj.clicked.connect(func)
+ obj.click()
+ self.assertTrue(ctr.arg)
+ self.assertTrue(obj.clicked.disconnect(func))
+
+ def testSpinButton(self):
+ # Connecting a lambda to a QPushButton.clicked()
+ obj = QSpinBox()
+ ctr = Control()
+ arg = 444
+ func = lambda x: setattr(ctr, 'arg', 444) # noqa: E731
+ obj.valueChanged.connect(func)
+ obj.setValue(444)
+ self.assertEqual(ctr.arg, arg)
+ self.assertTrue(obj.valueChanged.disconnect(func))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/lambda_test.py b/sources/pyside6/tests/signals/lambda_test.py
new file mode 100644
index 000000000..23fcdf5fa
--- /dev/null
+++ b/sources/pyside6/tests/signals/lambda_test.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Connecting lambda to signals'''
+
+import os
+import sys
+import unittest
+import weakref
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QCoreApplication, QObject, Signal, SIGNAL, QProcess
+
+from helper.usesqapplication import UsesQApplication
+
+
+class Sender(QObject):
+ void_signal = Signal()
+ int_signal = Signal(int)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._delayed_int = 0
+
+ def emit_void(self):
+ self.void_signal.emit()
+
+ def emit_int(self, v):
+ self.int_signal.emit(v)
+
+
+class Receiver(QObject):
+
+ def __init__(self, *args):
+ super().__init__(*args)
+
+
+class BasicCase(unittest.TestCase):
+
+ def testSimplePythonSignalNoArgs(self):
+ # Connecting a lambda to a simple python signal without arguments
+ receiver = Receiver()
+ sender = Sender()
+ sender.void_signal.connect(lambda: setattr(receiver, 'called', True))
+ sender.emit_void()
+ self.assertTrue(receiver.called)
+
+ def testSimplePythonSignal(self):
+ # Connecting a lambda to a simple python signal witharguments
+ receiver = Receiver()
+ sender = Sender()
+ arg = 42
+ sender.int_signal.connect(lambda x: setattr(receiver, 'arg', arg))
+ sender.emit_int(arg)
+ self.assertEqual(receiver.arg, arg)
+
+ def testSimplePythonSignalNoArgsString(self):
+ # Connecting a lambda to a simple python signal without arguments
+ receiver = Receiver()
+ sender = Sender()
+ QObject.connect(sender, SIGNAL('void_signal()'),
+ lambda: setattr(receiver, 'called', True))
+ sender.emit_void()
+ self.assertTrue(receiver.called)
+
+ def testSimplePythonSignalString(self):
+ # Connecting a lambda to a simple python signal witharguments
+ receiver = Receiver()
+ sender = Sender()
+ arg = 42
+ QObject.connect(sender, SIGNAL('int_signal(int)'),
+ lambda x: setattr(receiver, 'arg', arg))
+ sender.emit_int(arg)
+ self.assertEqual(receiver.arg, arg)
+
+
+class QtSigLambda(UsesQApplication):
+
+ qapplication = True
+
+ def testWithArgs(self):
+ '''Connecting a lambda to a signal with and without arguments'''
+ proc = QProcess()
+ dummy = Receiver()
+ proc.started.connect(lambda: setattr(dummy, 'called', True))
+ proc.finished.connect(lambda x: setattr(dummy, 'exit_code', x))
+
+ proc.start(sys.executable, ['-c', '""'])
+ self.assertTrue(proc.waitForStarted())
+ self.assertTrue(proc.waitForFinished())
+
+ self.assertTrue(dummy.called)
+ self.assertEqual(dummy.exit_code, proc.exitCode())
+
+ def testRelease(self):
+ """PYSIDE-2646: Test whether main thread target slot lambda/methods
+ (and their captured objects) are released by the signal manager
+ after a while."""
+
+ def do_connect(sender):
+ receiver = Receiver()
+ sender.void_signal.connect(lambda: setattr(receiver, 'called', True))
+ return receiver
+
+ sender = Sender()
+ receiver = weakref.ref(do_connect(sender))
+ sender.emit_void()
+ self.assertTrue(receiver().called)
+ del sender
+ for i in range(3):
+ if not receiver():
+ break
+ QCoreApplication.processEvents()
+ self.assertFalse(receiver())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/leaking_signal_test.py b/sources/pyside6/tests/signals/leaking_signal_test.py
new file mode 100644
index 000000000..666ae7a13
--- /dev/null
+++ b/sources/pyside6/tests/signals/leaking_signal_test.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class LeakingSignal(unittest.TestCase):
+
+ def testLeakingSignal(self):
+ # Was segfaulting when the signal was garbage collected.
+ class Emitter(QObject):
+ my_signal = Signal(object)
+
+ emitter = Emitter() # noqa: F841
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/multiple_connections_gui_test.py b/sources/pyside6/tests/signals/multiple_connections_gui_test.py
new file mode 100644
index 000000000..295369b7d
--- /dev/null
+++ b/sources/pyside6/tests/signals/multiple_connections_gui_test.py
@@ -0,0 +1,60 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtWidgets import QPushButton, QSpinBox
+
+from helper.basicpyslotcase import BasicPySlotCase
+from helper.usesqapplication import UsesQApplication
+
+
+class QtGuiMultipleSlots(UsesQApplication):
+ '''Multiple connections to QtGui signals'''
+
+ def run_many(self, signal, emitter, receivers, args=None):
+ """Utility method to connect a list of receivers to a signal.
+ sender - QObject that will emit the signal
+ signal - string with the signal signature
+ emitter - the callable that will trigger the signal
+ receivers - list of BasicPySlotCase instances
+ args - tuple with the arguments to be sent.
+ """
+
+ if args is None:
+ args = tuple()
+
+ for rec in receivers:
+ rec.setUp()
+ signal.connect(rec.cb)
+ rec.args = tuple(args)
+
+ emitter(*args)
+
+ for rec in receivers:
+ self.assertTrue(rec.called)
+
+ def testButtonClick(self):
+ """Multiple connections to QPushButton.clicked()"""
+ sender = QPushButton('button')
+ receivers = [BasicPySlotCase() for x in range(30)]
+ self.run_many(sender.clicked, sender.click, receivers)
+
+ def testSpinBoxValueChanged(self):
+ """Multiple connections to QSpinBox.valueChanged(int)"""
+ sender = QSpinBox()
+ # FIXME if number of receivers if higher than 50, segfaults
+ receivers = [BasicPySlotCase() for x in range(10)]
+ self.run_many(sender.valueChanged, sender.setValue,
+ receivers, (1,))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/multiple_connections_test.py b/sources/pyside6/tests/signals/multiple_connections_test.py
new file mode 100644
index 000000000..233851797
--- /dev/null
+++ b/sources/pyside6/tests/signals/multiple_connections_test.py
@@ -0,0 +1,90 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+from functools import partial
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, QProcess
+
+from helper.basicpyslotcase import BasicPySlotCase
+from helper.usesqapplication import UsesQApplication
+
+
+class MultipleSignalConnections(unittest.TestCase):
+ '''Base class for multiple signal connection testing'''
+
+ def run_many(self, signal, emitter, receivers, args=None):
+ """Utility method to connect a list of receivers to a signal.
+ sender - QObject that will emit the signal
+ signal - string with the signal signature
+ emitter - the callable that will trigger the signal
+ receivers - list of BasicPySlotCase instances
+ args - tuple with the arguments to be sent.
+ """
+
+ if args is None:
+ args = tuple()
+ for rec in receivers:
+ rec.setUp()
+ self.assertTrue(signal.connect(rec.cb))
+ rec.args = tuple(args)
+
+ emitter(*args)
+
+ for rec in receivers:
+ self.assertTrue(rec.called)
+
+
+class PythonMultipleSlots(UsesQApplication, MultipleSignalConnections):
+ '''Multiple connections to python signals'''
+
+ def testPythonSignal(self):
+ """Multiple connections to a python signal (short-circuit)"""
+
+ class Sender(QObject):
+
+ foobar = Signal(int)
+
+ sender = Sender()
+ receivers = [BasicPySlotCase() for x in range(10)]
+ self.run_many(sender.foobar, partial(sender.foobar.emit),
+ receivers, (0, ))
+
+
+class QProcessMultipleSlots(UsesQApplication, MultipleSignalConnections):
+ '''Multiple connections to QProcess signals'''
+
+ def testQProcessStarted(self):
+ '''Multiple connections to QProcess.started()'''
+ sender = QProcess()
+ receivers = [BasicPySlotCase() for x in range(10)]
+
+ def start_proc(*args):
+ sender.start(sys.executable, ['-c', '""'])
+ self.assertTrue(sender.waitForStarted())
+ self.assertTrue(sender.waitForFinished())
+
+ self.run_many(sender.started, start_proc, receivers)
+
+ def testQProcessFinished(self):
+ '''Multiple connections to QProcess.finished(int)'''
+ sender = QProcess()
+ receivers = [BasicPySlotCase() for x in range(10)]
+
+ def start_proc(*args):
+ sender.start(sys.executable, ['-c', '""'])
+ self.assertTrue(sender.waitForStarted())
+ self.assertTrue(sender.waitForFinished())
+
+ self.run_many(sender.finished, start_proc, receivers, (0, QProcess.ExitStatus.NormalExit))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/pysignal_test.py b/sources/pyside6/tests/signals/pysignal_test.py
new file mode 100644
index 000000000..d6f44edf8
--- /dev/null
+++ b/sources/pyside6/tests/signals/pysignal_test.py
@@ -0,0 +1,203 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, Qt
+from PySide6.QtWidgets import QSpinBox, QApplication, QWidget # noqa: F401
+
+from helper.usesqapplication import UsesQApplication
+
+
+TEST_LIST = ["item1", "item2", "item3"]
+
+
+class Sender(QObject):
+ """Sender class used in this test."""
+
+ foo = Signal()
+ foo_int = Signal(int)
+ dummy = Signal(str)
+ dummy2 = Signal(str, list)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def callDummy(self):
+ self.dummy.emit("PyObject")
+
+ def callDummy2(self):
+ self.dummy2.emit("PyObject0", TEST_LIST)
+
+
+class PyObjectType(UsesQApplication):
+ def mySlot(self, arg):
+ self.assertEqual(arg, "PyObject")
+ self.called = True
+ self.callCount += 1
+
+ def mySlot2(self, arg0, arg1):
+ self.assertEqual(arg0, "PyObject0")
+ self.assertEqual(arg1, TEST_LIST)
+ self.callCount += 1
+ if self.running:
+ self.app.quit()
+
+ def setUp(self):
+ super().setUp()
+ self.callCount = 0
+ self.running = False
+
+ def testWithOneArg(self):
+ o = Sender()
+ o.dummy.connect(self.mySlot)
+ o.callDummy()
+ self.assertEqual(self.callCount, 1)
+
+ def testWithTwoArg(self):
+ o = Sender()
+ o.dummy2.connect(self.mySlot2)
+ o.callDummy2()
+ self.assertEqual(self.callCount, 1)
+
+ def testAsyncSignal(self):
+ self.called = False
+ self.running = True
+ o = Sender()
+ o.dummy2.connect(self.mySlot2, Qt.QueuedConnection)
+ o.callDummy2()
+ self.app.exec()
+ self.assertEqual(self.callCount, 1)
+
+ def testTwice(self):
+ self.called = False
+ self.running = True
+ o = Sender()
+ o.dummy2.connect(self.mySlot2, Qt.QueuedConnection)
+ o.callDummy2()
+ o.callDummy2()
+ self.app.exec()
+ self.assertEqual(self.callCount, 2)
+
+
+class PythonSigSlot(unittest.TestCase):
+ def setUp(self):
+ self.called = False
+
+ def tearDown(self):
+ try:
+ del self.args
+ except: # noqa: E722
+ pass
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def callback(self, *args):
+ if tuple(self.args) == args:
+ self.called = True
+
+ def testNoArgs(self):
+ """Python signal and slots without arguments"""
+ obj1 = Sender()
+
+ obj1.foo.connect(self.callback)
+ self.args = tuple()
+ obj1.foo.emit(*self.args)
+
+ self.assertTrue(self.called)
+
+ def testWithArgs(self):
+ """Python signal and slots with integer arguments"""
+ obj1 = Sender()
+
+ obj1.foo_int.connect(self.callback)
+ self.args = (42,)
+ obj1.foo_int.emit(*self.args)
+
+ self.assertTrue(self.called)
+
+ def testDisconnect(self):
+ obj1 = Sender()
+
+ obj1.foo_int.connect(self.callback)
+ self.assertTrue(obj1.foo_int.disconnect(self.callback))
+
+ self.args = (42, )
+ obj1.foo_int.emit(*self.args)
+
+ self.assertTrue(not self.called)
+
+
+class SpinBoxPySignal(UsesQApplication):
+ """Tests the connection of python signals to QSpinBox qt slots."""
+
+ def setUp(self):
+ super().setUp()
+ self.obj = Sender()
+ self.spin = QSpinBox()
+ self.spin.setValue(0)
+
+ def tearDown(self):
+ super().tearDown()
+ del self.obj
+ del self.spin
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testValueChanged(self):
+ """Emission of a python signal to QSpinBox setValue(int)"""
+
+ self.obj.foo_int.connect(self.spin.setValue)
+ self.assertEqual(self.spin.value(), 0)
+
+ self.obj.foo_int.emit(4)
+ self.assertEqual(self.spin.value(), 4)
+
+ def testValueChangedMultiple(self):
+ """Multiple emissions of a python signal to QSpinBox setValue(int)"""
+ self.obj.foo_int.connect(self.spin.setValue)
+ self.assertEqual(self.spin.value(), 0)
+
+ self.obj.foo_int.emit(4)
+ self.assertEqual(self.spin.value(), 4)
+
+ self.obj.foo_int.emit(77)
+ self.assertEqual(self.spin.value(), 77)
+
+
+class WidgetPySignal(UsesQApplication):
+ """Tests the connection of python signals to QWidget qt slots."""
+
+ def setUp(self):
+ super(WidgetPySignal, self).setUp()
+ self.obj = Sender()
+ self.widget = QWidget()
+
+ def tearDown(self):
+ super(WidgetPySignal, self).tearDown()
+ del self.obj
+ del self.widget
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testShow(self):
+ """Emission of a python signal to QWidget slot show()"""
+ self.widget.hide()
+
+ self.obj.foo.connect(self.widget.show)
+ self.assertTrue(not self.widget.isVisible())
+
+ self.obj.foo.emit()
+ self.assertTrue(self.widget.isVisible())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/qobject_callable_connect_test.py b/sources/pyside6/tests/signals/qobject_callable_connect_test.py
new file mode 100644
index 000000000..a7a26d6f5
--- /dev/null
+++ b/sources/pyside6/tests/signals/qobject_callable_connect_test.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Emitter(QObject):
+ sig = Signal(int)
+
+
+class CallableObject(QObject):
+ called = False
+ x = 0
+
+ def __call__(self, x: int):
+ self.called = True
+ self.x = x
+
+
+class QObjectCallableConnectTest(unittest.TestCase):
+ '''Test case for QObject.connect() when the callable is also a QObject.'''
+
+ def testCallableConnect(self):
+ emitter = Emitter()
+ obj = CallableObject()
+ x = 1
+
+ emitter.sig.connect(obj)
+ emitter.sig.emit(x)
+
+ self.assertTrue(obj.called)
+ self.assertEqual(obj.x, x)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/qobject_destroyed_test.py b/sources/pyside6/tests/signals/qobject_destroyed_test.py
new file mode 100644
index 000000000..a21762b41
--- /dev/null
+++ b/sources/pyside6/tests/signals/qobject_destroyed_test.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject
+
+
+class QObjectDestroyed(unittest.TestCase):
+ """Very simple test case for the destroyed() signal of QObject"""
+
+ def setUp(self):
+ self.called = False
+
+ def destroyed_cb(self):
+ self.called = True
+
+ def testDestroyed(self):
+ """Emission of QObject.destroyed() to a python slot"""
+ obj = QObject()
+ obj.destroyed.connect(self.destroyed_cb)
+ del obj
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ # PYSIDE-535: Why do I need to do it twice, here?
+ gc.collect()
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/qobject_receivers_test.py b/sources/pyside6/tests/signals/qobject_receivers_test.py
new file mode 100644
index 000000000..9839255ac
--- /dev/null
+++ b/sources/pyside6/tests/signals/qobject_receivers_test.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+''' Test case for QObject.receivers()'''
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, SIGNAL, SLOT
+
+
+def cute_slot():
+ pass
+
+
+class TestQObjectReceivers(unittest.TestCase):
+ '''Test case for QObject::receivers'''
+
+ def testBasic(self):
+ sender = QObject()
+ receiver1 = QObject()
+ receiver2 = QObject()
+ self.assertEqual(sender.receivers(SIGNAL("")), 0)
+ sender.destroyed.connect(receiver1.deleteLater)
+ self.assertEqual(sender.receivers(SIGNAL("destroyed()")), 1)
+ sender.destroyed.connect(receiver2.deleteLater)
+ self.assertEqual(sender.receivers(SIGNAL("destroyed()")), 2)
+ sender.disconnect(sender, SIGNAL("destroyed()"), receiver2, SLOT("deleteLater()"))
+ self.assertEqual(sender.receivers(SIGNAL("destroyed()")), 1)
+ del receiver2
+ del receiver1
+ del sender
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testPySlots(self):
+ sender = QObject()
+ receiver = QObject()
+ sender.destroyed.connect(cute_slot)
+ self.assertEqual(sender.receivers(SIGNAL("destroyed( )")), 1)
+ sender.destroyed.connect(receiver.deleteLater)
+ self.assertEqual(sender.receivers(SIGNAL("destroyed()")), 2)
+ del sender
+ del receiver
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testPySignals(self):
+ sender = QObject()
+ receiver = QObject()
+ sender.connect(sender, SIGNAL("some_dynamic_signal()"), cute_slot)
+ self.assertEqual(sender.receivers(SIGNAL("some_dynamic_signal( )")), 1)
+ sender.connect(sender, SIGNAL("some_dynamic_signal()"), receiver, SLOT("deleteLater()"))
+ self.assertEqual(sender.receivers(SIGNAL("some_dynamic_signal( )")), 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/qobject_sender_test.py b/sources/pyside6/tests/signals/qobject_sender_test.py
new file mode 100644
index 000000000..9c1121eb8
--- /dev/null
+++ b/sources/pyside6/tests/signals/qobject_sender_test.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test cases for QObject.sender()'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QCoreApplication, QObject, QTimer, Signal
+from helper.usesqapplication import UsesQApplication
+
+
+class ExtQTimer(QTimer):
+ def __init__(self):
+ super().__init__()
+
+
+class Sender(QObject):
+ foo = Signal()
+
+
+class Receiver(QObject):
+ def __init__(self):
+ super().__init__()
+ self.the_sender = None
+
+ def callback(self):
+ self.the_sender = self.sender()
+ if QCoreApplication.instance():
+ QCoreApplication.instance().exit()
+
+
+class ObjectSenderTest(unittest.TestCase):
+ '''Test case for QObject.sender() method.'''
+
+ def testSenderPythonSignal(self):
+ sender = Sender()
+ recv = Receiver()
+ sender.foo.connect(recv.callback)
+ sender.foo.emit()
+ self.assertEqual(sender, recv.the_sender)
+
+
+class ObjectSenderCheckOnReceiverTest(unittest.TestCase):
+ '''Test case for QObject.sender() method, this one tests the equality on the Receiver object.'''
+
+ def testSenderPythonSignal(self):
+ sender = Sender()
+ recv = Receiver()
+ sender.foo.connect(recv.callback)
+ sender.foo.emit()
+ self.assertEqual(sender, recv.the_sender)
+
+
+class ObjectSenderWithQAppTest(UsesQApplication):
+ '''Test case for QObject.sender() method with QApplication.'''
+
+ def testSenderCppSignal(self):
+ sender = QTimer()
+ sender.setObjectName('foo')
+ recv = Receiver()
+ sender.timeout.connect(recv.callback)
+ sender.start(10)
+ self.app.exec()
+ self.assertEqual(sender, recv.the_sender)
+
+ def testSenderCppSignalSingleShotTimer(self):
+ recv = Receiver()
+ QTimer.singleShot(10, recv.callback)
+ self.app.exec()
+ self.assertTrue(isinstance(recv.the_sender, QObject))
+
+ def testSenderCppSignalSingleShotTimerWithContext(self):
+ recv = Receiver()
+ QTimer.singleShot(10, recv, recv.callback)
+ self.app.exec()
+ self.assertTrue(isinstance(recv.the_sender, QObject))
+
+ def testSenderCppSignalWithPythonExtendedClass(self):
+ sender = ExtQTimer()
+ recv = Receiver()
+ sender.timeout.connect(recv.callback)
+ sender.start(10)
+ self.app.exec()
+ self.assertEqual(sender, recv.the_sender)
+
+
+class ObjectSenderWithQAppCheckOnReceiverTest(UsesQApplication):
+ '''Test case for QObject.sender() method with QApplication.'''
+
+ def testSenderCppSignal(self):
+ sender = QTimer()
+ sender.setObjectName('foo')
+ recv = Receiver()
+ sender.timeout.connect(recv.callback)
+ sender.start(10)
+ self.app.exec()
+ self.assertEqual(sender, recv.the_sender)
+
+ def testSenderCppSignalWithPythonExtendedClass(self):
+ sender = ExtQTimer()
+ recv = Receiver()
+ sender.timeout.connect(recv.callback)
+ sender.start(10)
+ self.app.exec()
+ self.assertEqual(sender, recv.the_sender)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref01_test.py b/sources/pyside6/tests/signals/ref01_test.py
new file mode 100644
index 000000000..1a62b2218
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref01_test.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class BoundAndUnboundSignalsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.methods = set(('connect', 'disconnect', 'emit'))
+
+ def tearDown(self):
+ del self.methods
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testUnboundSignal(self):
+ self.assertEqual(type(QObject.destroyed), Signal)
+ self.assertFalse(self.methods.issubset(dir(QObject.destroyed)))
+
+ def testBoundSignal(self):
+ obj = QObject()
+ self.assertNotEqual(type(obj.destroyed), Signal)
+ self.assertTrue(self.methods.issubset(dir(obj.destroyed)))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref02_test.py b/sources/pyside6/tests/signals/ref02_test.py
new file mode 100644
index 000000000..54b6f4a52
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref02_test.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QTimeLine
+from helper.usesqapplication import UsesQApplication
+
+
+class NativeSignalsTest(UsesQApplication):
+
+ def setUp(self):
+ UsesQApplication.setUp(self)
+ self.called = False
+ self.timeline = QTimeLine(100)
+
+ def tearDown(self):
+ del self.called
+ del self.timeline
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ UsesQApplication.tearDown(self)
+
+ def testSignalWithIntArgument(self):
+
+ def valueChangedSlot(value):
+ self.called = True
+ self.assertEqual(type(value), float)
+ self.app.quit()
+
+ self.timeline.valueChanged.connect(valueChangedSlot)
+ self.timeline.start()
+
+ self.app.exec()
+ self.assertTrue(self.called)
+
+ def testSignalWithoutArguments(self):
+
+ def finishedSlot():
+ self.called = True
+ self.app.quit()
+
+ self.timeline.finished.connect(finishedSlot)
+ self.timeline.start()
+
+ self.app.exec()
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref03_test.py b/sources/pyside6/tests/signals/ref03_test.py
new file mode 100644
index 000000000..c43c2e549
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref03_test.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject
+
+
+class DisconnectSignalsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.emitter = QObject()
+
+ def tearDown(self):
+ del self.emitter
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testConnectionRefCount(self):
+
+ def destroyedSlot():
+ pass
+
+ self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+ self.emitter.destroyed.connect(destroyedSlot)
+ self.assertEqual(sys.getrefcount(destroyedSlot), 3)
+ self.emitter.destroyed.disconnect(destroyedSlot)
+ self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref04_test.py b/sources/pyside6/tests/signals/ref04_test.py
new file mode 100644
index 000000000..fce801456
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref04_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class ExtQObject(QObject):
+
+ mySignal = Signal()
+
+ def __init__(self):
+ super().__init__()
+
+
+class UserSignalTest(unittest.TestCase):
+
+ def setUp(self):
+ self.emitter = ExtQObject()
+ self.counter = 0
+
+ def tearDown(self):
+ del self.emitter
+ del self.counter
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def testConnectEmitDisconnect(self):
+
+ def slot():
+ self.counter += 1
+
+ self.emitter.mySignal.connect(slot)
+
+ self.assertEqual(self.counter, 0)
+ self.emitter.mySignal.emit()
+ self.assertEqual(self.counter, 1)
+ self.emitter.mySignal.emit()
+ self.assertEqual(self.counter, 2)
+
+ self.emitter.mySignal.disconnect(slot)
+
+ self.emitter.mySignal.emit()
+ self.assertEqual(self.counter, 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref05_test.py b/sources/pyside6/tests/signals/ref05_test.py
new file mode 100644
index 000000000..fb9debf39
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref05_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, QTimeLine, Slot
+from helper.usesqapplication import UsesQApplication
+
+
+class ExtQObject(QObject):
+
+ def __init__(self):
+ super().__init__()
+ self.counter = 0
+
+ @Slot('qreal')
+ def foo(self, value):
+ self.counter += 1
+
+
+class UserSlotTest(UsesQApplication):
+
+ def setUp(self):
+ UsesQApplication.setUp(self)
+ self.receiver = ExtQObject()
+ self.timeline = QTimeLine(100)
+
+ def tearDown(self):
+ del self.timeline
+ del self.receiver
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ UsesQApplication.tearDown(self)
+
+ def testUserSlot(self):
+ self.timeline.setUpdateInterval(10)
+
+ self.timeline.finished.connect(self.app.quit)
+
+ self.timeline.valueChanged.connect(self.receiver.foo)
+ self.timeline.start()
+
+ self.app.exec()
+
+ self.assertTrue(self.receiver.counter > 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/ref06_test.py b/sources/pyside6/tests/signals/ref06_test.py
new file mode 100644
index 000000000..a827131db
--- /dev/null
+++ b/sources/pyside6/tests/signals/ref06_test.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, QTimeLine, Signal, Slot
+from helper.usesqapplication import UsesQApplication
+
+
+class ExtQObject(QObject):
+ signalbetween = Signal('qreal')
+
+ def __init__(self):
+ super().__init__()
+ self.counter = 0
+
+ @Slot('qreal')
+ def foo(self, value):
+ self.counter += 1
+
+
+class SignaltoSignalTest(UsesQApplication):
+
+ def setUp(self):
+ UsesQApplication.setUp(self)
+ self.receiver = ExtQObject()
+ self.timeline = QTimeLine(100)
+
+ def tearDown(self):
+ del self.timeline
+ del self.receiver
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ UsesQApplication.tearDown(self)
+
+ def testSignaltoSignal(self):
+ self.timeline.setUpdateInterval(10)
+
+ self.timeline.finished.connect(self.app.quit)
+
+ self.timeline.valueChanged.connect(self.receiver.signalbetween)
+ self.receiver.signalbetween.connect(self.receiver.foo)
+
+ self.timeline.start()
+
+ self.app.exec()
+
+ self.assertTrue(self.receiver.counter > 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/segfault_proxyparent_test.py b/sources/pyside6/tests/signals/segfault_proxyparent_test.py
new file mode 100644
index 000000000..cb0df0978
--- /dev/null
+++ b/sources/pyside6/tests/signals/segfault_proxyparent_test.py
@@ -0,0 +1,78 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+# Description of the problem
+# After creating an PyObject that inherits from QObject, connecting it,
+# deleting it and later creating another Python QObject-based object, this
+# new object will point to the same memory position as the first one.
+
+# Somehow the underlying QObject also points to the same position.
+
+
+class Sender(QObject):
+
+ bar = Signal(int)
+
+ def __init__(self, parent=None):
+ QObject.__init__(self, parent)
+
+
+class Joe(QObject):
+
+ bar = Signal(int)
+
+ def __init__(self, parent=None):
+ QObject.__init__(self, parent)
+
+
+class SegfaultCase(unittest.TestCase):
+ """Test case for the segfault happening when parent() is called inside
+ ProxyObject"""
+
+ def setUp(self):
+ self.called = False
+
+ def tearDown(self):
+ try:
+ del self.args
+ except: # noqa: E722
+ pass
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def callback(self, *args):
+ if tuple(self.args) == args:
+ self.called = True
+
+ def testSegfault(self):
+ """Regression: Segfault for qobjects in the same memory position."""
+ obj = Sender()
+ obj.bar.connect(self.callback)
+ self.args = (33,)
+ obj.bar.emit(self.args[0])
+ self.assertTrue(self.called)
+ del obj
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ obj = Joe()
+ obj.bar.connect(self.callback)
+ self.args = (33,)
+ obj.bar.emit(self.args[0])
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/self_connect_test.py b/sources/pyside6/tests/signals/self_connect_test.py
new file mode 100644
index 000000000..08ca725f8
--- /dev/null
+++ b/sources/pyside6/tests/signals/self_connect_test.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Using self.connect(signal, method)'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Slot
+from PySide6.QtWidgets import QPushButton, QWidget
+
+from helper.usesqapplication import UsesQApplication
+
+
+class Receiver(QObject):
+ def __init__(self, p=None):
+ super().__init__(p)
+ self.triggered = False
+
+ @Slot(bool, int)
+ def default_parameter_slot(self, bool_value, int_value=0):
+ self.triggered = True
+
+
+class SelfConnect(UsesQApplication):
+
+ def testButtonClickClose(self):
+ button = QPushButton()
+ button.clicked.connect(button.close)
+
+ button.show()
+ self.assertTrue(button.isVisible())
+ button.click()
+ self.assertTrue(not button.isVisible())
+
+ def testWindowButtonClickClose(self):
+ button = QPushButton()
+ window = QWidget()
+ button.clicked.connect(window.close)
+
+ window.show()
+ self.assertTrue(window.isVisible())
+ button.click()
+ self.assertTrue(not window.isVisible())
+
+ def testDefaultParameters(self):
+ button = QPushButton()
+ receiver = Receiver(button)
+ button.clicked.connect(receiver.default_parameter_slot)
+ button.clicked.connect(button.close)
+ button.show()
+ button.click()
+ self.assertTrue(receiver.triggered)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/short_circuit_test.py b/sources/pyside6/tests/signals/short_circuit_test.py
new file mode 100644
index 000000000..1ad4bc24c
--- /dev/null
+++ b/sources/pyside6/tests/signals/short_circuit_test.py
@@ -0,0 +1,85 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Sender(QObject):
+ """Sender class used in this test."""
+
+ foo = Signal()
+ foo_int = Signal(int)
+ foo_int_int_string = Signal(int, int, str)
+ foo_int_qobject = Signal(int, QObject)
+
+ def __init__(self, parent=None):
+ QObject.__init__(self, parent)
+
+
+class ShortCircuitSignals(unittest.TestCase):
+ def setUp(self):
+ self.called = False
+
+ def tearDown(self):
+ try:
+ del self.args
+ except: # noqa: E722
+ pass
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def callback(self, *args):
+ if tuple(self.args) == args:
+ self.called = True
+
+ def testNoArgs(self):
+ """Short circuit signal without arguments"""
+ obj1 = Sender()
+ obj1.foo.connect(self.callback)
+ self.args = tuple()
+ obj1.foo.emit(*self.args)
+ self.assertTrue(self.called)
+
+ def testWithArgs(self):
+ """Short circuit signal with integer arguments"""
+ obj1 = Sender()
+
+ obj1.foo_int.connect(self.callback)
+ self.args = (42,)
+ obj1.foo_int.emit(*self.args)
+
+ self.assertTrue(self.called)
+
+ def testMultipleArgs(self):
+ """Short circuit signal with multiple arguments"""
+ obj1 = Sender()
+
+ obj1.foo_int_int_string.connect(self.callback)
+ self.args = (42, 33, 'char')
+ obj1.foo_int_int_string.emit(*self.args)
+
+ self.assertTrue(self.called)
+
+ def testComplexArgs(self):
+ """Short circuit signal with complex arguments"""
+ obj1 = Sender()
+
+ obj1.foo_int_qobject.connect(self.callback)
+ self.args = (42, obj1)
+
+ obj1.foo_int_qobject.emit(*self.args)
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal2signal_connect_test.py b/sources/pyside6/tests/signals/signal2signal_connect_test.py
new file mode 100644
index 000000000..31129f7a1
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal2signal_connect_test.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+''' Test case for signal to signal connections.'''
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Sender(QObject):
+
+ mysignal_int = Signal(int)
+ mysignal_int_int = Signal(int, int)
+ mysignal_string = Signal(str)
+
+
+class Forwarder(Sender):
+
+ forward = Signal()
+ forward_qobject = Signal(QObject)
+
+
+def cute_slot():
+ pass
+
+
+class TestSignal2SignalConnect(unittest.TestCase):
+ '''Test case for signal to signal connections'''
+
+ def setUp(self):
+ # Set up the basic resources needed
+ self.sender = Sender()
+ self.forwarder = Forwarder()
+ self.args = None
+ self.called = False
+
+ def tearDown(self):
+ # Delete used resources
+ try:
+ del self.sender
+ except: # noqa: E722
+ pass
+ try:
+ del self.forwarder
+ except: # noqa: E722
+ pass
+ del self.args
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+
+ def callback_noargs(self):
+ # Default callback without arguments
+ self.called = True
+
+ def callback_args(self, *args):
+ # Default callback with arguments
+ if args == self.args:
+ self.called = True
+ else:
+ raise TypeError("Invalid arguments")
+
+ def callback_qobject(self, *args):
+ # Default callback for QObject as argument
+ if args[0].objectName() == self.args[0]:
+ self.called = True
+ else:
+ raise TypeError("Invalid arguments")
+
+ def testSignalWithoutArguments(self):
+ self.sender.destroyed.connect(self.forwarder.forward)
+ self.forwarder.forward.connect(self.callback_noargs)
+ del self.sender
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ self.assertTrue(self.called)
+
+ def testSignalWithOnePrimitiveTypeArgument(self):
+ self.sender.mysignal_int.connect(self.forwarder.mysignal_int)
+ self.forwarder.mysignal_int.connect(self.callback_args)
+ self.args = (19,)
+ self.sender.mysignal_int.emit(*self.args)
+ self.assertTrue(self.called)
+
+ def testSignalWithMultiplePrimitiveTypeArguments(self):
+ self.sender.mysignal_int_int.connect(self.forwarder.mysignal_int_int)
+ self.forwarder.mysignal_int_int.connect(self.callback_args)
+ self.args = (23, 29)
+ self.sender.mysignal_int_int.emit(*self.args)
+ self.assertTrue(self.called)
+
+ def testSignalWithOneStringArgument(self):
+ self.sender.mysignal_string.connect(self.forwarder.mysignal_string)
+ self.forwarder.mysignal_string.connect(self.callback_args)
+ self.args = ('myargument',)
+ self.sender.mysignal_string.emit(*self.args)
+ self.assertTrue(self.called)
+
+ def testSignalWithOneQObjectArgument(self):
+ self.sender.destroyed.connect(self.forwarder.forward_qobject)
+ self.forwarder.forward_qobject.connect(self.callback_qobject)
+
+ obj_name = 'sender'
+ self.sender.setObjectName(obj_name)
+ self.args = (obj_name, )
+ del self.sender
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_across_threads.py b/sources/pyside6/tests/signals/signal_across_threads.py
new file mode 100644
index 000000000..91b1ca986
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_across_threads.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test case for PYSIDE-1354: Ensure that slots are invoked from the receiver's
+thread context when using derived classes (and thus, a global receiver).'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, QThread, QTimer, Slot
+from helper.usesqapplication import UsesQApplication
+
+
+class ReceiverBase(QObject):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.senderThread = None
+
+ @Slot()
+ def slot_function(self):
+ self.senderThread = QThread.currentThread()
+
+
+class Receiver(ReceiverBase):
+ pass
+
+
+class TestThread(QThread):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def run(self):
+ pass
+
+
+class SignalAcrossThreads(UsesQApplication):
+ def setUp(self):
+ UsesQApplication.setUp(self)
+ self._timer_tick = 0
+ self._timer = QTimer()
+ self._timer.setInterval(20)
+ self._timer.timeout.connect(self._control_test)
+ self._worker_thread = TestThread()
+
+ def tearDown(self):
+ UsesQApplication.tearDown(self)
+
+ @Slot()
+ def _control_test(self):
+ if self._timer_tick == 0:
+ self._worker_thread.start()
+ elif self._timer_tick == 1:
+ self._worker_thread.wait()
+ else:
+ self._timer.stop()
+ self.app.quit()
+ self._timer_tick += 1
+
+ def test(self):
+ worker_thread_receiver = Receiver()
+ worker_thread_receiver.moveToThread(self._worker_thread)
+ self._worker_thread.started.connect(worker_thread_receiver.slot_function)
+
+ main_thread = QThread.currentThread()
+ main_thread_receiver = Receiver()
+ self._worker_thread.started.connect(main_thread_receiver.slot_function)
+
+ self._timer.start()
+ self.app.exec()
+
+ self.assertEqual(worker_thread_receiver.senderThread, self._worker_thread)
+ self.assertEqual(main_thread_receiver.senderThread, main_thread)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_autoconnect_test.py b/sources/pyside6/tests/signals/signal_autoconnect_test.py
new file mode 100644
index 000000000..51d1cea3a
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_autoconnect_test.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QMetaObject, Slot
+from PySide6.QtWidgets import QApplication, QPushButton, QWidget
+
+
+class MyObject(QWidget):
+ def __init__(self, parent=None):
+ QWidget.__init__(self, parent)
+ self._method_called = False
+
+ @Slot()
+ def on_button_clicked(self):
+ self._method_called = True
+
+
+class AutoConnectionTest(unittest.TestCase):
+
+ def testConnection(self):
+ app = QApplication([]) # noqa: F841
+
+ win = MyObject()
+ btn = QPushButton("click", win)
+ btn.setObjectName("button")
+ QMetaObject.connectSlotsByName(win)
+ btn.click()
+ self.assertTrue(win._method_called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_connectiontype_support_test.py b/sources/pyside6/tests/signals/signal_connectiontype_support_test.py
new file mode 100644
index 000000000..0a69c1e02
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_connectiontype_support_test.py
@@ -0,0 +1,42 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, Qt
+
+
+class Sender(QObject):
+ """Dummy class used in this test."""
+
+ foo = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+
+class TestConnectionTypeSupport(unittest.TestCase):
+ def callback(self, *args):
+ if tuple(self.args) == args:
+ self.called = True
+
+ def testNoArgs(self):
+ """Connect signal using a Qt.ConnectionType as argument"""
+ obj1 = Sender()
+
+ obj1.foo.connect(self.callback, Qt.DirectConnection)
+ self.args = tuple()
+ obj1.foo.emit(*self.args)
+
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_emission_gui_test.py b/sources/pyside6/tests/signals/signal_emission_gui_test.py
new file mode 100644
index 000000000..5a49b9d12
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_emission_gui_test.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+"""Tests covering signal emission and receiving to python slots"""
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtWidgets import QSpinBox, QPushButton
+
+from helper.basicpyslotcase import BasicPySlotCase
+from helper.usesqapplication import UsesQApplication
+
+
+class ButtonPySlot(UsesQApplication, BasicPySlotCase):
+ """Tests the connection of python slots to QPushButton signals"""
+
+ def testButtonClicked(self):
+ """Connection of a python slot to QPushButton.clicked()"""
+ button = QPushButton('Mylabel')
+ button.clicked.connect(self.cb)
+ self.args = tuple()
+ button.clicked.emit()
+ self.assertTrue(self.called)
+
+ def testButtonClick(self):
+ """Indirect qt signal emission using the QPushButton.click() method """
+ button = QPushButton('label')
+ button.clicked.connect(self.cb)
+ self.args = tuple()
+ button.click()
+ self.assertTrue(self.called)
+
+
+class SpinBoxPySlot(UsesQApplication, BasicPySlotCase):
+ """Tests the connection of python slots to QSpinBox signals"""
+
+ def setUp(self):
+ super(SpinBoxPySlot, self).setUp()
+ self.spin = QSpinBox()
+
+ def tearDown(self):
+ del self.spin
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ super(SpinBoxPySlot, self).tearDown()
+
+ def testSpinBoxValueChanged(self):
+ """Connection of a python slot to QSpinBox.valueChanged(int)"""
+ self.spin.valueChanged.connect(self.cb)
+ self.args = [3]
+ self.spin.valueChanged.emit(*self.args)
+ self.assertTrue(self.called)
+
+ def testSpinBoxValueChangedImplicit(self):
+ """Indirect qt signal emission using QSpinBox.setValue(int)"""
+ self.spin.valueChanged.connect(self.cb)
+ self.args = [42]
+ self.spin.setValue(self.args[0])
+ self.assertTrue(self.called)
+
+ def atestSpinBoxValueChangedFewArgs(self):
+ """Emission of signals with fewer arguments than needed"""
+ self.spin.valueChanged.connect(self.cb)
+ self.args = (554,)
+ self.assertRaises(TypeError, self.spin.valueChanged.emit)
+
+
+class QSpinBoxQtSlots(UsesQApplication):
+ """Tests the connection to QSpinBox qt slots"""
+
+ qapplication = True
+
+ def testSetValueIndirect(self):
+ """Indirect signal emission: QSpinBox using valueChanged(int)/setValue(int)"""
+ spinSend = QSpinBox()
+ spinRec = QSpinBox()
+
+ spinRec.setValue(5)
+
+ spinSend.valueChanged.connect(spinRec.setValue)
+ self.assertEqual(spinRec.value(), 5)
+ spinSend.setValue(3)
+ self.assertEqual(spinRec.value(), 3)
+ self.assertEqual(spinSend.value(), 3)
+
+ def testSetValue(self):
+ """Direct signal emission: QSpinBox using valueChanged(int)/setValue(int)"""
+ spinSend = QSpinBox()
+ spinRec = QSpinBox()
+
+ spinRec.setValue(5)
+ spinSend.setValue(42)
+
+ spinSend.valueChanged.connect(spinRec.setValue)
+ self.assertEqual(spinRec.value(), 5)
+ self.assertEqual(spinSend.value(), 42)
+ spinSend.valueChanged.emit(3)
+
+ self.assertEqual(spinRec.value(), 3)
+ # Direct emission shouldn't change the value of the emitter
+ self.assertEqual(spinSend.value(), 42)
+
+ spinSend.valueChanged.emit(66)
+ self.assertEqual(spinRec.value(), 66)
+ self.assertEqual(spinSend.value(), 42)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_emission_test.py b/sources/pyside6/tests/signals/signal_emission_test.py
new file mode 100644
index 000000000..b31d89c2f
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_emission_test.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+"""Tests covering signal emission and receiving to python slots"""
+
+import functools
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, SIGNAL, QProcess, QTimeLine
+
+from helper.usesqapplication import UsesQApplication
+
+
+class ArgsOnEmptySignal(UsesQApplication):
+ '''Trying to emit a signal without arguments passing some arguments'''
+
+ def testArgsToNoArgsSignal(self):
+ '''Passing arguments to a signal without arguments'''
+ process = QProcess()
+ self.assertRaises(TypeError, process.started.emit, 42)
+
+
+class MoreArgsOnEmit(UsesQApplication):
+ '''Trying to pass more args than needed to emit (signals with args)'''
+
+ def testMoreArgs(self):
+ '''Passing more arguments than needed'''
+ process = QProcess()
+ self.assertRaises(TypeError, process.finished.emit, 55, QProcess.ExitStatus.NormalExit, 42)
+
+
+class Sender(QObject):
+ '''Sender class'''
+
+ dummy = Signal()
+ dummy_int = Signal(int)
+
+
+class PythonSignalToCppSlots(UsesQApplication):
+ '''Connect python signals to C++ slots'''
+
+ def testWithoutArgs(self):
+ '''Connect python signal to QTimeLine.toggleDirection()'''
+ timeline = QTimeLine()
+ sender = Sender()
+ sender.dummy.connect(timeline.toggleDirection)
+
+ orig_dir = timeline.direction()
+ sender.dummy.emit()
+ new_dir = timeline.direction()
+
+ if orig_dir == QTimeLine.Forward:
+ self.assertEqual(new_dir, QTimeLine.Backward)
+ else:
+ self.assertEqual(new_dir, QTimeLine.Forward)
+
+ def testWithArgs(self):
+ '''Connect python signals to QTimeLine.setCurrentTime(int)'''
+ timeline = QTimeLine()
+ sender = Sender()
+
+ sender.dummy_int.connect(timeline.setCurrentTime)
+
+ current = timeline.currentTime()
+ sender.dummy_int.emit(current + 42)
+ self.assertEqual(timeline.currentTime(), current + 42)
+
+
+class CppSignalsToCppSlots(UsesQApplication):
+ '''Connection between C++ slots and signals'''
+
+ def testWithoutArgs(self):
+ '''Connect QProcess.started() to QTimeLine.togglePaused()'''
+ process = QProcess()
+ timeline = QTimeLine()
+
+ process.finished.connect(timeline.toggleDirection)
+
+ orig_dir = timeline.direction()
+
+ process.start(sys.executable, ['-c', '"print 42"'])
+ self.assertTrue(process.waitForStarted())
+ self.assertTrue(process.waitForFinished())
+
+ new_dir = timeline.direction()
+
+ if orig_dir == QTimeLine.Forward:
+ self.assertEqual(new_dir, QTimeLine.Backward)
+ else:
+ self.assertEqual(new_dir, QTimeLine.Forward)
+
+
+called = False
+
+
+def someSlot(args=None):
+ global called
+ called = True
+
+
+class DynamicSignalsToFuncPartial(UsesQApplication):
+
+ def testIt(self):
+ global called
+ called = False
+ o = Sender()
+ o.dummy.connect(functools.partial(someSlot, "partial .."))
+ o.dummy.emit()
+ self.assertTrue(called)
+
+
+class EmitUnknownType(UsesQApplication):
+ def testIt(self):
+ a = QObject()
+ a.connect(SIGNAL('foobar(Dummy)'), lambda x: 42) # Just connect with an unknown type
+ self.assertRaises(TypeError, a.emit, SIGNAL('foobar(Dummy)'), 22)
+
+
+class EmitEnum(UsesQApplication):
+ """Test emission of enum arguments"""
+
+ def slot(self, arg):
+ self.arg = arg
+
+ def testIt(self):
+ self.arg = None
+ p = QProcess()
+ p.stateChanged.connect(self.slot)
+ p.stateChanged.emit(QProcess.NotRunning)
+ self.assertEqual(self.arg, QProcess.NotRunning)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_enum_test.py b/sources/pyside6/tests/signals/signal_enum_test.py
new file mode 100644
index 000000000..a792e9b0c
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_enum_test.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+from enum import Enum
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, Slot
+
+
+class Colors(Enum):
+ red = 1
+ green = 2
+ blue = 3
+
+
+class Obj(QObject):
+ enum_signal = Signal(Colors)
+ object_signal = Signal(object)
+
+ def __init__(self, parent=None):
+ QObject.__init__(self, parent)
+ self.enum_signal.connect(self.get_result)
+ self.object_signal.connect(self.get_result)
+ self.value = -1
+
+ @Slot()
+ def get_result(self, i):
+ self.value = i
+
+
+class SignalEnumTests(unittest.TestCase):
+ '''Test Signal with enum.Enum'''
+
+ def testSignal(self):
+ o = Obj()
+ # Default value
+ self.assertEqual(o.value, -1)
+
+ # Enum Signal
+ o.enum_signal.emit(Colors.green)
+ self.assertEqual(o.value, Colors.green)
+
+ # object Signal
+ o.object_signal.emit(Colors.red)
+ self.assertEqual(o.value, Colors.red)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_func_test.py b/sources/pyside6/tests/signals/signal_func_test.py
new file mode 100644
index 000000000..d441d4de9
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_func_test.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import SIGNAL, SLOT
+
+
+class SIGNALSLOTTests(unittest.TestCase):
+ '''Test the output of SIGNAL and SLOT.'''
+
+ def testSIGNAL(self):
+ # SIGNAL function
+ a = "foobar"
+ self.assertEqual(str(SIGNAL(a)), "2foobar")
+
+ def testSLOT(self):
+ # SLOT function
+ a = "foobar"
+ self.assertEqual(str(SLOT(a)), "1foobar")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_manager_refcount_test.py b/sources/pyside6/tests/signals/signal_manager_refcount_test.py
new file mode 100644
index 000000000..955d5b65b
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_manager_refcount_test.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject
+
+
+class SignalManagerRefCount(unittest.TestCase):
+ """Simple test case to check if the signal_manager is erroneously incrementing the
+ object refcounter."""
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testObjectRefcount(self):
+ """Emission of QObject.destroyed() to a python slot"""
+ def callback():
+ pass
+ obj = QObject()
+ refcount = sys.getrefcount(obj)
+ obj.destroyed.connect(callback)
+ self.assertEqual(refcount, sys.getrefcount(obj))
+ obj.destroyed.disconnect(callback)
+ self.assertEqual(refcount, sys.getrefcount(obj))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_newenum_test.py b/sources/pyside6/tests/signals/signal_newenum_test.py
new file mode 100644
index 000000000..5fbb875af
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_newenum_test.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Qt, Slot, Signal
+
+
+class Receiver(QObject):
+ def __init__(self):
+ super().__init__()
+ self.result = 0
+
+ @Slot(Qt.Alignment, str)
+ def handler(self, e, s):
+ print('handler', e, "type=", type(e).__name__, s)
+ self.result += 1
+
+
+class Sender(QObject):
+ test_sig = Signal(Qt.AlignmentFlag, str)
+
+ def __init__(self):
+ super().__init__()
+
+ def emit_test_sig(self):
+ self.test_sig.emit(Qt.AlignLeft, "bla")
+
+
+class TestSignalNewEnum(unittest.TestCase):
+ """Test for PYSIDE-2095, signals with new enums in Python 3.11."""
+
+ def testIt(self):
+ sender = Sender()
+ receiver = Receiver()
+ sender.test_sig.connect(receiver.handler)
+
+ sender.emit_test_sig()
+ self.assertEqual(receiver.result, 1)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_number_limit_test.py b/sources/pyside6/tests/signals/signal_number_limit_test.py
new file mode 100644
index 000000000..29825fe50
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_number_limit_test.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Emitter(QObject):
+ s1 = Signal()
+ s2 = Signal()
+ s3 = Signal()
+ s4 = Signal()
+ s5 = Signal()
+ s6 = Signal()
+ s7 = Signal()
+ s8 = Signal()
+ s9 = Signal()
+ s10 = Signal()
+ s11 = Signal()
+ s12 = Signal()
+ s13 = Signal()
+ s14 = Signal()
+
+
+class SignalNumberLimitTest(unittest.TestCase):
+ def myCb(self):
+ self._count += 1
+
+ def testBug(self):
+ e = Emitter()
+ e.s1.connect(self.myCb)
+ e.s2.connect(self.myCb)
+ e.s3.connect(self.myCb)
+ e.s4.connect(self.myCb)
+ e.s5.connect(self.myCb)
+ e.s6.connect(self.myCb)
+ e.s7.connect(self.myCb)
+ e.s8.connect(self.myCb)
+ e.s9.connect(self.myCb)
+ e.s10.connect(self.myCb)
+ e.s11.connect(self.myCb)
+ e.s12.connect(self.myCb)
+ e.s13.connect(self.myCb)
+ e.s14.connect(self.myCb)
+
+ self._count = 0
+ e.s1.emit()
+ e.s2.emit()
+ e.s3.emit()
+ e.s4.emit()
+ e.s5.emit()
+ e.s6.emit()
+ e.s7.emit()
+ e.s8.emit()
+ e.s9.emit()
+ e.s10.emit()
+ e.s11.emit()
+ e.s12.emit()
+ e.s13.emit()
+ e.s14.emit()
+ self.assertEqual(self._count, 14)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_object_test.py b/sources/pyside6/tests/signals/signal_object_test.py
new file mode 100644
index 000000000..607f51813
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_object_test.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QTimer, Signal, QObject, Slot, Qt
+from helper.usesqapplication import UsesQApplication
+
+
+class MyObject(QTimer):
+ sig1 = Signal()
+ sig2 = Signal(int, name='rangeChanged')
+ sig3 = Signal(int)
+ sig4 = Signal((int,), (str,))
+ sig5 = Signal((str,), (int,))
+ sig6 = Signal(QObject)
+
+ @Slot(int)
+ def myRange(self, r):
+ self._range = r
+
+ def slot1(self):
+ self._called = True
+
+ def slotString(self, s):
+ self._s = s
+
+ def slotObject(self, o):
+ self._o = o
+
+
+class SignalObjectTest(UsesQApplication):
+ def cb(self):
+ self._cb_called = True
+ self.app.exit()
+
+ def testsingleConnect(self):
+ o = MyObject()
+ o.sig1.connect(o.slot1)
+ o.sig1.emit()
+ self.assertTrue(o._called)
+
+ def testSignalWithArgs(self):
+ o = MyObject()
+ o.sig3.connect(o.myRange)
+ o.sig3.emit(10)
+ self.assertEqual(o._range, 10)
+
+ def testSignatureParse(self):
+ o = MyObject()
+ o.sig2.connect(o.myRange)
+ o.sig2.emit(10)
+
+ def testDictOperator(self):
+ o = MyObject()
+ o.sig4[str].connect(o.slotString)
+ o.sig4[str].emit("PySide")
+ self.assertEqual(o._s, "PySide")
+
+ def testGeneretedSignal(self):
+ o = MyObject()
+ o.timeout.connect(self.cb)
+ o.start(100)
+ self.app.exec()
+ self.assertTrue(self._cb_called)
+
+ def testConnectionType(self):
+ o = MyObject()
+ o.timeout.connect(self.cb, type=Qt.DirectConnection)
+ o.start(100)
+ self.app.exec()
+ self.assertTrue(self._cb_called)
+
+ def testSignalWithSignal(self):
+ o = MyObject()
+ o.sig2.connect(o.myRange)
+ o.sig5.connect(o.sig2)
+ o.sig5[int].emit(10)
+ self.assertEqual(o._range, 10)
+
+ def testSignalWithObject(self):
+ o = MyObject()
+ o.sig6.connect(o.slotObject)
+ arg = QObject()
+ o.sig6.emit(arg)
+ self.assertEqual(arg, o._o)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_signature_test.py b/sources/pyside6/tests/signals/signal_signature_test.py
new file mode 100644
index 000000000..e8f08b2d9
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_signature_test.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test case for signal signature received by QObject::connectNotify().'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, SIGNAL, SLOT
+from helper.usesqapplication import UsesQApplication
+
+
+called = False
+name = "Old"
+
+
+class Sender(QObject):
+ dummySignal = Signal()
+
+
+class Obj(QObject):
+ dummySignalArgs = Signal(str)
+ numberSignal = Signal(int)
+
+ def __init__(self):
+ super().__init__()
+ self.signal = ''
+
+ def connectNotify(self, signal):
+ self.signal = signal
+
+ @staticmethod
+ def static_method():
+ global called
+ called = True
+
+ @staticmethod
+ def static_method_args(arg="default"):
+ global name
+ name = arg
+
+
+def callback(arg=None):
+ pass
+
+
+def callback_empty():
+ pass
+
+
+class TestConnectNotifyWithNewStyleSignals(UsesQApplication):
+ '''Test case for signal signature received by QObject::connectNotify().'''
+
+ def testOldStyle(self):
+ sender = Obj()
+ receiver = QObject()
+ sender.connect(SIGNAL('destroyed()'), receiver, SLOT('deleteLater()'))
+ # When connecting to a regular slot, and not a python callback function, QObject::connect
+ # will use the non-cloned method signature, so connectinc to destroyed() will actually
+ # connect to destroyed(QObject*).
+ self.assertEqual(sender.signal.methodSignature(), 'destroyed(QObject*)')
+
+ def testOldStyleWithPythonCallback(self):
+ sender = Obj()
+ sender.connect(SIGNAL('destroyed()'), callback)
+ self.assertEqual(sender.signal.methodSignature(), 'destroyed()')
+
+ def testNewStyle(self):
+ sender = Obj()
+
+ sender.destroyed.connect(callback_empty)
+ self.assertEqual(sender.signal.methodSignature(), 'destroyed()')
+
+ sender.destroyed[QObject].connect(callback)
+ self.assertEqual(sender.signal.methodSignature(), 'destroyed(QObject*)')
+
+ def testStaticSlot(self):
+ global called
+ sender = Sender()
+ sender.dummySignal.connect(Obj.static_method)
+ sender.dummySignal.emit()
+ self.assertTrue(called)
+
+ def testStaticSlotArgs(self):
+ global name
+ sender = Obj()
+ sender.dummySignalArgs.connect(Obj.static_method_args)
+ sender.dummySignalArgs[str].emit("New")
+ self.assertEqual(name, "New")
+
+ def testLambdaSlot(self):
+ sender = Obj()
+ sender.numberSignal[int].connect(lambda x: 42)
+ with self.assertRaises(IndexError):
+ sender.numberSignal[str].emit("test")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signal_with_primitive_type_test.py b/sources/pyside6/tests/signals/signal_with_primitive_type_test.py
new file mode 100644
index 000000000..01492b333
--- /dev/null
+++ b/sources/pyside6/tests/signals/signal_with_primitive_type_test.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QCoreApplication, QTimeLine
+
+
+class SignalPrimitiveTypeTest(unittest.TestCase):
+
+ def signalValueChanged(self, v):
+ self.called = True
+ self._app.quit()
+
+ def createTimeLine(self):
+ self.called = False
+ tl = QTimeLine(10000)
+ tl.valueChanged.connect(self.signalValueChanged)
+ return tl
+
+ def testTimeLine(self):
+ self._valueChangedCount = 0
+ self._app = QCoreApplication([])
+ tl = self.createTimeLine()
+ tl.start()
+ self._app.exec()
+ self.assertTrue(self.called)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/signals.pyproject b/sources/pyside6/tests/signals/signals.pyproject
new file mode 100644
index 000000000..b63724eaf
--- /dev/null
+++ b/sources/pyside6/tests/signals/signals.pyproject
@@ -0,0 +1,19 @@
+{
+ "files": ["anonymous_slot_leak_test.py", "args_dont_match_test.py",
+ "bug_189.py", "bug_311.py", "bug_312.py", "bug_319.py", "bug_79.py",
+ "decorators_test.py", "disconnect_test.py", "invalid_callback_test.py",
+ "lambda_gui_test.py", "lambda_test.py", "leaking_signal_test.py",
+ "multiple_connections_gui_test.py", "multiple_connections_test.py",
+ "pysignal_test.py", "qobject_callable_connect_test.py", "qobject_destroyed_test.py",
+ "qobject_receivers_test.py", "qobject_sender_test.py", "ref01_test.py",
+ "ref02_test.py", "ref03_test.py", "ref04_test.py", "ref05_test.py",
+ "ref06_test.py", "segfault_proxyparent_test.py",
+ "self_connect_test.py", "short_circuit_test.py",
+ "signal2signal_connect_test.py", "signal_across_threads.py",
+ "signal_autoconnect_test.py", "signal_connectiontype_support_test.py",
+ "signal_emission_gui_test.py", "signal_emission_test.py",
+ "signal_enum_test.py", "signal_func_test.py", "signal_manager_refcount_test.py",
+ "signal_newenum_test.py", "signal_number_limit_test.py",
+ "signal_object_test.py", "signal_signature_test.py", "signal_with_primitive_type_test.py",
+ "slot_reference_count_test.py", "static_metaobject_test.py"]
+}
diff --git a/sources/pyside6/tests/signals/slot_reference_count_test.py b/sources/pyside6/tests/signals/slot_reference_count_test.py
new file mode 100644
index 000000000..9d5c73652
--- /dev/null
+++ b/sources/pyside6/tests/signals/slot_reference_count_test.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+''' Forced disconnection: Delete one end of the signal connection'''
+
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal
+
+
+class Dummy(QObject):
+ foo = Signal()
+
+ def dispatch(self):
+ self.foo.emit()
+
+
+class PythonSignalRefCount(unittest.TestCase):
+
+ def setUp(self):
+ self.emitter = Dummy()
+
+ def tearDown(self):
+ self.emitter
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testRefCount(self):
+ def cb(*args):
+ pass
+
+ self.assertEqual(sys.getrefcount(cb), 2)
+
+ self.emitter.foo.connect(cb)
+ self.assertEqual(sys.getrefcount(cb), 3)
+
+ self.emitter.foo.disconnect(cb)
+ self.assertEqual(sys.getrefcount(cb), 2)
+
+
+class CppSignalRefCount(unittest.TestCase):
+
+ def setUp(self):
+ self.emitter = QObject()
+
+ def tearDown(self):
+ self.emitter
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testRefCount(self):
+ def cb(*args):
+ pass
+
+ self.assertEqual(sys.getrefcount(cb), 2)
+
+ self.emitter.destroyed.connect(cb)
+ self.assertEqual(sys.getrefcount(cb), 3)
+
+ self.emitter.destroyed.disconnect(cb)
+ self.assertEqual(sys.getrefcount(cb), 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/signals/static_metaobject_test.py b/sources/pyside6/tests/signals/static_metaobject_test.py
new file mode 100644
index 000000000..d7bf73e44
--- /dev/null
+++ b/sources/pyside6/tests/signals/static_metaobject_test.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+"""Tests covering signal emission and receiving to python slots"""
+
+import gc
+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
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Signal, Slot, SIGNAL
+from helper.usesqapplication import UsesQApplication
+
+
+class Sender(QObject):
+
+ foo = Signal()
+ foo2 = Signal()
+
+
+class MyObject(QObject):
+
+ foo2 = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._slotCalledCount = 0
+
+ # this '@Slot()' is needed to get the right sort order in testSharedSignalEmission.
+ # For some reason, it also makes the tests actually work!
+ @Slot()
+ def mySlot(self):
+ self._slotCalledCount = self._slotCalledCount + 1
+
+
+class StaticMetaObjectTest(UsesQApplication):
+
+ def testSignalPropagation(self):
+ """Old style, dynamic signal creation."""
+ o = QObject()
+ o2 = MyObject()
+
+ # SIGNAL foo not created yet
+ self.assertEqual(o.metaObject().indexOfSignal("foo()"), -1)
+
+ o.connect(SIGNAL("foo()"), o2.mySlot)
+ # SIGNAL foo create after connect
+ self.assertTrue(o.metaObject().indexOfSignal("foo()") > 0)
+
+ # SIGNAL does not propagate to others objects of the same type
+ self.assertEqual(o2.metaObject().indexOfSignal("foo()"), -1)
+
+ del o
+ del o2
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ o = MyObject()
+ # The SIGNAL was destroyed with old objects
+ self.assertEqual(o.metaObject().indexOfSignal("foo()"), -1)
+
+ def testSharedSignalEmission(self):
+ o = Sender()
+ m = MyObject()
+
+ o.foo2.connect(m.mySlot)
+ m.foo2.connect(m.mySlot)
+ o.foo2.emit()
+ self.assertEqual(m._slotCalledCount, 1)
+ del o
+ # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
+ gc.collect()
+ m.foo2.emit()
+ self.assertEqual(m._slotCalledCount, 2)
+
+
+if __name__ == '__main__':
+ unittest.main()