diff options
Diffstat (limited to 'sources/pyside6/tests/pysidetest')
55 files changed, 3853 insertions, 0 deletions
diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt new file mode 100644 index 000000000..38f42f342 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt @@ -0,0 +1,171 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(pysidetest) +project(testbinding) + +cmake_minimum_required(VERSION 3.18) + +set(QT_USE_QTCORE 1) +# no more supported: include(${QT_USE_FILE}) + +# removed after qtbase/054b66a65748c9ebfafeca88bf31669a24994237, is this required? +# add_definitions(${Qt${QT_MAJOR_VERSION}Core_DEFINITIONS}) + +set(CMAKE_AUTOMOC ON) + +add_definitions(-DQT_SHARED) +add_definitions(-DRXX_ALLOCATOR_INIT_0) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +set(pysidetest_SRC +containertest.cpp containertest.h +flagstest.cpp flagstest.h +hiddenobject.cpp hiddenobject.h +pysidetest_global.h +pysidetest_macros.h +sharedpointertestbench.cpp sharedpointertestbench.h +testobject.cpp testobject.h +testview.cpp testview.h +testqvariantenum.cpp testqvariantenum.h +) + +set(testbinding_SRC +${CMAKE_CURRENT_BINARY_DIR}/testbinding/containertest_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/flagsnamespace_classforenum_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/testobject_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/intvalue_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/pysidecpp_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/pysidecpp_testobjectwithnamespace_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/pysidecpp_testobject2withnamespace_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/pysidecpp2_testobjectwithoutnamespace_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/qsharedpointer_qobject_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/qsharedpointer_int_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/sharedpointertestbench_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/testview_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/testbinding_module_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/testqvariantenum_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/testbinding/qvariantholder_wrapper.cpp +) + +# Get per module include dirs. +# There are usually 3 paths there: +# ./qt/include/; ./qt/include/QtCore ; ./qt/mkspecs/linux-g++ +# on framework build they are: +# ./qt/lib/QtCore.framework; ./qt/lib/QtCore.framework/Headers ; ./qt/mkspecs/macx-clang +# Thus we use the second direct path, which contains the actual header files. + +set(Qt6Core_DIRECT_INCLUDE_DIR ${Qt6Core_INCLUDE_DIRS}) +set(Qt6Gui_DIRECT_INCLUDE_DIR ${Qt6Gui_INCLUDE_DIRS}) +set(Qt6Widgets_DIRECT_INCLUDE_DIR ${Qt6Widgets_INCLUDE_DIRS}) + +# Adjust include headers paths for frameworks. +set(shiboken_framework_include_dirs_option "") +if(CMAKE_HOST_APPLE AND QtCore_is_framework) + set(shiboken_framework_include_dirs "${QT_FRAMEWORK_INCLUDE_DIR}") + set(shiboken_framework_include_dirs_option "--framework-include-paths=${shiboken_framework_include_dirs}") +endif() + +make_path(testbinding_include_dirs ${pyside6_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../../../shiboken6/libshiboken + ${CMAKE_CURRENT_SOURCE_DIR}/../../PySide6 + ${CMAKE_CURRENT_SOURCE_DIR}/../../libpyside + ${QT_INCLUDE_DIR} + ${Qt${QT_MAJOR_VERSION}Core_DIRECT_INCLUDE_DIR} + ${Qt${QT_MAJOR_VERSION}Gui_DIRECT_INCLUDE_DIR} + ${Qt${QT_MAJOR_VERSION}Widgets_DIRECT_INCLUDE_DIR} + ) + +make_path(testbinding_typesystem_path ${pyside6_SOURCE_DIR} + ${pyside6_BINARY_DIR}) + +shiboken_get_tool_shell_wrapper(shiboken tool_wrapper) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/mjb_rejected_classes.log" + BYPRODUCTS ${testbinding_SRC} + COMMAND + ${tool_wrapper} + $<TARGET_FILE:Shiboken6::shiboken6> + ${GENERATOR_EXTRA_FLAGS} + ${CMAKE_CURRENT_SOURCE_DIR}/pysidetest_global.h + --include-paths=${testbinding_include_dirs} + ${shiboken_framework_include_dirs_option} + --typesystem-paths=${testbinding_typesystem_path} + --output-directory=${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_pysidetest.xml + --api-version=${SUPPORTED_QT_VERSION} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running generator for test binding..." +) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} + ${pyside6_SOURCE_DIR} + ${QtCore_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtWidgets_GEN_DIR} + ${libpyside_SOURCE_DIR}) + +add_library(pysidetest SHARED ${pysidetest_SRC}) +set_target_properties(pysidetest PROPERTIES + DEFINE_SYMBOL BUILD_PYSIDETEST) + +target_link_libraries(pysidetest + Shiboken6::libshiboken + Qt::Core Qt::Gui Qt::Widgets) + +add_library(testbinding MODULE ${testbinding_SRC}) +set_property(TARGET testbinding PROPERTY PREFIX "") +set_property(TARGET testbinding PROPERTY OUTPUT_NAME "testbinding${SHIBOKEN_PYTHON_EXTENSION_SUFFIX}") +if(WIN32) + set_property(TARGET testbinding PROPERTY SUFFIX ".pyd") +endif() + +target_link_libraries(testbinding + pysidetest + pyside6 + Shiboken6::libshiboken + Qt::Core Qt::Gui Qt::Widgets) + +add_dependencies(testbinding pyside6 QtCore QtGui QtWidgets pysidetest) +create_generator_target(testbinding) + +PYSIDE_TEST(constructor_properties_test.py) +PYSIDE_TEST(container_test.py) +PYSIDE_TEST(decoratedslot_test.py) +PYSIDE_TEST(delegatecreateseditor_test.py) +PYSIDE_TEST(all_modules_load_test.py) +PYSIDE_TEST(bug_1016.py) +PYSIDE_TEST(enum_test.py) +PYSIDE_TEST(homonymoussignalandmethod_test.py) +PYSIDE_TEST(iterable_test.py) +PYSIDE_TEST(list_signal_test.py) +PYSIDE_TEST(mixin_signal_slots_test.py) +PYSIDE_TEST(modelview_test.py) +PYSIDE_TEST(multiple_inheritance_test.py) +PYSIDE_TEST(new_inherited_functions_test.py) +PYSIDE_TEST(notify_id.py) +PYSIDE_TEST(properties_test.py) +PYSIDE_TEST(property_python_test.py) +PYSIDE_TEST(snake_case_test.py) +PYSIDE_TEST(true_property_test.py) +PYSIDE_TEST(qapp_like_a_macro_test.py) +PYSIDE_TEST(qvariant_test.py) +PYSIDE_TEST(repr_test.py) +PYSIDE_TEST(shared_pointer_test.py) +PYSIDE_TEST(signal_tp_descr_get_test.py) +PYSIDE_TEST(signal_slot_warning.py) +PYSIDE_TEST(signalandnamespace_test.py) +PYSIDE_TEST(signalemissionfrompython_test.py) +PYSIDE_TEST(signalinstance_equality_test.py) +PYSIDE_TEST(signalwithdefaultvalue_test.py) +PYSIDE_TEST(typedef_signal_test.py) +PYSIDE_TEST(version_test.py) +PYSIDE_TEST(mock_as_slot_test.py) +PYSIDE_TEST(pyenum_relax_options_test.py) diff --git a/sources/pyside6/tests/pysidetest/all_modules_load_test.py b/sources/pyside6/tests/pysidetest/all_modules_load_test.py new file mode 100644 index 000000000..454c2b14f --- /dev/null +++ b/sources/pyside6/tests/pysidetest/all_modules_load_test.py @@ -0,0 +1,29 @@ +# 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) + +import PySide6 + +# Note: +# "from PySide6 import *" can only be used at module level. +# It is also really not recommended to use. But for testing, +# the "__all__" variable is a great feature! + + +class AllModulesImportTest(unittest.TestCase): + def testAllModulesCanImport(self): + # would also work: exec("from PySide6 import *") + for name in PySide6.__all__: + exec(f"import PySide6.{name}") + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/bug_1016.py b/sources/pyside6/tests/pysidetest/bug_1016.py new file mode 100644 index 000000000..219384e66 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/bug_1016.py @@ -0,0 +1,26 @@ +# 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) + +import shiboken6 +from testbinding import getHiddenObject + + +class TestBug1016 (unittest.TestCase): + + def testIt(self): + obj = getHiddenObject() + self.assertEqual(obj.callMe(), None) + self.assertTrue(obj.wasCalled()) + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/constructor_properties_test.py b/sources/pyside6/tests/pysidetest/constructor_properties_test.py new file mode 100644 index 000000000..ec6e39821 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/constructor_properties_test.py @@ -0,0 +1,127 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +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 helper.usesqapplication import UsesQApplication +from PySide6.QtCore import Qt +from PySide6.QtGui import QColor, QAction +from PySide6.QtWidgets import QApplication, QLabel, QFrame + +is_pypy = hasattr(sys, "pypy_version_info") +if not is_pypy: + from PySide6.support import feature + + +class ConstructorPropertiesTest(unittest.TestCase): + + def setUp(self): + qApp or QApplication() + if not is_pypy: + feature.reset() + + def tearDown(self): + if not is_pypy: + feature.reset() + qApp.shutdown() + + # PYSIDE-1019: First property extension was support by the constructor. + def testCallConstructor(self): + label = QLabel( + frameStyle=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property + lineWidth=2, # QFrame property + text="first line\nsecond line", # QLabel property + alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property + ) + self.assertEqual(label.lineWidth(), 2) + self.assertRaises(AttributeError, lambda: QLabel( + somethingelse=42, + )) + + # PYSIDE-1705: The same with snake_case + @unittest.skipIf(is_pypy, "feature switching is not yet possible in PyPy") + def testCallConstructor_snake(self): + from __feature__ import snake_case + + label = QLabel( + frame_style=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property + line_width=2, # QFrame property + text="first line\nsecond line", # QLabel property + alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property + ) + self.assertEqual(label.line_width(), 2) + self.assertRaises(AttributeError, lambda: QLabel( + lineWidth=2, # QFrame property + )) + + # PYSIDE-1705: The same with true_property + @unittest.skipIf(is_pypy, "feature switching is not yet possible in PyPy") + def testCallConstructor_prop(self): + from __feature__ import true_property + + label = QLabel( + frameStyle=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property + lineWidth=2, # QFrame property + text="first line\nsecond line", # QLabel property + alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property + ) + self.assertEqual(label.lineWidth, 2) + self.assertRaises(AttributeError, lambda: QLabel( + line_width=2, # QFrame property + )) + + # PYSIDE-1705: The same with snake_case and true_property + @unittest.skipIf(is_pypy, "feature switching is not yet possible in PyPy") + def testCallConstructor_prop_snake(self): + from __feature__ import snake_case, true_property + + label = QLabel( + frame_style=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property + line_width=2, # QFrame property + text="first line\nsecond line", # QLabel property + alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property + ) + self.assertEqual(label.line_width, 2) + self.assertRaises(AttributeError, lambda: QLabel( + lineWidth=2, # QFrame property + )) + + +class DiverseKeywordsTest(UsesQApplication): + + def testDuplicateKeyword(self): + r, g, b, a = 1, 2, 3, 4 + with self.assertRaises(TypeError) as cm: + QColor(r, g, b, a, a=0) + self.assertTrue("multiple" in cm.exception.args[0]) + + # PYSIDE-1305: Handle keyword args correctly. + def testUndefinedKeyword(self): + r, g, b, a = 1, 2, 3, 4 + # From the jira issue: + with self.assertRaises(AttributeError) as cm: + QColor(r, g, b, a, alpha=0) + self.assertTrue("unsupported" in cm.exception.args[0]) + + # PYSIDE-1305: Handle keyword args correctly. + def testUndefinedConstructorKeyword(self): + # make sure that the given attribute lands in the constructor + x = QAction(autoRepeat=False) + self.assertEqual(x.autoRepeat(), False) + x = QAction(autoRepeat=True) + self.assertEqual(x.autoRepeat(), True) + # QAction derives from QObject, and so the missing attributes + # in the constructor are reported as AttributeError. + with self.assertRaises(AttributeError): + QAction(some_other_name=42) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/container_test.py b/sources/pyside6/tests/pysidetest/container_test.py new file mode 100644 index 000000000..c83e1f26c --- /dev/null +++ b/sources/pyside6/tests/pysidetest/container_test.py @@ -0,0 +1,63 @@ +# 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 testbinding import ContainerTest + + +EXPECTED_DICT = {1: ["v1"], 2: ["v2_1", "v2_2"], + 3: ["v3"], + 4: ["v4_1", "v4_2"]} + + +EXPECTED_LIST = [1, 2] + + +def sort_values(m): + """Sort value lists in dicts since passing through a QMultiMap changes the order""" + result = {} + for key, values in m.items(): + result[key] = sorted(values) + return result + + +class ContainerTestTest(unittest.TestCase): + + def testMultiMap(self): + m1 = ContainerTest.createMultiMap() + self.assertEqual(sort_values(m1), EXPECTED_DICT) + m2 = ContainerTest.passThroughMultiMap(m1) + self.assertEqual(sort_values(m2), EXPECTED_DICT) + + def testMultiHash(self): + m1 = ContainerTest.createMultiHash() + self.assertEqual(sort_values(m1), EXPECTED_DICT) + m2 = ContainerTest.passThroughMultiHash(m1) + self.assertEqual(sort_values(m2), EXPECTED_DICT) + + def testList(self): + l1 = ContainerTest.createList() + self.assertEqual(l1, EXPECTED_LIST) + l2 = ContainerTest.passThroughList(l1) + self.assertEqual(l2, EXPECTED_LIST) + + def testSet(self): + s1 = ContainerTest.createSet() # Order is not predictable + s2 = ContainerTest.passThroughSet(s1) + self.assertEqual(sorted(list(s1)), sorted(list(s2))) + + # Since lists are iterable, it should be possible to pass them to set API + l2 = ContainerTest.passThroughSet(EXPECTED_LIST) + self.assertEqual(sorted(list(l2)), EXPECTED_LIST) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/containertest.cpp b/sources/pyside6/tests/pysidetest/containertest.cpp new file mode 100644 index 000000000..da8729af3 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/containertest.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "containertest.h" + +using namespace Qt::StringLiterals; + +ContainerTest::ContainerTest() = default; + +QMultiMap<int, QString> ContainerTest::createMultiMap() +{ + static const QMultiMap<int, QString> + result{{1, u"v1"_s}, + {2, u"v2_1"_s}, {2, u"v2_2"_s}, + {3, u"v3"_s}, + {4, u"v4_1"_s}, {4, u"v4_2"_s}}; + return result; +} + +QMultiMap<int, QString> ContainerTest::passThroughMultiMap(const QMultiMap<int, QString> &in) +{ + return in; +} + +QMultiHash<int, QString> ContainerTest::createMultiHash() +{ + static const QMultiHash<int, QString> + result{{1, u"v1"_s}, + {2, u"v2_1"_s}, {2, u"v2_2"_s}, + {3, u"v3"_s}, + {4, u"v4_1"_s}, {4, u"v4_2"_s}}; + return result; + +} + +QMultiHash<int, QString> ContainerTest::passThroughMultiHash(const QMultiHash<int, QString> &in) +{ + return in; +} + +QList<int> ContainerTest::createList() +{ + return {1, 2}; +} + +QList<int> ContainerTest::passThroughList(const QList<int> &list) +{ + return list; +} + +QSet<int> ContainerTest::createSet() +{ + return {1, 2}; +} + +QSet<int> ContainerTest::passThroughSet(const QSet<int> &set) +{ + return set; +} diff --git a/sources/pyside6/tests/pysidetest/containertest.h b/sources/pyside6/tests/pysidetest/containertest.h new file mode 100644 index 000000000..068fb3c19 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/containertest.h @@ -0,0 +1,34 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "pysidetest_macros.h" + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QMultiMap> +#include <QtCore/QMultiHash> +#include <QtCore/QSet> +#include <QtCore/QString> + +class PYSIDETEST_API ContainerTest +{ +public: + ContainerTest(); + + static QMultiMap<int, QString> createMultiMap(); + + static QMultiMap<int, QString> passThroughMultiMap(const QMultiMap<int, QString> &in); + + static QMultiHash<int, QString> createMultiHash(); + + static QMultiHash<int, QString> passThroughMultiHash(const QMultiHash<int, QString> &in); + + static QList<int> createList(); + static QList<int> passThroughList(const QList<int> &list); + + static QSet<int> createSet(); + static QSet<int> passThroughSet(const QSet<int> &set); +}; diff --git a/sources/pyside6/tests/pysidetest/curr_errors.txt b/sources/pyside6/tests/pysidetest/curr_errors.txt new file mode 100644 index 000000000..a02da203d --- /dev/null +++ b/sources/pyside6/tests/pysidetest/curr_errors.txt @@ -0,0 +1,12 @@ +Generating class model... [OK] +Generating enum model... [OK] +Generating namespace model... [OK] +Resolving typedefs... [OK] +Fixing class inheritance... [OK] +Detecting inconsistencies in class model... [OK] +[OK] + type 'QPyTextObject' is specified in typesystem, but not defined. This could potentially lead to compilation errors. + + + +Done, 1 warnings (1051 known issues) diff --git a/sources/pyside6/tests/pysidetest/decoratedslot_test.py b/sources/pyside6/tests/pysidetest/decoratedslot_test.py new file mode 100644 index 000000000..3ec9ac9e3 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/decoratedslot_test.py @@ -0,0 +1,50 @@ +#!/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(True) + +from PySide6.QtCore import QObject +from testbinding import TestObject + + +class Receiver(QObject): + def __init__(self): + super().__init__() + self.called = False + + def ReceiverDecorator(func): + def decoratedFunction(self, *args, **kw): + func(self, *args, **kw) + return decoratedFunction + + # This method with the same name of the internal decorated function + # is here to test the binding capabilities. + def decoratedFunction(self): + pass + + @ReceiverDecorator + def slot(self): + self.called = True + + +class DecoratedSlotTest(unittest.TestCase): + + def testCallingOfDecoratedSlot(self): + obj = TestObject(0) + receiver = Receiver() + obj.staticMethodDouble.connect(receiver.slot) + obj.emitStaticMethodDoubleSignal() + self.assertTrue(receiver.called) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/delegatecreateseditor_test.py b/sources/pyside6/tests/pysidetest/delegatecreateseditor_test.py new file mode 100644 index 000000000..8964ec1ed --- /dev/null +++ b/sources/pyside6/tests/pysidetest/delegatecreateseditor_test.py @@ -0,0 +1,81 @@ +#!/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(True) + +from helper.usesqapplication import UsesQApplication +from testbinding import TestView +from PySide6.QtCore import Qt +from PySide6.QtGui import QStandardItem, QStandardItemModel +from PySide6.QtWidgets import (QAbstractItemDelegate, QComboBox, + QSpinBox, QStyledItemDelegate, + QStyleOptionViewItem, QWidget) + +id_text = 'This is me' + + +class DelegateDoesntKeepReferenceToEditor(QAbstractItemDelegate): + def createEditor(self, parent, option, index): + comboBox = QComboBox(parent) + comboBox.addItem(id_text) + return comboBox + + +class DelegateKeepsReferenceToEditor(QAbstractItemDelegate): + def __init__(self, parent=None): + QAbstractItemDelegate.__init__(self, parent) + self.comboBox = QComboBox() + self.comboBox.addItem(id_text) + + def createEditor(self, parent, option, index): + self.comboBox.setParent(parent) + return self.comboBox + + +class EditorCreatedByDelegateTest(UsesQApplication): + + def testDelegateDoesntKeepReferenceToEditor(self): + view = TestView(None) + delegate = DelegateDoesntKeepReferenceToEditor() + view.setItemDelegate(delegate) + editor = view.getEditorWidgetFromItemDelegate() + self.assertEqual(type(editor), QComboBox) + self.assertEqual(editor.count(), 1) + self.assertEqual(editor.itemData(0, Qt.DisplayRole), id_text) + editor.metaObject() + + def testDelegateKeepsReferenceToEditor(self): + view = TestView(None) + delegate = DelegateKeepsReferenceToEditor() + view.setItemDelegate(delegate) + editor = view.getEditorWidgetFromItemDelegate() + self.assertEqual(type(editor), QComboBox) + self.assertEqual(editor.count(), 1) + self.assertEqual(editor.itemData(0, Qt.DisplayRole), id_text) + editor.metaObject() + + def testIntDelegate(self): + """PYSIDE-1250: When creating a QVariant, use int instead of long long + for anything that fits into a int. Verify by checking that a spin + box is created as item view editor for int.""" + item = QStandardItem() + item.setData(123123, Qt.EditRole) # <-- QVariant conversion here + model = QStandardItemModel() + model.appendRow(item) + style_option = QStyleOptionViewItem() + delegate = QStyledItemDelegate() + editor = delegate.createEditor(None, style_option, model.index(0, 0)) + self.assertEqual(type(editor), QSpinBox) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/enum_test.py b/sources/pyside6/tests/pysidetest/enum_test.py new file mode 100644 index 000000000..832834530 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/enum_test.py @@ -0,0 +1,186 @@ +# 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 Qt +from testbinding import Enum1, TestObjectWithoutNamespace + +import dis + + +class ListConnectionTest(unittest.TestCase): + + def testEnumVisibility(self): + self.assertEqual(Enum1.Option1, 1) + self.assertEqual(Enum1.Option2, 2) + self.assertEqual(TestObjectWithoutNamespace.Enum2.Option3, 3) + self.assertEqual(TestObjectWithoutNamespace.Enum2.Option4, 4) + + def testFlagComparisonOperators(self): # PYSIDE-1696, compare to self + f1 = Qt.AlignHCenter | Qt.AlignBottom + f2 = Qt.AlignHCenter | Qt.AlignBottom + self.assertTrue(f1 == f1) + self.assertTrue(f1 <= f1) + self.assertTrue(f1 >= f1) + self.assertFalse(f1 != f1) + self.assertFalse(f1 < f1) + self.assertFalse(f1 > f1) + + self.assertTrue(f1 == f2) + self.assertTrue(f1 <= f2) + self.assertTrue(f1 >= f2) + self.assertFalse(f1 != f2) + self.assertFalse(f1 < f2) + self.assertFalse(f1 > f2) + + self.assertTrue(Qt.AlignHCenter < Qt.AlignBottom) + self.assertFalse(Qt.AlignHCenter > Qt.AlignBottom) + self.assertFalse(Qt.AlignBottom < Qt.AlignHCenter) + self.assertTrue(Qt.AlignBottom > Qt.AlignHCenter) + + +# PYSIDE-1735: We are testing that opcodes do what they are supposed to do. +# This is needed in the PyEnum forgiveness mode where we need +# to introspect the code if an Enum was called with no args. +class InvestigateOpcodesTest(unittest.TestCase): + + def probe_function1(self): + x = Qt.Alignment + + def probe_function2(self): + x = Qt.Alignment() + + @staticmethod + def read_code(func, **kw): + return list(instr[:3] for instr in dis.Bytecode(func, **kw)) + + @staticmethod + def get_sizes(func, **kw): + ops = list((instr.opname, instr.offset) for instr in dis.Bytecode(func, **kw)) + res = [] + for idx in range(1, len(ops)): + res.append((ops[idx - 1][0], ops[idx][1] - ops[idx - 1][1])) + return sorted(res, key=lambda x: (x[1], x[0])) + + _sin = sys.implementation.name + + @unittest.skipIf(hasattr(sys.flags, "nogil"), f"{_sin} has different opcodes") + def testByteCode(self): + import dis + # opname, opcode, arg + result_1 = [('LOAD_GLOBAL', 116, 0), + ('LOAD_ATTR', 106, 1), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + result_2 = [('LOAD_GLOBAL', 116, 0), + ('LOAD_METHOD', 160, 1), + ('CALL_METHOD', 161, 0), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + if sys.version_info[:2] <= (3, 6): + + result_2 = [('LOAD_GLOBAL', 116, 0), + ('LOAD_ATTR', 106, 1), + ('CALL_FUNCTION', 131, 0), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + if sys.version_info[:2] == (3, 11): + # Note: Python 3.11 is a bit more complex because it can optimize itself. + # Opcodes are a bit different, and a hidden second code object is used. + # We investigate this a bit, because we want to be warned when things change. + QUICKENING_WARMUP_DELAY = 8 + + result_1 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 0), + ('LOAD_ATTR', 106, 1), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + result_2 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 1), + ('LOAD_ATTR', 106, 1), + ('PRECALL', 166, 0), + ('CALL', 171, 0), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + sizes_2 = [('LOAD_CONST', 2), + ('RESUME', 2), + ('STORE_FAST', 2), + ('PRECALL', 4), + ('CALL', 10), + ('LOAD_ATTR', 10), + ('LOAD_GLOBAL', 12)] + + self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_2) + self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_2) + + @staticmethod + def code_quicken(f, times): + # running the code triggers acceleration after some runs. + for _ in range(times): + f() + + code_quicken(self.probe_function2, QUICKENING_WARMUP_DELAY - 1) + self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_2) + self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_2) + + result_3 = [('RESUME_QUICK', 150, 0), + ('LOAD_GLOBAL_MODULE', 55, 1), + ('LOAD_ATTR_ADAPTIVE', 39, 1), + ('PRECALL_ADAPTIVE', 64, 0), + ('CALL_ADAPTIVE', 22, 0), + ('STORE_FAST', 125, 1), + ('LOAD_CONST', 100, 0), + ('RETURN_VALUE', 83, None)] + + sizes_3 = [('LOAD_CONST', 2), + ('RESUME_QUICK', 2), + ('STORE_FAST', 2), + ('PRECALL_ADAPTIVE', 4), + ('CALL_ADAPTIVE', 10), + ('LOAD_ATTR_ADAPTIVE', 10), + ('LOAD_GLOBAL_MODULE', 12)] + + code_quicken(self.probe_function2, 1) + self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_3) + self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_3) + + if sys.version_info[:2] >= (3, 12): + + result_1 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 0), + ('LOAD_ATTR', 106, 2), + ('STORE_FAST', 125, 1), + ('RETURN_CONST', 121, 0)] + + result_2 = [('RESUME', 151, 0), + ('LOAD_GLOBAL', 116, 1), + ('LOAD_ATTR', 106, 2), + ('CALL', 171, 0), + ('STORE_FAST', 125, 1), + ('RETURN_CONST', 121, 0)] + + self.assertEqual(self.read_code(self.probe_function1), result_1) + self.assertEqual(self.read_code(self.probe_function2), result_2) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/flagstest.cpp b/sources/pyside6/tests/pysidetest/flagstest.cpp new file mode 100644 index 000000000..ac6c3b3b1 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/flagstest.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "flagstest.h" + +namespace FlagsNamespace +{ + ClassForEnum::ClassForEnum(FlagsNamespace::Options) {} + + ClassForEnum::~ClassForEnum() = default; +} diff --git a/sources/pyside6/tests/pysidetest/flagstest.h b/sources/pyside6/tests/pysidetest/flagstest.h new file mode 100644 index 000000000..b5c73c9bd --- /dev/null +++ b/sources/pyside6/tests/pysidetest/flagstest.h @@ -0,0 +1,32 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "pysidetest_macros.h" + +#include <QtCore/QObject> + +namespace FlagsNamespace +{ + +enum Option { + NoOptions = 0x0, + ShowTabs = 0x1, + ShowAll = 0x2, + SqueezeBlank = 0x4 +}; +Q_DECLARE_FLAGS(Options, Option) +Q_DECLARE_OPERATORS_FOR_FLAGS(Options) + +class PYSIDETEST_API ClassForEnum : public QObject +{ + Q_OBJECT +public: + Q_DISABLE_COPY_MOVE(ClassForEnum) + + ClassForEnum(FlagsNamespace::Options opt = FlagsNamespace::Option::NoOptions); + virtual ~ClassForEnum() override; +}; + +} // namespace FlagsNamespace diff --git a/sources/pyside6/tests/pysidetest/hiddenobject.cpp b/sources/pyside6/tests/pysidetest/hiddenobject.cpp new file mode 100644 index 000000000..d4feabb66 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/hiddenobject.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "hiddenobject.h" + +void HiddenObject::callMe() +{ + m_called = true; +} + +bool HiddenObject::wasCalled() const +{ + return m_called; +} + +QObject *getHiddenObject() +{ + return new HiddenObject(); +} diff --git a/sources/pyside6/tests/pysidetest/hiddenobject.h b/sources/pyside6/tests/pysidetest/hiddenobject.h new file mode 100644 index 000000000..f399be985 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/hiddenobject.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HIDDENOBJECT_H +#define HIDDENOBJECT_H + +#include "pysidetest_macros.h" + +#include <QtCore/QObject> + +// This class shouldn't be exported! +class HiddenObject : public QObject +{ + Q_OBJECT +public: + HiddenObject() noexcept = default; + Q_INVOKABLE void callMe(); +public Q_SLOTS: + bool wasCalled() const; +private: + bool m_called = false; +}; + +// Return a instance of HiddenObject +PYSIDETEST_API QObject* getHiddenObject(); + +#endif diff --git a/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py b/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py new file mode 100644 index 000000000..b58232a1b --- /dev/null +++ b/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py @@ -0,0 +1,109 @@ +#!/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 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(True) + +from testbinding import TestObject +from PySide6.QtCore import QObject, Signal, SignalInstance + +'''Tests the behaviour of homonymous signals and slots.''' + + +class HomonymousSignalAndMethodTest(unittest.TestCase): + + def setUp(self): + self.value = 123 + self.called = False + self.obj = TestObject(self.value) + + def tearDown(self): + del self.value + del self.called + del self.obj + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + def testIdValueSignalEmission(self): + def callback(idValue): + self.assertEqual(idValue, self.value) + self.obj.idValue.connect(callback) + self.obj.emitIdValueSignal() + + def testStaticMethodDoubleSignalEmission(self): + def callback(): + self.called = True + self.obj.staticMethodDouble.connect(callback) + self.obj.emitStaticMethodDoubleSignal() + self.assertTrue(self.called) + + def testSignalNotCallable(self): + self.assertRaises(TypeError, self.obj.justASignal) + + def testCallingInstanceMethodWithArguments(self): + self.assertRaises(TypeError, TestObject.idValue, 1) + + def testCallingInstanceMethodWithoutArguments(self): + self.assertRaises(TypeError, TestObject.idValue) + + def testHomonymousSignalAndMethod(self): + self.assertEqual(self.obj.idValue(), self.value) + + def testHomonymousSignalAndStaticMethod(self): + self.assertEqual(TestObject.staticMethodDouble(3), 6) + + def testHomonymousSignalAndStaticMethodFromInstance(self): + self.assertEqual(self.obj.staticMethodDouble(4), 8) + + +# PYSIDE-1730: Homonymous Methods with multiple inheritance + +class Q(QObject): + signal = Signal() + + def method(self): + msg = 'Q::method' + print(msg) + return msg + + +class M: + + def signal(self): + msg = 'M::signal' + print(msg) + return msg + + def method(self): + msg = 'M::method' + print(msg) + return msg + + +class C(M, Q): + + def __init__(self): + Q.__init__(self) + M.__init__(self) + + +class HomonymousMultipleInheritanceTest(unittest.TestCase): + + def testHomonymousMultipleInheritance(self): + c = C() + self.assertEqual(c.method(), "M::method") # okay + self.assertEqual(c.signal(), "M::signal") # problem on PySide6 6.2.2 + self.assertEqual(type(c.signal), SignalInstance) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/iterable_test.py b/sources/pyside6/tests/pysidetest/iterable_test.py new file mode 100644 index 000000000..bdb2ae7be --- /dev/null +++ b/sources/pyside6/tests/pysidetest/iterable_test.py @@ -0,0 +1,68 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +""" +iterable_test.py + +This test checks that the Iterable protocol is implemented correctly. +""" + +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) + +import PySide6 +from PySide6 import QtCore, QtGui + +try: + import numpy as np + have_numpy = True +except ImportError: + have_numpy = False + + +class PySequenceTest(unittest.TestCase): + + def test_iterable(self): + def gen(lis): + for item in lis: + if item == "crash": + raise IndexError + yield item + # testing "pyseq_to_cpplist_conversion" + testfunc = QtCore.QUrl.fromStringList + # use a generator (iterable) + self.assertEqual(testfunc(gen(["asd", "ghj"])), + [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')]) + # use an iterator + self.assertEqual(testfunc(iter(["asd", "ghj"])), + [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')]) + self.assertRaises(IndexError, testfunc, gen(["asd", "crash", "ghj"])) + # testing QMatrix4x4 + testfunc = QtGui.QMatrix4x4 + self.assertEqual(testfunc(gen(range(16))), testfunc(range(16))) + # Note: The errormessage needs to be improved! + # We should better get a ValueError + self.assertRaises((TypeError, ValueError), testfunc, gen(range(15))) + # All other matrix sizes: + testfunc = QtGui.QMatrix2x2 + self.assertEqual(testfunc(gen(range(4))), testfunc(range(4))) + testfunc = QtGui.QMatrix2x3 + self.assertEqual(testfunc(gen(range(6))), testfunc(range(6))) + + @unittest.skipUnless(have_numpy, "requires numpy") + def test_iterable_numpy(self): + # Demo for numpy: We create a unit matrix. + num_mat = np.eye(4) + num_mat.shape = 16 + unit = QtGui.QMatrix4x4(num_mat) + self.assertEqual(unit, QtGui.QMatrix4x4()) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/list_signal_test.py b/sources/pyside6/tests/pysidetest/list_signal_test.py new file mode 100644 index 000000000..da4bc298d --- /dev/null +++ b/sources/pyside6/tests/pysidetest/list_signal_test.py @@ -0,0 +1,34 @@ +# 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 testbinding import TestObject +from PySide6.QtCore import QObject + + +class ListConnectionTest(unittest.TestCase): + + def childrenChanged(self, children): + self._child = children[0] + + def testConnection(self): + o = TestObject(0) + c = QObject() + c.setObjectName("child") + self._child = None + o.childrenChanged.connect(self.childrenChanged) + o.addChild(c) + self.assertEqual(self._child.objectName(), "child") + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py b/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py new file mode 100644 index 000000000..1d536c0ae --- /dev/null +++ b/sources/pyside6/tests/pysidetest/mixin_signal_slots_test.py @@ -0,0 +1,205 @@ +#!/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 + +''' PYSIDE-315: https://bugreports.qt.io/browse/PYSIDE-315 + Test that all signals and slots of a class (including any mixin classes) + are registered at type parsing time. Also test that the signal and slot + indices do not change after signal connection or emission. ''' + +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 Mixin(object): + mixinSignal = Signal() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class MixinTwo(Mixin): + mixinTwoSignal = Signal() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.mixinTwoSlotCalled = False + + @Slot() + def mixinTwoSlot(self): + self.mixinTwoSlotCalled = True + + +class MixinThree(object): + mixinThreeSignal = Signal() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.mixinThreeSlotCalled = False + + @Slot() + def mixinThreeSlot(self): + self.mixinThreeSlotCalled = True + + +class Derived(Mixin, QObject): + derivedSignal = Signal(str) + + def __init__(self): + super().__init__() + self.derivedSlotCalled = False + self.derivedSlotString = '' + + @Slot(str) + def derivedSlot(self, theString): + self.derivedSlotCalled = True + self.derivedSlotString = theString + + +class MultipleDerived(MixinTwo, MixinThree, Mixin, QObject): + derivedSignal = Signal(str) + + def __init__(self): + super().__init__() + self.derivedSlotCalled = False + self.derivedSlotString = '' + + @Slot(str) + def derivedSlot(self, theString): + self.derivedSlotCalled = True + self.derivedSlotString = theString + + +class MixinTest(unittest.TestCase): + def testMixinSignalSlotRegistration(self): + obj = Derived() + m = obj.metaObject() + + # Should contain 2 signals and 1 slot immediately after type parsing + self.assertEqual(m.methodCount() - m.methodOffset(), 3) + + # Save method indices to check that they do not change + methodIndices = {} + for i in range(m.methodOffset(), m.methodCount()): + signature = m.method(i).methodSignature() + methodIndices[signature] = i + + # Check derivedSignal emission + obj.derivedSignal.connect(obj.derivedSlot) + obj.derivedSignal.emit('emit1') + self.assertTrue(obj.derivedSlotCalled) + obj.derivedSlotCalled = False + + # Check derivedSignal emission after mixingSignal connection + self.outsideSlotCalled = False + + @Slot() + def outsideSlot(): + self.outsideSlotCalled = True + + obj.mixinSignal.connect(outsideSlot) + obj.derivedSignal.emit('emit2') + self.assertTrue(obj.derivedSlotCalled) + self.assertFalse(self.outsideSlotCalled) + obj.derivedSlotCalled = False + + # Check mixinSignal emission + obj.mixinSignal.emit() + self.assertTrue(self.outsideSlotCalled) + self.assertFalse(obj.derivedSlotCalled) + self.outsideSlotCalled = False + + # Check that method indices haven't changed. + # Make sure to requery for the meta object, to check that a new one was not + # created as a child of the old one. + m = obj.metaObject() + self.assertEqual(m.methodCount() - m.methodOffset(), 3) + for i in range(m.methodOffset(), m.methodCount()): + signature = m.method(i).methodSignature() + self.assertEqual(methodIndices[signature], i) + + def testMixinSignalSlotRegistrationWithMultipleInheritance(self): + obj = MultipleDerived() + m = obj.metaObject() + + # Should contain 4 signals and 3 slots immediately after type parsing + self.assertEqual(m.methodCount() - m.methodOffset(), 7) + + # Save method indices to check that they do not change + methodIndices = {} + for i in range(m.methodOffset(), m.methodCount()): + signature = m.method(i).methodSignature() + methodIndices[signature] = i + + # Check derivedSignal emission + obj.derivedSignal.connect(obj.derivedSlot) + obj.derivedSignal.emit('emit1') + self.assertTrue(obj.derivedSlotCalled) + self.assertFalse(obj.mixinTwoSlotCalled) + self.assertFalse(obj.mixinThreeSlotCalled) + obj.derivedSlotCalled = False + + # Check derivedSignal emission after mixinThreeSignal connection + self.outsideSlotCalled = False + + @Slot() + def outsideSlot(): + self.outsideSlotCalled = True + + obj.mixinThreeSignal.connect(obj.mixinThreeSlot) + obj.mixinThreeSignal.connect(outsideSlot) + obj.derivedSignal.emit('emit2') + self.assertTrue(obj.derivedSlotCalled) + self.assertFalse(obj.mixinTwoSlotCalled) + self.assertFalse(obj.mixinThreeSlotCalled) + self.assertFalse(self.outsideSlotCalled) + obj.derivedSlotCalled = False + + # Check mixinThreeSignal emission + obj.mixinThreeSignal.emit() + self.assertTrue(self.outsideSlotCalled) + self.assertTrue(obj.mixinThreeSlotCalled) + self.assertFalse(obj.derivedSlotCalled) + self.assertFalse(obj.mixinTwoSlotCalled) + self.outsideSlotCalled = False + obj.mixinThreeSlotCalled = False + + # Check mixinTwoSignal emission + obj.mixinTwoSignal.connect(obj.mixinTwoSlot) + obj.mixinTwoSignal.emit() + self.assertTrue(obj.mixinTwoSlotCalled) + self.assertFalse(obj.mixinThreeSlotCalled) + self.assertFalse(obj.derivedSlotCalled) + self.assertFalse(self.outsideSlotCalled) + obj.mixinTwoSlotCalled = False + + # Check mixinSignal emission + obj.mixinSignal.connect(outsideSlot) + obj.mixinSignal.emit() + self.assertTrue(self.outsideSlotCalled) + self.assertFalse(obj.mixinTwoSlotCalled) + self.assertFalse(obj.mixinThreeSlotCalled) + self.assertFalse(obj.derivedSlotCalled) + self.outsideSlotCalled = False + + # Check that method indices haven't changed. + # Make sure to requery for the meta object, to check that a new one was not + # created as a child of the old one. + m = obj.metaObject() + self.assertEqual(m.methodCount() - m.methodOffset(), 7) + for i in range(m.methodOffset(), m.methodCount()): + signature = m.method(i).methodSignature() + self.assertEqual(methodIndices[signature], i) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/mock_as_slot_test.py b/sources/pyside6/tests/pysidetest/mock_as_slot_test.py new file mode 100644 index 000000000..39f52adea --- /dev/null +++ b/sources/pyside6/tests/pysidetest/mock_as_slot_test.py @@ -0,0 +1,33 @@ +#!/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 + +""" PYSIDE-1755: https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-1755 + Tests that a unittest.mock.MagicMock() can be used as a slot for quick + prototyping. """ + +import os +import sys +import unittest +from unittest.mock import MagicMock + +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 MockAsSlot(unittest.TestCase): + def testMockAsSlot(self): + obj = QObject() + mock = MagicMock() + obj.objectNameChanged.connect(mock) + + obj.objectNameChanged.emit("test") + mock.assert_called_once() + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/modelview_test.py b/sources/pyside6/tests/pysidetest/modelview_test.py new file mode 100644 index 000000000..53231aebe --- /dev/null +++ b/sources/pyside6/tests/pysidetest/modelview_test.py @@ -0,0 +1,85 @@ +#!/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(True) + +from testbinding import TestView +from PySide6.QtCore import QAbstractListModel, QObject, QModelIndex + +'''Tests model/view relationship.''' + +object_name = 'test object' + + +class MyObject(QObject): + pass + + +class ListModelKeepsReference(QAbstractListModel): + def __init__(self, parent=None): + QAbstractListModel.__init__(self, parent) + self.obj = MyObject() + self.obj.setObjectName(object_name) + + def rowCount(self, parent=QModelIndex()): + return 1 + + def data(self, index, role): + return self.obj + + +class ListModelDoesntKeepsReference(QAbstractListModel): + def rowCount(self, parent=QModelIndex()): + return 1 + + def data(self, index, role): + obj = MyObject() + obj.setObjectName(object_name) + return obj + + +class ListModelThatReturnsString(QAbstractListModel): + def rowCount(self, parent=QModelIndex()): + return 1 + + def data(self, index, role): + self.obj = 'string' + return self.obj + + +class ModelViewTest(unittest.TestCase): + + def testListModelDoesntKeepsReference(self): + model = ListModelDoesntKeepsReference() + view = TestView(model) + obj = view.getData() + self.assertEqual(type(obj), MyObject) + self.assertEqual(obj.objectName(), object_name) + obj.metaObject() + + def testListModelKeepsReference(self): + model = ListModelKeepsReference() + view = TestView(model) + obj = view.getData() + self.assertEqual(type(obj), MyObject) + self.assertEqual(obj.objectName(), object_name) + + def testListModelThatReturnsString(self): + model = ListModelThatReturnsString() + view = TestView(model) + obj = view.getData() + self.assertEqual(type(obj), str) + self.assertEqual(obj, 'string') + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py new file mode 100644 index 000000000..49550ba55 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py @@ -0,0 +1,189 @@ +# 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 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 helper.usesqapplication import UsesQApplication +from PySide6 import QtCore, QtGui, QtWidgets +from PySide6.QtWidgets import QMainWindow, QLabel, QWidget + + +def xprint(*args, **kw): + if "-v" in sys.argv: + print(*args, **kw) + + +# This is the original testcase of PYSIDE-1564 +class Age(object): + def __init__(self, age=0, **kwds): + super().__init__(**kwds) + + self.age = age + + +class Person(QtCore.QObject, Age): + def __init__(self, name, **kwds): + super().__init__(**kwds) + + self.name = name + + +class OriginalMultipleInheritanceTest(unittest.TestCase): + + def testIt(self): + xprint() + p = Person("Joe", age=38) + xprint(f"p.age = {p.age}") + # This would crash if MI does not work. + +# More tests follow: + + +# mro ('C', 'A', 'QObject', 'Object', 'B', 'object') +class A(QtCore.QObject): + def __init__(self, anna=77, **kw): + xprint(f'A: before init kw = {kw}') + super().__init__(**kw) + xprint('A: after init') + + +class B: + def __init__(self, otto=6, age=7, **kw): + xprint(f'B: before init kw = {kw}') + if "killme" in kw: + raise AssertionError("asdf") + super().__init__(**kw) + self.age = age + xprint('B: after init') + + +class C(A, B): + def __init__(self, **kw): + xprint(f'C: before init kw = {kw}') + super().__init__(**kw) + xprint('C: after init') + + +# mro ('F', 'D', 'QCursor', 'E', 'QLabel', 'QFrame', 'QWidget', 'QObject', +# 'QPaintDevice', 'Object', 'object') +class D(QtGui.QCursor): + def __init__(self, anna=77, **kw): + xprint(f'D: before init kw = {kw}') + super().__init__(**kw) + xprint('D: after init') + + +class E: + def __init__(self, age=7, **kw): + xprint(f'E: before init kw = {kw}') + super().__init__(**kw) + self.age = age + xprint('E: after init') + + +class F(D, E, QtWidgets.QLabel): + def __init__(self, **kw): + xprint(f'F: before init kw = {kw}') + super().__init__(**kw) + xprint('F: after init') + + +# mro ('I', 'G', 'QTextDocument', 'H', 'QLabel', 'QFrame', 'QWidget', 'QObject', +# 'QPaintDevice', 'Object', 'object') +# Similar, but this time we want to reach `H` without support from `super`. +class G(QtGui.QTextDocument): + pass + + +class H: + def __init__(self, age=7, **kw): + xprint(f'H: before init kw = {kw}') + super().__init__(**kw) + self.age = age + xprint('H: after init') + + +class II(G, H, QtWidgets.QLabel): + pass + + +# PYSIDE-2294: Friedemann's test adapted. +# We need to ignore positional args in mixin classes. +class Ui_X_MainWindow(object): # Emulating uic + def setupUi(self, MainWindow): + MainWindow.resize(400, 300) + self.lbl = QLabel(self) + + +class MainWindow(QMainWindow, Ui_X_MainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + + +class AdditionalMultipleInheritanceTest(UsesQApplication): + + def testABC(self): + xprint() + res = C(otto=3, anna=5) + self.assertEqual(res.age, 7) + xprint() + with self.assertRaises(AssertionError): + res = C(killme=42) + xprint() + + def testDEF(self): + xprint() + res = F(anna=5) + self.assertEqual(res.age, 7) + xprint() + + def testGHI(self): + xprint() + res = II(age=7) + self.assertEqual(res.age, 7) + xprint() + + def testParentDoesNotCrash(self): + # This crashed with + # TypeError: object.__init__() takes exactly one argument (the instance to initialize) + MainWindow() + + +# PYSIDE-2654: Additional missing init test. +# This must work if no __init__ is defined (Ui_Form) +class Ui_Form(object): + pass + + +class Mixin: + def __init__(self, **kwargs) -> None: + super().__init__(**kwargs) + + +class Card(Mixin, QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + +class Demo(Card, Ui_Form): + def __init__(self) -> None: + super().__init__() + + +class MissingInitFunctionTest(UsesQApplication): + def testMissing(self): + Demo() + # Tests if this works. Would crash without the extra + # check for object.__init__ + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/new_inherited_functions_test.py b/sources/pyside6/tests/pysidetest/new_inherited_functions_test.py new file mode 100644 index 000000000..924a2eea7 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/new_inherited_functions_test.py @@ -0,0 +1,159 @@ +# 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 import * +from PySide6.support.signature import get_signature +for modname, mod in sys.modules.items(): + # Python 2 leaves "None" in the dict. + if modname.startswith("PySide6.") and mod is not None: + print("importing", modname) + exec("import " + modname) + +# This test tests the existence and callability of the newly existing functions, +# after the inheritance was made complete in the course of PYSIDE-331. + +new_functions = """ + PySide6.QtCore.QAbstractItemModel().parent() + PySide6.QtCore.QAbstractListModel().parent() + PySide6.QtCore.QAbstractTableModel().parent() + PySide6.QtCore.QFile().resize(qint64) + m = PySide6.QtCore.QMutex(); m.tryLock(); m.unlock() # prevent a message "QMutex: destroying locked mutex" + PySide6.QtCore.QSortFilterProxyModel().parent() + PySide6.QtCore.QTemporaryFile(tfarg).open(openMode) +""" + +new_functions += """ + PySide6.QtGui.QStandardItemModel().insertColumn(int,qModelIndex) + PySide6.QtGui.QStandardItemModel().parent() + # PySide6.QtGui.QTextList(qTextDocument).setFormat(qTextFormat) # Segmentation fault: 11 + # PySide6.QtGui.QTextTable(qTextDocument).setFormat(qTextFormat) # Segmentation fault: 11 +""" if "PySide6.QtGui" in sys.modules else "" + +new_functions += """ + PySide6.QtWidgets.QAbstractItemView().update() + PySide6.QtWidgets.QApplication.palette() + PySide6.QtWidgets.QApplication.setFont(qFont) + PySide6.QtWidgets.QApplication.setPalette(qPalette) + PySide6.QtWidgets.QBoxLayout(direction).addWidget(qWidget) + PySide6.QtWidgets.QColorDialog().open() + PySide6.QtWidgets.QFileDialog().open() + PySide6.QtWidgets.QFileSystemModel().index(int,int,qModelIndex) + PySide6.QtWidgets.QFileSystemModel().parent() + PySide6.QtWidgets.QFontDialog().open() + PySide6.QtWidgets.QGestureEvent([]).accept() + PySide6.QtWidgets.QGestureEvent([]).ignore() + PySide6.QtWidgets.QGestureEvent([]).isAccepted() + PySide6.QtWidgets.QGestureEvent([]).setAccepted(bool) + # PySide6.QtWidgets.QGraphicsView().render(qPaintDevice,qPoint,qRegion,renderFlags) # QPaintDevice: NotImplementedError + PySide6.QtWidgets.QGridLayout().addWidget(qWidget) + PySide6.QtWidgets.QInputDialog().open() + PySide6.QtWidgets.QLineEdit().addAction(qAction) + PySide6.QtWidgets.QMessageBox().open() + PySide6.QtWidgets.QPlainTextEdit().find(findStr) + PySide6.QtWidgets.QProgressDialog().open() + PySide6.QtWidgets.QStackedLayout().widget() + # PySide6.QtWidgets.QStylePainter().begin(qPaintDevice) # QPaintDevice: NotImplementedError + PySide6.QtWidgets.QTextEdit().find(findStr) + PySide6.QtWidgets.QWidget.find(quintptr) +""" if "PySide6.QtWidgets" in sys.modules else "" + +new_functions += """ + # PySide6.QtPrintSupport.QPageSetupDialog().open() # Segmentation fault: 11 + # PySide6.QtPrintSupport.QPrintDialog().open() # opens the dialog, but works + PySide6.QtPrintSupport.QPrintDialog().printer() + PySide6.QtPrintSupport.QPrintPreviewDialog().open() # note: this prints something, but really shouldn't ;-) +""" if "PySide6.QtPrintSupport" in sys.modules else "" + +new_functions += """ + PySide6.QtHelp.QHelpContentModel().parent() + # PySide6.QtHelp.QHelpIndexModel().createIndex(int,int,quintptr) # returned NULL without setting an error + # PySide6.QtHelp.QHelpIndexModel().createIndex(int,int,object()) # returned NULL without setting an error +""" if "PySide6.QtHelp" in sys.modules else "" + +new_functions += """ + PySide6.QtQuick.QQuickPaintedItem().update() +""" if "PySide6.QtQuick" in sys.modules else "" + + +class MainTest(unittest.TestCase): + + def testNewInheriedFunctionsExist(self): + """ + Run all new method signarures + """ + for app in "QtWidgets.QApplication", "QtGui.QGuiApplication", "QtCore.QCoreApplication": + try: + exec(f"qApp = PySide6.{app}([]) or PySide6.{app}.instance()") + break + except AttributeError: + continue + bool = True + int = 42 + qint64 = 42 + tfarg = os.path.join(PySide6.QtCore.QDir.tempPath(), "XXXXXX.tmp") + findStr = 'bla' + orientation = PySide6.QtCore.Qt.Orientations() + openMode = PySide6.QtCore.QIODevice.OpenMode(PySide6.QtCore.QIODevice.ReadOnly) + qModelIndex = PySide6.QtCore.QModelIndex() + transformationMode = PySide6.QtCore.Qt.TransformationMode() + qObject = PySide6.QtCore.QObject() + qPoint = PySide6.QtCore.QPoint() + try: + PySide6.QtGui + #qPaintDevice = PySide6.QtGui.QPaintDevice() # NotImplementedError + qTextDocument = PySide6.QtGui.QTextDocument() + qTextFormat = PySide6.QtGui.QTextFormat() + quintptr = 42 + qFont = PySide6.QtGui.QFont() + qPalette = PySide6.QtGui.QPalette() + except AttributeError: + pass + try: + PySide6.QtWidgets + direction = PySide6.QtWidgets.QBoxLayout.Direction() + qWidget = PySide6.QtWidgets.QWidget() + qStyleOptionFrame = PySide6.QtWidgets.QStyleOptionFrame() + qAction = PySide6.QtGui.QAction(qObject) + renderFlags = PySide6.QtWidgets.QWidget.RenderFlags + except AttributeError: + pass + + for func in new_functions.splitlines(): + func = func.strip() + if func.startswith("#"): + # this is a crashing or otherwise untestable function + print(func) + continue + try: + exec(func) + except NotImplementedError: + print(func, "# raises NotImplementedError") + else: + print(func) + + def testQAppSignatures(self): + """ + Verify that qApp.palette owns three signatures, especially + palette() without argument. + """ + try: + qApp = (PySide6.QtWidgets.QApplication.instance() or + PySide6.QtWidgets.QApplication([])) + except AttributeError: + unittest.TestCase().skipTest("this test makes only sense if QtWidgets is available.") + + sigs = get_signature(PySide6.QtWidgets.QApplication.palette) + self.assertEqual(len(sigs), 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/notify_id.py b/sources/pyside6/tests/pysidetest/notify_id.py new file mode 100644 index 000000000..0c4f39f66 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/notify_id.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 + +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, Property, Slot + +'''Tests that the signal notify id of a property is correct, aka corresponds to the initially set +notify method.''' + + +class Foo(QObject): + def __init__(self): + super().__init__() + self._prop = "Empty" + + def getProp(self): + return self._prop + + def setProp(self, value): + if value != self._prop: + self._prop = value + self.propChanged.emit() + + # Inside the dynamic QMetaObject, the methods have to be sorted, so that this slot comes + # after any signals. That means the property notify id has to be updated, to have the correct + # relative method id. + @Slot() + def randomSlot(): + pass + + propChanged = Signal() + prop = Property(str, getProp, setProp, notify=propChanged) + + +class NotifyIdSignal(unittest.TestCase): + def setUp(self): + self.obj = Foo() + + def tearDown(self): + del self.obj + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + def testSignalEmission(self): + metaObject = self.obj.metaObject() + propertyIndex = metaObject.indexOfProperty("prop") + property = metaObject.property(propertyIndex) + + signalIndex = property.notifySignalIndex() + signal = metaObject.method(signalIndex) + signalName = signal.name() + self.assertEqual(signalName, "propChanged") + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/properties_test.py b/sources/pyside6/tests/pysidetest/properties_test.py new file mode 100644 index 000000000..8eb7812d1 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/properties_test.py @@ -0,0 +1,108 @@ +# 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, QStringListModel, Signal, Property, Slot + +"""Tests PySide6.QtCore.Property()""" + + +class TestObject(QObject): + + valueChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self._value = -1 + self.valueChanged.connect(self._changed) + self.getter_called = 0 + self.setter_called = 0 + self.changed_emitted = 0 + + @Slot(int) + def _changed(self): + self.changed_emitted += 1 + + def getValue(self): + self.getter_called += 1 + return self._value + + def setValue(self, value): + self.setter_called += 1 + if (self._value != value): + self._value = value + self.valueChanged.emit() + + value = Property(int, fget=getValue, fset=setValue, + notify=valueChanged) + + +class TestDerivedObject(QStringListModel): + + valueChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self._value = -1 + self.valueChanged.connect(self._changed) + self.getter_called = 0 + self.setter_called = 0 + self.changed_emitted = 0 + + @Slot(int) + def _changed(self): + self.changed_emitted += 1 + + def getValue(self): + self.getter_called += 1 + return self._value + + def setValue(self, value): + self.setter_called += 1 + if (self._value != value): + self._value = value + self.valueChanged.emit() + + value = Property(int, fget=getValue, fset=setValue, + notify=valueChanged) + + +class PropertyTest(unittest.TestCase): + + def test1Object(self): + """Basic property test.""" + testObject = TestObject() + v = testObject.value + self.assertEqual(v, -1) + self.assertEqual(testObject.getter_called, 1) + testObject.value = 42 + v = testObject.value + self.assertEqual(v, 42) + self.assertEqual(testObject.changed_emitted, 1) + self.assertEqual(testObject.setter_called, 1) + self.assertEqual(testObject.getter_called, 2) + + def test2DerivedObject(self): + """PYSIDE-1255: Run the same test for a class inheriting QObject.""" + testObject = TestDerivedObject() + v = testObject.value + self.assertEqual(v, -1) + self.assertEqual(testObject.getter_called, 1) + testObject.value = 42 + v = testObject.value + self.assertEqual(v, 42) + self.assertEqual(testObject.changed_emitted, 1) + self.assertEqual(testObject.setter_called, 1) + self.assertEqual(testObject.getter_called, 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/property_python_test.py b/sources/pyside6/tests/pysidetest/property_python_test.py new file mode 100644 index 000000000..1209aad4f --- /dev/null +++ b/sources/pyside6/tests/pysidetest/property_python_test.py @@ -0,0 +1,222 @@ +# 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 for PySide's Property +========================== + +This test is copied from Python's `test_property.py` and adapted to +the PySide Property implementation. + +This test is to ensure maximum compatibility. +""" + +# Test case for property +# more tests are in test_descr + +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 Property, QObject + +# This are the original imports. +import sys +import unittest +has_test = False +try: + from test import support + has_test = True +except ImportError: + pass + + +class PropertyBase(Exception): + pass + + +class PropertyGet(PropertyBase): + pass + + +class PropertySet(PropertyBase): + pass + + +class PropertyDel(PropertyBase): + pass + + +class BaseClass(QObject): + def __init__(self): + super().__init__() + + self._spam = 5 + + @Property(object) + def spam(self): + """BaseClass.getter""" + return self._spam + + @spam.setter + def spam(self, value): + self._spam = value + + @spam.deleter + def spam(self): + del self._spam + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + +class SubClass(BaseClass): + + @BaseClass.spam.getter + def spam(self): + """SubClass.getter""" + raise PropertyGet(self._spam) + + @spam.setter + def spam(self, value): + raise PropertySet(self._spam) + + @spam.deleter + def spam(self): + raise PropertyDel(self._spam) + + +class PropertyDocBase(object): + _spam = 1 + + def _get_spam(self): + return self._spam + spam = Property(object, _get_spam, doc="spam spam spam") + + +class PropertyDocSub(PropertyDocBase): + @PropertyDocBase.spam.getter + def spam(self): + """The decorator does not use this doc string""" + return self._spam + + +class PropertySubNewGetter(BaseClass): + @BaseClass.spam.getter + def spam(self): + """new docstring""" + return 5 + + +class PropertyNewGetter(QObject): + def __init__(self): + super().__init__() + + @Property(object) + def spam(self): + """original docstring""" + return 1 + + @spam.getter + def spam(self): + """new docstring""" + return 8 + + +class PropertyTests(unittest.TestCase): + def test_property_decorator_baseclass(self): + # see #1620 + base = BaseClass() + self.assertEqual(base.spam, 5) + self.assertEqual(base._spam, 5) + base.spam = 10 + self.assertEqual(base.spam, 10) + self.assertEqual(base._spam, 10) + delattr(base, "spam") + self.assertTrue(not hasattr(base, "spam")) + self.assertTrue(not hasattr(base, "_spam")) + base.spam = 20 + self.assertEqual(base.spam, 20) + self.assertEqual(base._spam, 20) + + def test_property_decorator_subclass(self): + # see #1620 + sub = SubClass() + self.assertRaises(PropertyGet, getattr, sub, "spam") + self.assertRaises(PropertySet, setattr, sub, "spam", None) + self.assertRaises(PropertyDel, delattr, sub, "spam") + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_decorator_subclass_doc(self): + sub = SubClass() + self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter") + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_decorator_baseclass_doc(self): + base = BaseClass() + self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter") + + def test_property_decorator_doc(self): + base = PropertyDocBase() + sub = PropertyDocSub() + self.assertEqual(base.__class__.spam.__doc__, "spam spam spam") + self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam") + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_getter_doc_override(self): + newgettersub = PropertySubNewGetter() + self.assertEqual(newgettersub.spam, 5) + self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring") + newgetter = PropertyNewGetter() + self.assertEqual(newgetter.spam, 8) + self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring") + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_builtin_doc_writable(self): + p = Property(object, doc='basic') + self.assertEqual(p.__doc__, 'basic') + p.__doc__ = 'extended' + self.assertEqual(p.__doc__, 'extended') + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_property_decorator_doc_writable(self): + class PropertyWritableDoc(object): + + @Property(object) + def spam(self): + """Eggs""" + return "eggs" + + sub = PropertyWritableDoc() + self.assertEqual(sub.__class__.spam.__doc__, 'Eggs') + sub.__class__.spam.__doc__ = 'Spam' + self.assertEqual(sub.__class__.spam.__doc__, 'Spam') + + if has_test: # This test has no support in Python 2 + @support.refcount_test + def test_refleaks_in___init__(self): + gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') + fake_prop = Property(object, 'fget', 'fset', "freset", 'fdel', 'doc') + refs_before = gettotalrefcount() + for i in range(100): + fake_prop.__init__(object, 'fget', 'fset', "freset", 'fdel', 'doc') + self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10) + + +# Note: We ignore the whole subclass tests concerning __doc__ strings. +# See the original Python test starting with: +# "Issue 5890: subclasses of property do not preserve method __doc__ strings" + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py new file mode 100644 index 000000000..625f9cdc5 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py @@ -0,0 +1,136 @@ +#!/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 + +""" +PYSIDE-1735: Testing different relax options for Enums + +This test uses different configurations and initializes QtCore with it. +Because re-initialization is not possible, the test uses a subprocess +for it. This makes the test pretty slow. + +Maybe we should implement a way to re-initialize QtCore enums without +using subprocess, just to speed this up?? +""" + +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) + +import subprocess +import tempfile +from textwrap import dedent + + +def runtest(program): + passed_path = os.fspath(Path(__file__).resolve().parents[1]) + with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".py") as fp: + preamble = dedent(f""" + import os + import sys + from pathlib import Path + sys.path.append({passed_path!r}) + from init_paths import init_test_paths + init_test_paths(False) + """) + print(preamble, program, file=fp) + fp.close() + try: + subprocess.run([sys.executable, fp.name], check=True, capture_output=True) + return True + except subprocess.CalledProcessError as e: + print(f"\ninfo: {e.__class__.__name__}: {e.stderr}") + return False + finally: + os.unlink(fp.name) + + +def testprog2(option): + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + from enum import IntEnum + assert(issubclass(QtCore.Qt.DateFormat, IntEnum)) + """)) + + +def testprog4(option): + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + QtCore.QtDebugMsg + """)) + + +def testprog8_16(option): + # this test needs flag 16, or the effect would be hidden by forgiving mode + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + QtCore.Qt.AlignTop + """)) + + +def testprog32(option): + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + QtCore.Qt.Alignment + """)) + + +def testprog64(option): + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + QtCore.Qt.AlignmentFlag() + """)) + + +def testprog128(option): + return runtest(dedent(f""" + sys.pyside6_option_python_enum = {option} + from PySide6 import QtCore + QtCore.Qt.Key(1234567) + """)) + + +class TestPyEnumRelaxOption(unittest.TestCase): + """ + This test is a bit involved, because we cannot unload QtCore after it is loaded once. + We use subprocess to test different cases, anyway. + """ + + def test_enumIsIntEnum(self): + self.assertTrue(testprog2(2)) + self.assertFalse(testprog2(4)) + + def test_globalDefault(self): + self.assertTrue(testprog4(4)) + self.assertFalse(testprog4(1)) + self.assertTrue(testprog4(12)) + + def test_localDefault(self): + self.assertTrue(testprog8_16(8 + 16)) + self.assertFalse(testprog8_16(0 + 16)) + + def test_fakeRenames(self): + self.assertTrue(testprog32(1)) + self.assertFalse(testprog32(32)) + + def test_zeroDefault(self): + self.assertTrue(testprog64(1)) + self.assertFalse(testprog64(64)) + + def test_Missing(self): + self.assertTrue(testprog128(1)) + self.assertFalse(testprog128(128)) + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/pysidetest.pyproject b/sources/pyside6/tests/pysidetest/pysidetest.pyproject new file mode 100644 index 000000000..032d31c6f --- /dev/null +++ b/sources/pyside6/tests/pysidetest/pysidetest.pyproject @@ -0,0 +1,33 @@ +{ + "files": ["all_modules_load_test.py", + "bug_1016.py", + "constructor_properties_test.py", + "container_test.py", + "decoratedslot_test.py", + "delegatecreateseditor_test.py", + "enum_test.py", + "homonymoussignalandmethod_test.py", + "iterable_test.py", + "list_signal_test.py", + "mixin_signal_slots_test.py", + "mock_as_slot_test.py", + "modelview_test.py", + "new_inherited_functions_test.py", + "notify_id.py", + "properties_test.py", + "property_python_test.py", + "pyenum_relax_options_test.py", + "qapp_like_a_macro_test.py", + "qvariant_test.py", + "repr_test.py", + "shared_pointer_test.py", + "signal_slot_warning.py", + "signal_tp_descr_get_test.py", + "signalandnamespace_test.py", + "signalemissionfrompython_test.py", + "signalinstance_equality_test.py", + "signalwithdefaultvalue_test.py", + "typedef_signal_test.py", + "version_test.py", + "typesystem_pysidetest.xml"] +} diff --git a/sources/pyside6/tests/pysidetest/pysidetest_global.h b/sources/pyside6/tests/pysidetest/pysidetest_global.h new file mode 100644 index 000000000..6f784dc58 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/pysidetest_global.h @@ -0,0 +1,16 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PYSIDETEST_GLOBAL_H +#define PYSIDETEST_GLOBAL_H + +// PySide global.h file +#include "containertest.h" +#include "testobject.h" +#include "testview.h" +#include "flagstest.h" +#include "hiddenobject.h" +#include "sharedpointertestbench.h" +#include "testqvariantenum.h" + +#endif // PYSIDETEST_GLOBAL_H diff --git a/sources/pyside6/tests/pysidetest/pysidetest_macros.h b/sources/pyside6/tests/pysidetest/pysidetest_macros.h new file mode 100644 index 000000000..29bd0dc39 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/pysidetest_macros.h @@ -0,0 +1,18 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PYSIDETEST_MACROS_H +#define PYSIDETEST_MACROS_H + +#include <pysidemacros.h> + +#define PYSIDETEST_EXPORT PYSIDE_EXPORT +#define PYSIDETEST_IMPORT PYSIDE_IMPORT + +#ifdef BUILD_PYSIDETEST +# define PYSIDETEST_API PYSIDETEST_EXPORT +#else +# define PYSIDETEST_API PYSIDETEST_IMPORT +#endif + +#endif // PYSIDETEST_MACROS_H diff --git a/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py b/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py new file mode 100644 index 000000000..2a3f34014 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/qapp_like_a_macro_test.py @@ -0,0 +1,68 @@ +# 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) + +import PySide6 + +# This test tests the new "macro" feature of qApp. +# It also uses the qApp variable to finish the instance and start over. + +# Note: this test makes qapplication_singleton_test.py obsolete. + + +class qAppMacroTest(unittest.TestCase): + _test_1093_is_first = True + + def test_qApp_is_like_a_macro_and_can_restart(self): + self._test_1093_is_first = False + from PySide6 import QtCore + try: + from PySide6 import QtGui, QtWidgets + except ImportError: + QtWidgets = QtGui = QtCore + # qApp is in the builtins + self.assertEqual(bool(qApp), False) + # and the type is None + self.assertTrue(qApp is None) + # now we create an application for all cases + classes = (QtCore.QCoreApplication, + QtGui.QGuiApplication, + QtWidgets.QApplication) + fil = sys.stderr + for klass in classes: + print("CREATED", klass([]), file=fil) + fil.flush() + qApp.shutdown() + print("DELETED qApp", qApp, file=fil) + fil.flush() + # creating without deletion raises: + QtCore.QCoreApplication([]) + with self.assertRaises(RuntimeError): + QtCore.QCoreApplication([]) + self.assertEqual(QtCore.QCoreApplication.instance(), qApp) + + def test_1093(self): + # Test that without creating a QApplication staticMetaObject still exists. + # Please see https://bugreports.qt.io/browse/PYSIDE-1093 for explanation. + # Note: This test must run first, otherwise we would be mislead! + assert self._test_1093_is_first + from PySide6 import QtCore + self.assertTrue(QtCore.QObject.staticMetaObject is not None) + app = QtCore.QCoreApplication.instance() + self.assertTrue(QtCore.QObject.staticMetaObject is not None) + if app is None: + app = QtCore.QCoreApplication([]) + self.assertTrue(QtCore.QObject.staticMetaObject is not None) + qApp.shutdown() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/qvariant_test.py b/sources/pyside6/tests/pysidetest/qvariant_test.py new file mode 100644 index 000000000..faefc8169 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/qvariant_test.py @@ -0,0 +1,67 @@ +# 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 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(True) + +from testbinding import TestObject, TestQVariantEnum +from PySide6.QtCore import Qt, QKeyCombination +from PySide6.QtGui import QKeySequence, QAction + +from helper.usesqapplication import UsesQApplication + + +class PyTestQVariantEnum(TestQVariantEnum): + def __init__(self, var_enum): + super().__init__(var_enum) + + def getRValEnum(self): + return Qt.Orientation.Vertical + + def channelingEnum(self, rval_enum): + return (isinstance(rval_enum, enum.Enum) + and rval_enum == Qt.Orientation.Vertical) + + +class QVariantTest(UsesQApplication): + + def testQKeySequenceQVariantOperator(self): + # bug #775 + ks = QKeySequence(Qt.ShiftModifier, Qt.ControlModifier, Qt.Key_P, Qt.Key_R) + self.assertEqual(TestObject.checkType(ks), 4107) + + def testQKeySequenceMoreVariations(self): + QAction().setShortcut(Qt.CTRL | Qt.Key_B) + QAction().setShortcut(Qt.CTRL | Qt.ALT | Qt.Key_B) + QAction().setShortcut(Qt.CTRL | Qt.AltModifier | Qt.Key_B) + QAction().setShortcut(QKeySequence(QKeyCombination(Qt.CTRL | Qt.Key_B))) + QKeySequence(Qt.CTRL | Qt.Key_Q) + + def testEnum(self): + # Testing C++ class + testqvariant = TestQVariantEnum(Qt.CheckState.Checked) + self.assertEqual(testqvariant.getLValEnum(), Qt.CheckState.Checked) + self.assertIsInstance(testqvariant.getLValEnum(), enum.Enum) + # in the case where we return a QVariant of C++ enum, it returns a + # QVariant(int) to Python unless explicitly handled manually by Shiboken + self.assertEqual(testqvariant.getRValEnum(), 1) + self.assertEqual(testqvariant.isEnumChanneled(), False) + + # Testing Python child class + pytestqvariant = PyTestQVariantEnum(Qt.CheckState.Checked) + self.assertEqual(pytestqvariant.isEnumChanneled(), True) + # check toInt() conversion works for PyObjectWrapper + self.assertEqual(PyTestQVariantEnum.getNumberFromQVarEnum(Qt.Orientation.Vertical), 2) + # check toInt() conversion for IntEnum + self.assertEqual(PyTestQVariantEnum.getNumberFromQVarEnum(Qt.GestureType.TapGesture), 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/repr_test.py b/sources/pyside6/tests/pysidetest/repr_test.py new file mode 100644 index 000000000..863f17657 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/repr_test.py @@ -0,0 +1,64 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# Copyright (C) 2019 Andreas Beckermann +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +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 testbinding import PySideCPP, TestObject + + +class QObjectDerivedReprTest(unittest.TestCase): + """Test the __repr__ implementation of QObject derived classes""" + + def testReprWithoutNamespace(self): + """Test that classes outside a namespace that have a operator<<(QDebug,...) defined use that + for __repr__""" + t = TestObject(123) + + # We don't define __str__, so str(q) should call __repr__ + self.assertEqual(t.__repr__(), str(t)) + + # __repr__ should use the operator<<(QDebug,...) implementation + self.assertIn('TestObject(id=123)', str(t)) + + def testReprWithNamespace(self): + """Test that classes inside a namespace that have a operator<<(QDebug,...) defined use that + for __repr__""" + t = PySideCPP.TestObjectWithNamespace(None) + + # We don't define __str__, so str(q) should call __repr__ + self.assertEqual(t.__repr__(), str(t)) + + # __repr__ should use the operator<<(QDebug,...) implementation + self.assertIn('TestObjectWithNamespace("TestObjectWithNamespace")', str(t)) + + def testReprInject(self): + """Test that injecting __repr__ via typesystem overrides the operator<<(QDebug, ...)""" + t = PySideCPP.TestObject2WithNamespace(None) + + # We don't define __str__, so str(q) should call __repr__ + self.assertEqual(t.__repr__(), str(t)) + + # __repr__ should use the operator<<(QDebug,...) implementation + self.assertEqual(str(t), "TestObject2WithNamespace(injected_repr)") + + def testLatin1StringField(self): + self.assertEqual(TestObject.LATIN1_TEST_FIELD, "test") + + def testLatin1Setter(self): + to = TestObject(123) + value = "test" + to.setQLatin1String(value) + self.assertEqual(to.qLatin1String(), value) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/shared_pointer_test.py b/sources/pyside6/tests/pysidetest/shared_pointer_test.py new file mode 100644 index 000000000..6f49d69b1 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/shared_pointer_test.py @@ -0,0 +1,54 @@ +# 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(True) + +from PySide6.QtCore import QObject + +from testbinding import SharedPointerTestbench, QSharedPointer_QObject + + +def create_qobject(name): + result = QObject() + result.setObjectName(name) + return result + + +class SharedPointerTests(unittest.TestCase): + + def testObjSharedPointer(self): + p = SharedPointerTestbench.createSharedPointerQObject() + self.assertEqual(p.objectName(), "TestObject") + SharedPointerTestbench.printSharedPointerQObject(p) + p = SharedPointerTestbench.createSharedPointerConstQObject() + SharedPointerTestbench.printSharedPointerConstQObject(p) + + def testIntSharedPointer(self): + p = SharedPointerTestbench.createSharedPointerInt(42) + SharedPointerTestbench.printSharedPointerInt(p) + + def testConstruction(self): + name1 = "CreatedQObject1" + p1 = QSharedPointer_QObject(create_qobject(name1)) + self.assertTrue(p1) + self.assertEqual(p1.objectName(), name1) + + p1.reset() + self.assertFalse(p1) + + name2 = "CreatedQObject2" + p1.reset(create_qobject(name2)) + self.assertTrue(p1) + self.assertEqual(p1.objectName(), name2) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/sharedpointertestbench.cpp b/sources/pyside6/tests/pysidetest/sharedpointertestbench.cpp new file mode 100644 index 000000000..44c2a4fe0 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/sharedpointertestbench.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sharedpointertestbench.h" + +#include <QtCore/QObject> +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +SharedPointerTestbench::SharedPointerTestbench() = default; + +QSharedPointer<int> SharedPointerTestbench::createSharedPointerInt(int v) +{ + return QSharedPointer<int>(new int(v)); +} + +void SharedPointerTestbench::printSharedPointerInt(const QSharedPointer<int> &p) +{ + qDebug() << __FUNCTION__ << *p; +} + +QSharedPointer<QObject> SharedPointerTestbench::createSharedPointerQObject() +{ + QSharedPointer<QObject> result(new QObject); + result->setObjectName(u"TestObject"_s); + return result; +} + +void SharedPointerTestbench::printSharedPointerQObject(const QSharedPointer<QObject> &p) +{ + qDebug() << __FUNCTION__ << p.data(); +} + +QSharedPointer<const QObject> SharedPointerTestbench::createSharedPointerConstQObject() +{ + auto *o = new QObject; + o->setObjectName(u"ConstTestObject"_s); + QSharedPointer<const QObject> result(o); + return result; +} + +void SharedPointerTestbench::printSharedPointerConstQObject(const QSharedPointer<const QObject> &p) +{ + qDebug() << __FUNCTION__ << p.data(); +} diff --git a/sources/pyside6/tests/pysidetest/sharedpointertestbench.h b/sources/pyside6/tests/pysidetest/sharedpointertestbench.h new file mode 100644 index 000000000..b23fd1b6c --- /dev/null +++ b/sources/pyside6/tests/pysidetest/sharedpointertestbench.h @@ -0,0 +1,29 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SHAREDPOINTERTESTBENCH_H +#define SHAREDPOINTERTESTBENCH_H + +#include "pysidetest_macros.h" + +#include <QtCore/QSharedPointer> + +QT_FORWARD_DECLARE_CLASS(QObject) + +class PYSIDETEST_API SharedPointerTestbench +{ +public: + SharedPointerTestbench(); + + static QSharedPointer<int> createSharedPointerInt(int v); + static void printSharedPointerInt(const QSharedPointer<int> &p); + + static QSharedPointer<QObject> createSharedPointerQObject(); + static void printSharedPointerQObject(const QSharedPointer<QObject> &p); + + static QSharedPointer<const QObject> createSharedPointerConstQObject(); + static void printSharedPointerConstQObject(const QSharedPointer<const QObject> &p); + +}; + +#endif // SHAREDPOINTERTESTBENCH_H diff --git a/sources/pyside6/tests/pysidetest/signal_slot_warning.py b/sources/pyside6/tests/pysidetest/signal_slot_warning.py new file mode 100644 index 000000000..b94281643 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signal_slot_warning.py @@ -0,0 +1,50 @@ +#!/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 + +''' PYSIDE-315: https://bugreports.qt.io/browse/PYSIDE-315 + Test that creating a signal in the wrong order triggers a warning. ''' + +import os +import sys +import unittest +import warnings + +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) + +import PySide6.QtCore as QtCore + + +class Whatever(QtCore.QObject): + echoSignal = QtCore.Signal(str) + + def __init__(self): + super().__init__() + self.echoSignal.connect(self.mySlot) + + def mySlot(self, v): + pass + + +class WarningTest(unittest.TestCase): + def testSignalSlotWarning(self): + # we create an object. This gives no warning. + obj = Whatever() + # then we insert a signal after slots have been created. + setattr(Whatever, "foo", QtCore.Signal()) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + obj.foo.connect(obj.mySlot) + # Verify some things + assert issubclass(w[-1].category, RuntimeWarning) + assert "*** Sort Warning ***" in str(w[-1].message) + # note that this warning cannot be turned into an error (too hard) + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py b/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py new file mode 100644 index 000000000..6025d119d --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signal_tp_descr_get_test.py @@ -0,0 +1,60 @@ +#!/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 + +""" +PYSIDE-68: Test that signals have a `__get__` function after all. + +We supply a `tp_descr_get` slot for the signal type. +That creates the `__get__` method via `PyType_Ready`. + +The original test script was converted to a unittest. +See https://bugreports.qt.io/browse/PYSIDE-68 . + +Created: 16 May '12 21:25 +Updated: 17 Sep '20 17:02 + +This fix was over 8 years late. :) +""" + +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 + + +def emit_upon_success(signal): + def f_(f): + def f__(self): + result = f(self) + s = signal.__get__(self) + print(result) + return result + return f__ + return f_ + + +class Foo(QObject): + SIG = Signal() + + @emit_upon_success(SIG) + def do_something(self): + print("hooka, it worrrks") + return 42 + + +class UnderUnderGetUnderUnderTest(unittest.TestCase): + def test_tp_descr_get(self): + foo = Foo() + ret = foo.do_something() + self.assertEqual(ret, 42) + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/signalandnamespace_test.py b/sources/pyside6/tests/pysidetest/signalandnamespace_test.py new file mode 100644 index 000000000..3e91ca338 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signalandnamespace_test.py @@ -0,0 +1,103 @@ +#!/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(True) + +from testbinding import PySideCPP, TestObjectWithoutNamespace + + +class ModelViewTest(unittest.TestCase): + + def callback(self, o): + self._called = o + + def testWithoutNamespace(self): + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignal.connect(self.callback) + o.emitSignal.emit(o) + self.assertTrue(o == self._called) + + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignal.connect(self.callback) + o.callSignal(o) + self.assertTrue(o == self._called) + + def testWithNamespace(self): + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignalWithNamespace.connect(self.callback) + o.emitSignalWithNamespace.emit(o) + self.assertTrue(o == self._called) + + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignalWithNamespace.connect(self.callback) + o.callSignalWithNamespace(o) + self.assertTrue(o == self._called) + + def testWithoutNamespace1(self): + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignal.connect(self.callback) + o.emitSignal.emit(o) + self.assertTrue(o == self._called) + + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignal.connect(self.callback) + o.callSignal(o) + self.assertTrue(o == self._called) + + def testWithNamespace1(self): + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignalWithNamespace.connect(self.callback) + o.emitSignalWithNamespace.emit(o) + self.assertTrue(o == self._called) + + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignalWithNamespace.connect(self.callback) + o.callSignalWithNamespace(o) + self.assertTrue(o == self._called) + + def testTypedfWithouNamespace(self): + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignalWithTypedef.connect(self.callback) + o.emitSignalWithTypedef.emit(10) + self.assertEqual(10, self._called) + + self._called = None + o = PySideCPP.TestObjectWithNamespace(None) + o.emitSignalWithTypedef.connect(self.callback) + o.callSignalWithTypedef(10) + self.assertEqual(10, self._called) + + def testTypedefWithNamespace(self): + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignalWithTypedef.connect(self.callback) + o.emitSignalWithTypedef.emit(10) + self.assertEqual(10, self._called) + + self._called = None + o = TestObjectWithoutNamespace(None) + o.emitSignalWithTypedef.connect(self.callback) + o.callSignalWithTypedef(10) + self.assertEqual(10, self._called) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/signalemissionfrompython_test.py b/sources/pyside6/tests/pysidetest/signalemissionfrompython_test.py new file mode 100644 index 000000000..70c9e0082 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signalemissionfrompython_test.py @@ -0,0 +1,96 @@ +#!/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 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(True) + +from testbinding import TestObject +from PySide6.QtCore import QObject, SIGNAL + +'''Tests the behaviour of signals with default values when emitted from Python.''' + + +class SignalEmissionFromPython(unittest.TestCase): + + def setUp(self): + self.obj1 = TestObject(0) + self.obj2 = TestObject(0) + self.one_called = 0 + self.two_called = 0 + + def tearDown(self): + del self.obj1 + del self.obj2 + del self.one_called + del self.two_called + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + def testConnectNewStyleEmitVoidSignal(self): + def callbackOne(): + self.one_called += 1 + self.obj2.signalWithDefaultValue.emit() + + def callbackTwo(): + self.two_called += 1 + self.obj1.signalWithDefaultValue.connect(callbackOne) + self.obj2.signalWithDefaultValue.connect(callbackTwo) + self.obj1.emitSignalWithDefaultValue_void() + self.obj2.emitSignalWithDefaultValue_void() + self.assertEqual(self.one_called, 1) + self.assertEqual(self.two_called, 2) + + def testConnectOldStyleEmitVoidSignal(self): + def callbackOne(): + self.one_called += 1 + self.obj2.signalWithDefaultValue.emit() + + def callbackTwo(): + self.two_called += 1 + self.obj1.signalWithDefaultValue.connect(callbackOne) + self.obj2.signalWithDefaultValue.connect(callbackTwo) + self.obj1.emitSignalWithDefaultValue_void() + self.obj2.emitSignalWithDefaultValue_void() + self.assertEqual(self.one_called, 1) + self.assertEqual(self.two_called, 2) + + def testConnectNewStyleEmitBoolSignal(self): + def callbackOne(): + self.one_called += 1 + self.obj2.signalWithDefaultValue[bool].emit(True) + + def callbackTwo(): + self.two_called += 1 + self.obj1.signalWithDefaultValue.connect(callbackOne) + self.obj2.signalWithDefaultValue.connect(callbackTwo) + self.obj1.emitSignalWithDefaultValue_void() + self.obj2.emitSignalWithDefaultValue_void() + self.assertEqual(self.one_called, 1) + self.assertEqual(self.two_called, 2) + + def testConnectOldStyleEmitBoolSignal(self): + def callbackOne(): + self.one_called += 1 + self.obj2.signalWithDefaultValue[bool].emit(True) + + def callbackTwo(): + self.two_called += 1 + self.obj1.signalWithDefaultValue.connect(callbackOne) + self.obj2.signalWithDefaultValue.connect(callbackTwo) + self.obj1.emitSignalWithDefaultValue_void() + self.obj2.emitSignalWithDefaultValue_void() + self.assertEqual(self.one_called, 1) + self.assertEqual(self.two_called, 2) + + +if __name__ == '__main__': + unittest.main() + diff --git a/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py b/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py new file mode 100644 index 000000000..5faaa38d4 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signalinstance_equality_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 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 helper.usesqapplication import UsesQApplication + +from PySide6.QtCore import QFile, QObject, QTimer, Signal, SignalInstance, Slot +from PySide6.QtWidgets import QSlider + + +class C(QObject): + custom_signal = Signal() + + +class D(C): + pass + + +class TestSignalInstance(unittest.TestCase): + def test_signal_instances_are_equal(self): + o = QTimer() + self.assertTrue(o.timeout == o.timeout) + + def test_inherited_signal_instances_are_equal(self): + o = QFile() + self.assertTrue(o.readyRead == o.readyRead) + + def test_custom_signal_instances_are_equal(self): + o = C() + self.assertTrue(o.custom_signal == o.custom_signal) + + def test_custom_inherited_signal_instances_are_equal(self): + o = D() + self.assertTrue(o.custom_signal == o.custom_signal) + + # additional tests of old errors from 2010 or so + def test_uninitialized_SignalInstance(self): + # This will no longer crash + print(SignalInstance()) + with self.assertRaises(RuntimeError): + SignalInstance().connect(lambda: None) + with self.assertRaises(RuntimeError): + SignalInstance().disconnect() + with self.assertRaises(RuntimeError): + SignalInstance().emit() + + +class MyWidget(QSlider): + valueChanged = Signal(tuple) + + def __init__(self): + super().__init__() + self.valueChanged.connect(self._on_change) + + def setValue(self, value): + self.valueChanged.emit(value) + + @Slot() + def _on_change(self, new_value): + print("new_value:", new_value) + global result + result = new_value + + +class TestRightOrder(UsesQApplication): + def test_rightOrder(self): + wdg = MyWidget() + + # PYSIDE-1751: Fixes the wrong behavior we got on >=6.2 + # PySide <=6.1.3 prints "new_value: (30, 40)" + # PySide >=6.2 prints "new_value: 0" + wdg.setValue((30, 40)) + self.assertEqual(result, (30, 40)) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py b/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py new file mode 100644 index 000000000..744b8c503 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/signalwithdefaultvalue_test.py @@ -0,0 +1,96 @@ +#!/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 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(True) + +from testbinding import TestObject +from PySide6.QtCore import Qt + +'''Tests the behaviour of signals with default values.''' + + +class SignalWithDefaultValueTest(unittest.TestCase): + + def setUp(self): + self.obj = TestObject(0) + self.void_called = False + self.bool_called = False + + def tearDown(self): + del self.obj + del self.void_called + del self.bool_called + # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion + gc.collect() + + def testConnectNewStyleEmitVoidSignal(self): + def callbackVoid(): + self.void_called = True + + def callbackBool(value): + self.bool_called = True + self.obj.signalWithDefaultValue.connect(callbackVoid) + self.obj.signalWithDefaultValue[bool].connect(callbackBool) + self.obj.emitSignalWithDefaultValue_void() + self.assertTrue(self.void_called) + self.assertTrue(self.bool_called) + + def testConnectNewStyleEmitBoolSignal(self): + def callbackVoid(): + self.void_called = True + + def callbackBool(value): + self.bool_called = True + self.obj.signalWithDefaultValue.connect(callbackVoid) + self.obj.signalWithDefaultValue[bool].connect(callbackBool) + self.obj.emitSignalWithDefaultValue_bool() + self.assertTrue(self.void_called) + self.assertTrue(self.bool_called) + + def testFlagsSignal(self): + test_value = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom + + def callbackAlignmentFlags(alignment): + self.alignment_flags_called = alignment + + self.obj.flagsSignal.connect(callbackAlignmentFlags) + self.obj.emitFlagsSignal(test_value) + self.assertTrue(self.alignment_flags_called) + self.assertEqual(self.alignment_flags_called, test_value) + + def testConnectOldStyleEmitVoidSignal(self): + def callbackVoid(): + self.void_called = True + + def callbackBool(value): + self.bool_called = True + self.obj.signalWithDefaultValue.connect(callbackVoid) + self.obj.signalWithDefaultValue.connect(callbackBool) + self.obj.emitSignalWithDefaultValue_void() + self.assertTrue(self.void_called) + self.assertTrue(self.bool_called) + + def testConnectOldStyleEmitBoolSignal(self): + def callbackVoid(): + self.void_called = True + + def callbackBool(value): + self.bool_called = True + self.obj.signalWithDefaultValue.connect(callbackVoid) + self.obj.signalWithDefaultValue.connect(callbackBool) + self.obj.emitSignalWithDefaultValue_bool() + self.assertTrue(self.void_called) + self.assertTrue(self.bool_called) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_sub.py new file mode 100644 index 000000000..4a482c35a --- /dev/null +++ b/sources/pyside6/tests/pysidetest/snake_case_sub.py @@ -0,0 +1,23 @@ +# 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) + +""" +PYSIDE-2029: Tests that snake_case is isolated from imported modules +""" + +from PySide6.QtWidgets import QWidget + + +def test_no_snake_case(): + print(__name__) + widget = QWidget() + check = widget.sizeHint diff --git a/sources/pyside6/tests/pysidetest/snake_case_test.py b/sources/pyside6/tests/pysidetest/snake_case_test.py new file mode 100644 index 000000000..14e035773 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/snake_case_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) + +""" +PYSIDE-2029: Tests that snake_case is isolated from imported modules +""" +is_pypy = hasattr(sys, "pypy_version_info") + +from PySide6.QtCore import QSize +from PySide6.QtWidgets import QWidget, QSpinBox +if not is_pypy: + from __feature__ import snake_case +from helper.usesqapplication import UsesQApplication + +import snake_case_sub + +@unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") +class SnakeCaseNoPropagateTest(UsesQApplication): + + def testSnakeCase(self): + # this worked + widget = QWidget() + check = widget.size_hint + + snake_case_sub.test_no_snake_case() + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/symbols.filter b/sources/pyside6/tests/pysidetest/symbols.filter new file mode 100644 index 000000000..af6c744dd --- /dev/null +++ b/sources/pyside6/tests/pysidetest/symbols.filter @@ -0,0 +1,7 @@ +{ +local: +_ZSt*; +_ZNSt*; +_ZNSs*; +_ZNKSt*; +}; diff --git a/sources/pyside6/tests/pysidetest/testobject.cpp b/sources/pyside6/tests/pysidetest/testobject.cpp new file mode 100644 index 000000000..fe4ec98f7 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testobject.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testobject.h" + +#include <QtCore/QDebug> + +void TestObject::emitIdValueSignal() +{ + emit idValue(m_idValue); +} + +void TestObject::emitStaticMethodDoubleSignal() +{ + emit staticMethodDouble(); +} + +void TestObject::emitSignalWithDefaultValue_void() +{ + emit signalWithDefaultValue(); +} + +void TestObject::emitSignalWithDefaultValue_bool() +{ + emit signalWithDefaultValue(true); +} + +void TestObject::emitSignalWithTypedefValue(int value) +{ + emit signalWithTypedefValue(TypedefValue(value)); +} + +void TestObject::emitSignalWithContainerTypedefValue(const IntList &il) +{ + emit signalWithContainerTypedefValue(il); +} + +void TestObject::emitFlagsSignal(Qt::Alignment alignment) +{ + emit flagsSignal(alignment); +} + +void TestObject::setQLatin1String(QLatin1String v) +{ + m_qLatin1String = v; +} + +QString TestObject::qLatin1String() const +{ + return m_qLatin1String; +} + +QDebug operator<<(QDebug dbg, TestObject& testObject) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << "TestObject(id=" << testObject.idValue() << ") "; + return dbg; +} + +namespace PySideCPP { + QDebug operator<<(QDebug dbg, TestObjectWithNamespace& testObject) + { + QDebugStateSaver saver(dbg); + dbg.nospace() << "TestObjectWithNamespace(" << testObject.name() << ") "; + return dbg; + } + QDebug operator<<(QDebug dbg, TestObject2WithNamespace& testObject) + { + QDebugStateSaver saver(dbg); + dbg.nospace() << "TestObject2WithNamespace(" << testObject.name() << ") "; + return dbg; + } +} diff --git a/sources/pyside6/tests/pysidetest/testobject.h b/sources/pyside6/tests/pysidetest/testobject.h new file mode 100644 index 000000000..a095a382e --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testobject.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTOBJECT_H +#define TESTOBJECT_H + +#include "pysidetest_macros.h" + +#include <QtWidgets/QApplication> + +#include <QtCore/QList> +#include <QtCore/QObject> +#include <QtCore/QMetaType> +#include <QtCore/QVariant> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +using IntList = QList<int>; + +class IntValue +{ +public: + + IntValue(int val): value(val){}; + IntValue() : value(0) {}; + int value; +}; + +using TypedefValue = IntValue; + +class PYSIDETEST_API TestObject : public QObject +{ + Q_OBJECT +public: + static void createApp() { int argc=0; new QApplication(argc, nullptr); }; + static int checkType(const QVariant& var) { return var.metaType().id(); } + + TestObject(int idValue, QObject* parent = nullptr) : QObject(parent), m_idValue(idValue) {} + int idValue() const { return m_idValue; } + static int staticMethodDouble(int value) { return value * 2; } + void addChild(QObject* c) { m_children.append(c); emit childrenChanged(m_children); } + + void emitIdValueSignal(); + void emitStaticMethodDoubleSignal(); + + void emitSignalWithDefaultValue_void(); + void emitSignalWithDefaultValue_bool(); + + void emitSignalWithTypedefValue(int value); + void emitSignalWithContainerTypedefValue(const IntList &il); + + void emitFlagsSignal(Qt::Alignment alignment); + + static constexpr auto LATIN1_TEST_FIELD = QLatin1StringView("test"); + + void setQLatin1String(QLatin1String v); + QString qLatin1String() const; + +signals: + void idValue(int newValue); + void justASignal(); + void staticMethodDouble(); + void childrenChanged(const QList<QObject*>&); + void signalWithDefaultValue(bool value = false); + void signalWithTypedefValue(TypedefValue value); + void signalWithContainerTypedefValue(const IntList &il); + void flagsSignal(Qt::Alignment alignment); + +private: + int m_idValue; + QList<QObject*> m_children; + QString m_qLatin1String; +}; + +PYSIDETEST_API QDebug operator<<(QDebug dbg, TestObject &testObject); + +using PySideInt = int; + +namespace PySideCPP { + +class PYSIDETEST_API TestObjectWithNamespace : public QObject +{ + Q_OBJECT +public: + TestObjectWithNamespace(QObject* parent) : QObject(parent) {} + QString name() { return QStringLiteral("TestObjectWithNamespace"); } + + void callSignal(TestObjectWithNamespace* obj) { emit emitSignal(obj); } + void callSignalWithNamespace(TestObjectWithNamespace* obj) { emit emitSignalWithNamespace(obj); } + void callSignalWithTypedef(int val) { emit emitSignalWithTypedef(val); } + +signals: + void emitSignal(TestObjectWithNamespace* obj); + void emitSignalWithNamespace(PySideCPP::TestObjectWithNamespace* obj); + void emitSignalWithTypedef(PySideInt val); +}; + +PYSIDETEST_API QDebug operator<<(QDebug dbg, TestObjectWithNamespace &testObject); + +class PYSIDETEST_API TestObject2WithNamespace : public QObject +{ + Q_OBJECT +public: + TestObject2WithNamespace(QObject* parent) : QObject(parent) {} + QString name() { return QStringLiteral("TestObject2WithNamespace"); } +}; + +PYSIDETEST_API QDebug operator<<(QDebug dbg, TestObject2WithNamespace& testObject); + +} // Namespace PySideCPP + +namespace PySideCPP2 { + +enum Enum1 { Option1 = 1, Option2 = 2 }; + +using PySideLong = long; + +class PYSIDETEST_API TestObjectWithoutNamespace : public QObject +{ + Q_OBJECT +public: + enum Enum2 { Option3 = 3, Option4 = 4}; + TestObjectWithoutNamespace(QObject* parent) : QObject(parent) {} + QString name() { return QStringLiteral("TestObjectWithoutNamespace"); } + + void callSignal(TestObjectWithoutNamespace* obj) { emitSignal(obj); } + void callSignalWithNamespace(TestObjectWithoutNamespace* obj) { emitSignalWithNamespace(obj); } + void callSignalWithTypedef(long val) { emitSignalWithTypedef(val); } + +signals: + void emitSignal(TestObjectWithoutNamespace* obj); + void emitSignalWithNamespace(PySideCPP2::TestObjectWithoutNamespace* obj); + void emitSignalWithTypedef(PySideLong val); +}; + + +} // Namespace PySideCPP2 + +#endif // TESTOBJECT_H + diff --git a/sources/pyside6/tests/pysidetest/testqvariantenum.cpp b/sources/pyside6/tests/pysidetest/testqvariantenum.cpp new file mode 100644 index 000000000..7135e422a --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testqvariantenum.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testqvariantenum.h" + +QVariant TestQVariantEnum::getLValEnum() const +{ + return this->m_enum; +} + +QVariant TestQVariantEnum::getRValEnum() const +{ + return QVariant(Qt::Orientation::Horizontal); +} + +int TestQVariantEnum::getNumberFromQVarEnum(QVariant variantEnum) +{ + return variantEnum.toInt(); +} + +bool TestQVariantEnum::channelingEnum([[maybe_unused]] QVariant rvalEnum) const +{ + return false; +} + +bool TestQVariantEnum::isEnumChanneled() const +{ + return this->channelingEnum(this->getRValEnum()); +} diff --git a/sources/pyside6/tests/pysidetest/testqvariantenum.h b/sources/pyside6/tests/pysidetest/testqvariantenum.h new file mode 100644 index 000000000..4b729e3dd --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testqvariantenum.h @@ -0,0 +1,35 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTQVARIANT_H +#define TESTQVARIANT_H + +#include "pysidetest_macros.h" + +#include <QtCore/QVariant> + +class PYSIDETEST_API TestQVariantEnum +{ +public: + TestQVariantEnum(QVariant lvalue_enum) : m_enum(lvalue_enum) {} + QVariant getLValEnum() const; + static int getNumberFromQVarEnum(QVariant variantEnum = QVariant()); + bool isEnumChanneled() const; + virtual QVariant getRValEnum() const; + virtual bool channelingEnum(QVariant rvalEnum) const; + virtual ~TestQVariantEnum() = default; +private: + QVariant m_enum; +}; + +class PYSIDETEST_API QVariantHolder // modeled after Q3DParameter, test QVariant conversion +{ +public: + void setValue(QVariant v) { m_variant = v; } + QVariant value() const { return m_variant; } + +private: + QVariant m_variant; +}; + +#endif // TESTQVARIANT_H diff --git a/sources/pyside6/tests/pysidetest/testview.cpp b/sources/pyside6/tests/pysidetest/testview.cpp new file mode 100644 index 000000000..362239112 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testview.cpp @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testview.h" + +#include <QtCore/QDebug> +#include <QtCore/QAbstractListModel> +#include <QtWidgets/QAbstractItemDelegate> +#include <QtWidgets/QWidget> + +QVariant +TestView::getData() +{ + QModelIndex index; + return m_model->data(index); +} + +QWidget* +TestView::getEditorWidgetFromItemDelegate() const +{ + if (m_delegate == nullptr) + return nullptr; + + QModelIndex index; + QStyleOptionViewItem options; + return m_delegate->createEditor(nullptr, options, index); +} diff --git a/sources/pyside6/tests/pysidetest/testview.h b/sources/pyside6/tests/pysidetest/testview.h new file mode 100644 index 000000000..746def83e --- /dev/null +++ b/sources/pyside6/tests/pysidetest/testview.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTVIEW_H +#define TESTVIEW_H + +#include "pysidetest_macros.h" + +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE +class QWidget; +class QAbstractListModel; +class QAbstractItemDelegate; +QT_END_NAMESPACE + +class PYSIDETEST_API TestView : public QObject +{ + Q_OBJECT +public: + TestView(QAbstractListModel* model, QObject* parent = nullptr) : + QObject(parent), m_model(model) {} + QAbstractListModel* model() { return m_model; } + QVariant getData(); + + void setItemDelegate(QAbstractItemDelegate* delegate) { m_delegate = delegate; } + QWidget* getEditorWidgetFromItemDelegate() const; + +private: + QAbstractListModel* m_model; + QAbstractItemDelegate* m_delegate = nullptr; +}; + +#endif // TESTVIEW_H + diff --git a/sources/pyside6/tests/pysidetest/true_property_test.py b/sources/pyside6/tests/pysidetest/true_property_test.py new file mode 100644 index 000000000..62f6505dc --- /dev/null +++ b/sources/pyside6/tests/pysidetest/true_property_test.py @@ -0,0 +1,58 @@ +# 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) + +""" +PYSIDE-2042: Tests true_property with inheritance +""" +is_pypy = hasattr(sys, "pypy_version_info") + +from PySide6.QtCore import QSize +from PySide6.QtWidgets import QWidget, QSpinBox +if not is_pypy: + from __feature__ import true_property +from helper.usesqapplication import UsesQApplication + + +@unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") +class TruePropertyInheritanceTest(UsesQApplication): + + def testTrueProperty(self): + # this worked + widget = QWidget() + check = widget.sizeHint + self.assertEqual(type(check), QSize) + + # PYSIDE-2042: inheritance did not work + spin_box = QSpinBox() + check = spin_box.sizeHint + self.assertEqual(type(check), QSize) + + def testHiddenMethods(self): + # PYSIDE-1889: setVisible is no longer a meta function but comes from the Property + widget = QWidget() + self.assertTrue("visible" in QWidget.__dict__) + self.assertFalse("isVisible" in QWidget.__dict__) + self.assertFalse("setVisible" in QWidget.__dict__) + self.assertTrue(hasattr(widget, "isVisible")) + self.assertTrue(hasattr(widget, "setVisible")) + self.assertEqual(widget.isVisible, QWidget.visible.fget) + self.assertEqual(widget.setVisible, QWidget.visible.fset) + + # This works with inheritance as well: + class SubClass(QWidget): + pass + sub_widget = SubClass() + self.assertEqual(sub_widget.isVisible, QWidget.visible.fget) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/typedef_signal_test.py b/sources/pyside6/tests/pysidetest/typedef_signal_test.py new file mode 100644 index 000000000..d0bdc880b --- /dev/null +++ b/sources/pyside6/tests/pysidetest/typedef_signal_test.py @@ -0,0 +1,52 @@ +# 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, Slot +from testbinding import TestObject + + +class Receiver(QObject): + + def __init__(self): + super().__init__() + self.received = None + + def slot(self, value): + self.received = value + + @Slot("IntList") + def containerSlot(self, value): + self.received = value + + +class TypedefSignal(unittest.TestCase): + + def testTypedef(self): + obj = TestObject(0) + receiver = Receiver() + + obj.signalWithTypedefValue.connect(receiver.slot) + obj.emitSignalWithTypedefValue(2) + self.assertEqual(receiver.received.value, 2) + + def testContainerTypedef(self): + obj = TestObject(0) + receiver = Receiver() + + test_list = [1, 2] + obj.signalWithContainerTypedefValue.connect(receiver.containerSlot) + obj.emitSignalWithContainerTypedefValue(test_list) + self.assertEqual(receiver.received, test_list) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml b/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml new file mode 100644 index 000000000..592d90a83 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/typesystem_pysidetest.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<typesystem package="testbinding"> + <load-typesystem name="QtWidgets/typesystem_widgets.xml" generate="no"/> + <value-type name="IntValue"/> + <primitive-type name="TypedefValue"> + <!-- + A conversion rule is used here because ApiExtractor can't associate + a primitive typedef to a non-primitive type. That would be a good + improvement to ApiExtractor. + --> + <conversion-rule> + <native-to-target> + return %CONVERTTOPYTHON[IntValue](%in); + </native-to-target> + <target-to-native> + <add-conversion type="IntValue"> + IntValue value = %CONVERTTOCPP[IntValue](%in); + %out = %OUTTYPE(value); + </add-conversion> + </target-to-native> + </conversion-rule> + </primitive-type> + <object-type name="TestObject" /> + + <primitive-type name="PySideInt"/> + <primitive-type name="PySideCPP2::PySideLong"/> + <!--<primitive-type name="PySideLong"/>--> + + <function signature="getHiddenObject()" /> + + <inject-code position="end"> + Shiboken::Conversions::registerConverterName(Shiboken::Conversions::PrimitiveTypeConverter<long>(), "PySideLong"); + Shiboken::Conversions::registerConverterName(Shiboken::Conversions::PrimitiveTypeConverter<long>(), "PySideCPP2::PySideLong"); + qRegisterMetaType<PySideInt>("PySideInt"); + qRegisterMetaType<PySideCPP2::PySideLong>("PySideLong"); + </inject-code> + + <object-type name="ContainerTest"/> + + <namespace-type name="PySideCPP"> + <object-type name="TestObjectWithNamespace"/> + <object-type name="TestObject2WithNamespace"> + <add-function signature="__repr__" return-type="PyObject*"> + <inject-code class="target" position="beginning"> + %PYARG_0 = Shiboken::String::fromCString("TestObject2WithNamespace(injected_repr)"); + </inject-code> + </add-function> + </object-type> + </namespace-type> + + <namespace-type name="PySideCPP2" generate="no"> + <enum-type name="Enum1" /> + <object-type name="TestObjectWithoutNamespace"> + <enum-type name="Enum2" /> + </object-type> + </namespace-type> + + <object-type name="TestView" > + <modify-function signature="TestView(QAbstractListModel*,QObject*)"> + <modify-argument index="1"> + <reference-count action="set"/> + </modify-argument> + </modify-function> + </object-type> + + <value-type name="TestQVariantEnum"/> + + <namespace-type name="FlagsNamespace" visible="no"> + <enum-type name="Option" flags="Options"/> + <object-type name="ClassForEnum" /> + </namespace-type> + + <object-type name="SharedPointerTestbench"/> + + <value-type name="QVariantHolder"/> + + <smart-pointer-type name="QSharedPointer" type="shared" getter="data" + reset-method="reset"/> + +</typesystem> diff --git a/sources/pyside6/tests/pysidetest/version_test.py b/sources/pyside6/tests/pysidetest/version_test.py new file mode 100644 index 000000000..f47ffc6d0 --- /dev/null +++ b/sources/pyside6/tests/pysidetest/version_test.py @@ -0,0 +1,31 @@ +#!/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 import __version_info__, __version__ + + +class CheckForVariablesTest(unittest.TestCase): + def testVesions(self): + version_tuple = (__version_info__[0], __version_info__[1], __version_info__[2]) + self.assertTrue(version_tuple >= (1, 0, 0)) + + self.assertTrue(version_tuple < (99, 99, 99)) + self.assertTrue(__version__) + + self.assertTrue(__version_info__ >= (4, 5, 0)) + self.assertTrue(__version__) + + +if __name__ == '__main__': + unittest.main() + |