diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2023-12-05 15:57:37 +0100 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2023-12-24 09:56:29 +0100 |
| commit | 3750fe1095df1f9dc3bed1168294bc10061ac8d2 (patch) | |
| tree | 1d8325177582d96e719e486467437f655d59315d | |
| parent | 65a607356369fc930429c3e63206c3a05911bb53 (diff) | |
Add QtQuickTest
[ChangeLog][PySide6] QtQuickTest has been added.
Fixes: PYSIDE-2543
Change-Id: I949a0e50a2e522589863ade1e2b2335be580a0d7
Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Adrian Herrmann <adrian.herrmann@qt.io>
14 files changed, 332 insertions, 0 deletions
diff --git a/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt b/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt new file mode 100644 index 000000000..cd224f2b0 --- /dev/null +++ b/sources/pyside6/PySide6/QtQuickTest/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(QtQuickTest) + +set(QtQuickTest_SRC +# module is always needed +${QtQuickTest_GEN_DIR}/qtquicktest_module_wrapper.cpp +) + +set(QtQuickTest_include_dirs ${QtQuickTest_SOURCE_DIR} + ${QtQml_SOURCE_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}OpenGL_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} + ${libpyside_SOURCE_DIR} + ${QtGui_GEN_DIR} + ${QtOpenGL_GEN_DIR} + ${QtCore_GEN_DIR} + ${QtNetwork_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtQuick_GEN_DIR} + ${QtQuickTest_GEN_DIR}) + +set(QtQuickTest_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}Core_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}Gui_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}OpenGL_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}Network_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}Qml_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}Quick_LIBRARIES} + ${Qt${QT_MAJOR_VERSION}QuickTest_LIBRARIES}) + +set(QtQuickTest_deps QtGui QtOpenGL QtNetwork QtQml QtQuick) + +create_pyside_module(NAME QtQuickTest + INCLUDE_DIRS QtQuickTest_include_dirs + LIBRARIES QtQuickTest_libraries + DEPS QtQuickTest_deps + TYPESYSTEM_PATH QtQuickTest_SOURCE_DIR + SOURCES QtQuickTest_SRC) diff --git a/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml b/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml new file mode 100644 index 000000000..4f30d1916 --- /dev/null +++ b/sources/pyside6/PySide6/QtQuickTest/typesystem_quicktest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +// Copyright (C) 2023 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 +--> +<typesystem package="PySide6.QtQuickTest"> + <load-typesystem name="QtCore/typesystem_core.xml" generate="no"/> + + <extra-includes> + <include file-name="QtQuickTest/quicktest.h" location="global"/> + <include file-name="QtCore/QDir" location="global"/> + <include file-name="pysideqobject.h" location="global"/> + <include file-name="vector" location="global"/> + </extra-includes> + <inject-code class="native" position="beginning" + file="../glue/qtquicktest.cpp" snippet="call-quick-test-main"/> + + <add-function signature="QUICK_TEST_MAIN(QString@name@,QStringList@argv@={},QString@dir@={})" + return-type="int"> + <inject-code file="../glue/qtquicktest.cpp" snippet="quick-test-main"/> + <inject-documentation format="target" mode="append" + file="../doc/qtquicktest.rst" + snippet="quick_test_main_documentation"/> + </add-function> + <add-function signature="QUICK_TEST_MAIN_WITH_SETUP(QString@name@,PyTypeObject*@setup@,QStringList@argv@={},QString@dir@={})" + return-type="int"> + <inject-code file="../glue/qtquicktest.cpp" snippet="quick-test-main_with_setup"/> + <inject-documentation format="target" mode="append" + file="../doc/qtquicktest.rst" + snippet="quick_test_main_with_setup_documentation"/> + </add-function> +</typesystem> diff --git a/sources/pyside6/PySide6/doc/qtquicktest.rst b/sources/pyside6/PySide6/doc/qtquicktest.rst new file mode 100644 index 000000000..9df2af071 --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtquicktest.rst @@ -0,0 +1,62 @@ +// @snippet quick_test_main_documentation + +Sets up the entry point for a Qt Quick Test application. +The ``name`` argument uniquely identifies this set of tests. + +``sys.argv`` should be passed to the ``argv`` argument to ensure +propagation of the command line arguments. + +.. note:: The function assumes that your test sources are in the current + directory, unless the ``QUICK_TEST_SOURCE_DIR`` environment + variable is set or a directory is passed in ``dir``. + +The following snippet demonstrates the use of this function: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN + + ex = QUICK_TEST_MAIN("example", sys.argv) + sys.exit(ex) + + +// @snippet quick_test_main_documentation + +// @snippet quick_test_main_with_setup_documentation + +Sets up the entry point for a Qt Quick Test application. +The ``name`` argument uniquely identifies this set of tests. + +``sys.argv`` should be passed to the ``argv`` argument to ensure +propagation of the command line arguments. + +This function is identical to ``QUICK_TEST_MAIN()``, except that it takes an +additional argument ``setup``, the type of a ``QObject``-derived +class which will be instantiated. With this class, it is possible to define +additional setup code to execute before running the QML test. + +The following snippet demonstrates the use of this function: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP + + class CustomTestSetup(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + @Slot(QQmlEngine) + def qmlEngineAvailable(self, qmlEngine): + pass + + ex = QUICK_TEST_MAIN_WITH_SETUP("qquicktestsetup", CustomTestSetup, sys.argv) + sys.exit(ex) + + +.. note:: The function assumes that your test sources are in the current + directory, unless the ``QUICK_TEST_SOURCE_DIR`` environment + variable is set or a directory is passed in ``dir``. + +// @snippet quick_test_main_with_setup_documentation diff --git a/sources/pyside6/PySide6/glue/qtquicktest.cpp b/sources/pyside6/PySide6/glue/qtquicktest.cpp new file mode 100644 index 000000000..f41735ddf --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtquicktest.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 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 + +/********************************************************************* + * INJECT CODE + ********************************************************************/ + +// @snippet call-quick-test-main +static int callQuickTestMain(const QString &name, QObject *setup, + QStringList argv, QString dir) +{ + if (dir.isEmpty()) + dir = QDir::currentPath(); + if (argv.isEmpty()) + argv.append(name); + + std::vector<QByteArray> argvB; + std::vector<char *> argvC; + const auto argc = argv.size(); + argvB.reserve(argc); + argvC.reserve(argc); + for (const auto &arg : argv) { + argvB.emplace_back(arg.toUtf8()); + argvC.push_back(argvB.back().data()); + } + + return quick_test_main_with_setup(int(argc), argvC.data(), + name.toUtf8().constData(), + dir.toUtf8().constData(), setup); +} +// @snippet call-quick-test-main + +// @snippet quick-test-main +const int exitCode = callQuickTestMain(%1, nullptr, %2, %3); +%PYARG_0 = %CONVERTTOPYTHON[int](exitCode); +// @snippet quick-test-main + +// @snippet quick-test-main_with_setup +Shiboken::AutoDecRef pySetupObject(PyObject_CallObject(reinterpret_cast<PyObject *>(%2), nullptr)); +if (pySetupObject.isNull() || PyErr_Occurred() != nullptr) + return nullptr; + +/// Convenience to convert a PyObject to QObject +QObject *setupObject = PySide::convertToQObject(pySetupObject.object(), true /* raiseError */); +if (setupObject == nullptr) + return nullptr; + +const int exitCode = callQuickTestMain(%1, setupObject, %3, %4); +%PYARG_0 = %CONVERTTOPYTHON[int](exitCode); +// @snippet quick-test-main_with_setup diff --git a/sources/pyside6/cmake/PySideHelpers.cmake b/sources/pyside6/cmake/PySideHelpers.cmake index c728e7c43..23ceda6bd 100644 --- a/sources/pyside6/cmake/PySideHelpers.cmake +++ b/sources/pyside6/cmake/PySideHelpers.cmake @@ -103,6 +103,7 @@ macro(collect_optional_modules) Quick Quick3D QuickControls2 + QuickTest QuickWidgets RemoteObjects Scxml diff --git a/sources/pyside6/doc/extras/QtQuickTest.rst b/sources/pyside6/doc/extras/QtQuickTest.rst new file mode 100644 index 000000000..52f13590b --- /dev/null +++ b/sources/pyside6/doc/extras/QtQuickTest.rst @@ -0,0 +1,58 @@ + Qt Quick Test is a unit test framework for QML applications. Test cases are + written as JavaScript functions within a QML TestCase type: + +.. code-block:: JavaScript + + import QtQuick + import QtTest + + TestCase { + name: "MathTests" + + function test_math() { + compare(2 + 2, 4, "2 + 2 = 4") + } + + function test_fail() { + compare(2 + 2, 5, "2 + 2 = 5") + } + } + +Functions whose names start with ``test_`` are treated as test cases to be +executed. + +QML API +^^^^^^^ + +The `QML types <https://doc.qt.io/qt-6/qttest-qmlmodule.html>`_ +in Qt Quick Test are available through the ``QtTest`` import. +To use the types, add the following import statement to your ``.qml`` file: + +.. code-block:: JavaScript + + import QtTest + +Running Tests +^^^^^^^^^^^^^ + +Test cases are launched by a harness that consists of the following code: + +.. code-block:: Python + + import sys + from PySide6.QtQuickTest import QUICK_TEST_MAIN + + QUICK_TEST_MAIN("example", sys.argv) + +Where "example" is the identifier to use to uniquely identify this set of +tests. + +Test execution can be controlled by a number of command line options (pass +``-h`` for help). + +Executing Code Before QML Tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To execute code before any of the QML tests are run, the +:py:func:`QUICK_TEST_MAIN_WITH_SETUP` function can be used. This can be useful +for setting context properties on the QML engine, amongst other things. diff --git a/sources/pyside6/doc/modules.rst b/sources/pyside6/doc/modules.rst index 625d60a75..357adcf8f 100644 --- a/sources/pyside6/doc/modules.rst +++ b/sources/pyside6/doc/modules.rst @@ -114,6 +114,10 @@ Qt Modules Supported by Qt for Python Provides classes for setting up the controls from C++. + .. grid-item-card:: :mod:`QtQuickTest <PySide6.QtQuickTest>` + + A unit test framework for QML applications where test cases are written as JavaScript functions. + .. grid-item-card:: :mod:`QtQuickWidgets <PySide6.QtQuickWidgets>` Provides the QQuickWidget class for embedding Qt Quick in widget-based applications. diff --git a/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in b/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in new file mode 100644 index 000000000..1d8397537 --- /dev/null +++ b/sources/pyside6/doc/qtmodules/pyside-qtquicktest.qdocconf.in @@ -0,0 +1,3 @@ +include(@QT_SRC_DIR@/../qtdeclarative/src/qmltest/doc/qtqmltest.qdocconf) +includepaths += -I @QT_SRC_DIR@/../qtdeclarative/src/qmltest +include(../pyside-config.qdocconf) diff --git a/sources/pyside6/tests/QtQuickTest/CMakeLists.txt b/sources/pyside6/tests/QtQuickTest/CMakeLists.txt new file mode 100644 index 000000000..49f15e447 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/CMakeLists.txt @@ -0,0 +1,3 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +PYSIDE_TEST(quicktestmainwithsetup/tst_quicktestmainwithsetup.py) diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml new file mode 100644 index 000000000..2cfe936a6 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/data/tst_setup.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick 2.0 +import QtTest 1.2 + +import QmlRegisterTypeCppModule 1.0 +import ImportPathQmlModule 1.0 + +TestCase { + name: "setup" + + QmlRegisterTypeCppType {} + ImportPathQmlType {} + + function initTestCase() + { + verify(qmlEngineAvailableCalled) + } +} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml new file mode 100644 index 000000000..617bdaaf6 --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/ImportPathQmlType.qml @@ -0,0 +1,3 @@ +import QtQuick 2.0 + +Item {} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir new file mode 100644 index 000000000..dea7c9a8a --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/imports/ImportPathQmlModule/qmldir @@ -0,0 +1,2 @@ +module ImportPathQmlModule +ImportPathQmlType 1.0 ImportPathQmlType.qml diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject new file mode 100644 index 000000000..61e89f4af --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/quicktestmainwithsetup.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["tst_quicktestmainwithsetup.py", "data/tst_setup.qml", + "imports/ImportPathQmlModule/ImportPathQmlType.qml"] +} diff --git a/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py new file mode 100644 index 000000000..33b2db08f --- /dev/null +++ b/sources/pyside6/tests/QtQuickTest/quicktestmainwithsetup/tst_quicktestmainwithsetup.py @@ -0,0 +1,46 @@ +# 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 + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) +from init_paths import init_test_paths +init_test_paths(False) + +from pathlib import Path +from PySide6.QtCore import QObject, Slot +from PySide6.QtQml import QQmlEngine, qmlRegisterType +from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP + + +"""Copy of the equivalent test in qtdeclarative.""" + + +class QmlRegisterTypeCppType(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + +class CustomTestSetup(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + @Slot(QQmlEngine) + def qmlEngineAvailable(self, qmlEngine): + # Test that modules are successfully imported by the TestCaseCollector + # that parses the QML files (but doesn't run them). For that to happen, + # qmlEngineAvailable() must be called before TestCaseCollector does its + # thing. + qmlRegisterType(QmlRegisterTypeCppType, "QmlRegisterTypeCppModule", 1, 0, + "QmlRegisterTypeCppType") + import_dir = Path(__file__).parent / "imports" + qmlEngine.addImportPath(os.fspath(import_dir)) + qmlEngine.rootContext().setContextProperty("qmlEngineAvailableCalled", True) + + +data_dir = Path(__file__).parent / "data" +exitCode = QUICK_TEST_MAIN_WITH_SETUP("qquicktestsetup", CustomTestSetup, sys.argv, + os.fspath(data_dir)) +sys.exit(exitCode) |
