aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/tests/tools
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/tests/tools')
-rw-r--r--sources/pyside6/tests/tools/__init__.py1
-rw-r--r--sources/pyside6/tests/tools/list-class-hierarchy.py35
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/baseline_default_birthdayparty.json1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/baseline_default_person.json1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_birthdayparty.json1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_person.json1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/baseline_property_happybirthdaysong.json1
-rw-r--r--sources/pyside6/tests/tools/metaobjectdump/test_metaobjectdump.py73
-rw-r--r--sources/pyside6/tests/tools/pyside6-android-deploy/CMakeLists.txt3
-rw-r--r--sources/pyside6/tests/tools/pyside6-android-deploy/extensive_android_deploy_test.py88
-rw-r--r--sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py274
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py88
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py400
-rw-r--r--sources/pyside6/tests/tools/pyside6-qml/CMakeLists.txt1
-rw-r--r--sources/pyside6/tests/tools/pyside6-qml/test_pyside6_qml.py75
17 files changed, 1014 insertions, 31 deletions
diff --git a/sources/pyside6/tests/tools/__init__.py b/sources/pyside6/tests/tools/__init__.py
new file mode 100644
index 000000000..31f792369
--- /dev/null
+++ b/sources/pyside6/tests/tools/__init__.py
@@ -0,0 +1 @@
+from init_paths import init_test_paths
diff --git a/sources/pyside6/tests/tools/list-class-hierarchy.py b/sources/pyside6/tests/tools/list-class-hierarchy.py
index 924706457..b773b7c58 100644
--- a/sources/pyside6/tests/tools/list-class-hierarchy.py
+++ b/sources/pyside6/tests/tools/list-class-hierarchy.py
@@ -1,32 +1,6 @@
#!/usr/bin/python
-
-#############################################################################
-##
-## Copyright (C) 2016 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of the test suite of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:GPL-EXCEPT$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 3 as published by the Free Software
-## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
# This is a small script printing out Qt binding class hierarchies
# for comparison purposes.
@@ -45,8 +19,7 @@ from inspect import isclass
ignore = ["staticMetaObject",
"pyqtConfigure",
"registerUserData",
- "thread",
- ]
+ "thread"]
def recurse_into(el, obj):
@@ -73,7 +46,7 @@ def recurse_into(el, obj):
return symbols
-if __name__=='__main__':
+if __name__ == '__main__':
modules = [ 'QtCore',
'QtGui',
'QtHelp',
diff --git a/sources/pyside6/tests/tools/metaobjectdump/CMakeLists.txt b/sources/pyside6/tests/tools/metaobjectdump/CMakeLists.txt
new file mode 100644
index 000000000..f1ad6ab16
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/CMakeLists.txt
@@ -0,0 +1 @@
+PYSIDE_TEST(test_metaobjectdump.py)
diff --git a/sources/pyside6/tests/tools/metaobjectdump/baseline_default_birthdayparty.json b/sources/pyside6/tests/tools/metaobjectdump/baseline_default_birthdayparty.json
new file mode 100644
index 000000000..6a695dd5c
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/baseline_default_birthdayparty.json
@@ -0,0 +1 @@
+[{"classes": [{"className": "BirthdayParty", "qualifiedClassName": "BirthdayParty", "object": true, "superClasses": [{"access": "public", "name": "QObject"}], "classInfos": [{"name": "QML.Element", "value": "auto"}, {"name": "DefaultProperty", "value": "guests"}], "properties": [{"name": "host", "type": "Person", "index": 0, "read": "host", "notify": "host_changed", "write": "host"}, {"name": "guests", "type": "QQmlListProperty<Person>", "index": 1}], "signals": [{"access": "public", "name": "host_changed", "arguments": [], "returnType": "void"}, {"access": "public", "name": "guests_changed", "arguments": [], "returnType": "void"}]}], "outputRevision": 68, "QML_IMPORT_NAME": "People", "QML_IMPORT_MAJOR_VERSION": 1, "QML_IMPORT_MINOR_VERSION": 0, "QT_MODULES": ["QtCore", "QtQml"]}] \ No newline at end of file
diff --git a/sources/pyside6/tests/tools/metaobjectdump/baseline_default_person.json b/sources/pyside6/tests/tools/metaobjectdump/baseline_default_person.json
new file mode 100644
index 000000000..571056c2a
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/baseline_default_person.json
@@ -0,0 +1 @@
+[{"classes": [{"className": "Person", "qualifiedClassName": "Person", "object": true, "superClasses": [{"access": "public", "name": "QObject"}], "classInfos": [{"name": "QML.Element", "value": "anonymous"}], "properties": [{"name": "name", "type": "QString", "index": 0, "read": "name", "notify": "name_changed", "write": "name"}, {"name": "shoe_size", "type": "int", "index": 1, "read": "shoe_size", "notify": "shoe_size_changed", "write": "shoe_size"}], "signals": [{"access": "public", "name": "name_changed", "arguments": [], "returnType": "void"}, {"access": "public", "name": "shoe_size_changed", "arguments": [], "returnType": "void"}]}, {"className": "Boy", "qualifiedClassName": "Boy", "object": true, "superClasses": [{"access": "public", "name": "Person"}], "classInfos": [{"name": "QML.Element", "value": "auto"}]}, {"className": "Girl", "qualifiedClassName": "Girl", "object": true, "superClasses": [{"access": "public", "name": "Person"}], "classInfos": [{"name": "QML.Element", "value": "auto"}]}], "outputRevision": 68, "QML_IMPORT_NAME": "People", "QML_IMPORT_MAJOR_VERSION": 1, "QML_IMPORT_MINOR_VERSION": 0, "QT_MODULES": ["QtCore", "QtQml"]}] \ No newline at end of file
diff --git a/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_birthdayparty.json b/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_birthdayparty.json
new file mode 100644
index 000000000..0491e41cc
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_birthdayparty.json
@@ -0,0 +1 @@
+[{"classes": [{"className": "BirthdayParty", "qualifiedClassName": "BirthdayParty", "object": true, "superClasses": [{"access": "public", "name": "QObject"}], "classInfos": [{"name": "QML.Element", "value": "auto"}], "properties": [{"name": "host", "type": "Person", "index": 0, "read": "host", "notify": "host_changed", "write": "host"}, {"name": "guests", "type": "QQmlListProperty<Person>", "index": 1}], "signals": [{"access": "public", "name": "host_changed", "arguments": [], "returnType": "void"}, {"access": "public", "name": "guests_changed", "arguments": [], "returnType": "void"}]}], "outputRevision": 68, "QML_IMPORT_NAME": "People", "QML_IMPORT_MAJOR_VERSION": 1, "QML_IMPORT_MINOR_VERSION": 0, "QT_MODULES": ["QtCore", "QtQml"]}] \ No newline at end of file
diff --git a/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_person.json b/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_person.json
new file mode 100644
index 000000000..82021ee0a
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/baseline_inheritance_person.json
@@ -0,0 +1 @@
+[{"classes": [{"className": "Person", "qualifiedClassName": "Person", "object": true, "superClasses": [{"access": "public", "name": "QObject"}], "classInfos": [{"name": "QML.Element", "value": "auto"}, {"name": "QML.Creatable", "value": "false"}, {"name": "QML.UncreatableReason", "value": "Person is an abstract base class."}], "properties": [{"name": "name", "type": "QString", "index": 0, "read": "name", "notify": "name_changed", "write": "name"}, {"name": "shoe_size", "type": "int", "index": 1, "read": "shoe_size", "notify": "shoe_size_changed", "write": "shoe_size"}], "signals": [{"access": "public", "name": "name_changed", "arguments": [], "returnType": "void"}, {"access": "public", "name": "shoe_size_changed", "arguments": [], "returnType": "void"}]}, {"className": "Boy", "qualifiedClassName": "Boy", "object": true, "superClasses": [{"access": "public", "name": "Person"}], "classInfos": [{"name": "QML.Element", "value": "auto"}]}, {"className": "Girl", "qualifiedClassName": "Girl", "object": true, "superClasses": [{"access": "public", "name": "Person"}], "classInfos": [{"name": "QML.Element", "value": "auto"}]}], "outputRevision": 68, "QML_IMPORT_NAME": "People", "QML_IMPORT_MAJOR_VERSION": 1, "QML_IMPORT_MINOR_VERSION": 0, "QT_MODULES": ["QtCore", "QtQml"]}] \ No newline at end of file
diff --git a/sources/pyside6/tests/tools/metaobjectdump/baseline_property_happybirthdaysong.json b/sources/pyside6/tests/tools/metaobjectdump/baseline_property_happybirthdaysong.json
new file mode 100644
index 000000000..c009111b7
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/baseline_property_happybirthdaysong.json
@@ -0,0 +1 @@
+[{"classes": [{"className": "HappyBirthdaySong", "qualifiedClassName": "HappyBirthdaySong", "object": false, "superClasses": [{"access": "public", "name": "QPyQmlPropertyValueSource"}], "classInfos": [{"name": "QML.Element", "value": "auto"}], "properties": [{"name": "name", "type": "QString", "index": 0, "read": "name", "notify": "name_changed", "write": "name"}], "signals": [{"access": "public", "name": "name_changed", "arguments": [], "returnType": "void"}], "slots": [{"access": "public", "name": "advance", "arguments": [], "returnType": "void"}]}], "outputRevision": 68, "QML_IMPORT_NAME": "People", "QML_IMPORT_MAJOR_VERSION": 1, "QML_IMPORT_MINOR_VERSION": 0, "QT_MODULES": ["QtCore", "QtQml"]}] \ No newline at end of file
diff --git a/sources/pyside6/tests/tools/metaobjectdump/test_metaobjectdump.py b/sources/pyside6/tests/tools/metaobjectdump/test_metaobjectdump.py
new file mode 100644
index 000000000..5e7412bf5
--- /dev/null
+++ b/sources/pyside6/tests/tools/metaobjectdump/test_metaobjectdump.py
@@ -0,0 +1,73 @@
+# 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 subprocess
+import unittest
+
+from pathlib import Path
+
+"""Test for pyside6-metaobjectdump.
+
+The test prints commands to regenerate the base line."""
+
+
+def msg_regenerate(cmd, baseline):
+ cmd_str = " ".join(cmd)
+ return (f"# Regenerate {baseline}\n"
+ f"{cmd_str} > {baseline}")
+
+
+@unittest.skipIf(sys.version_info < (3, 8), "Needs a recent ast module")
+class TestMetaObjectDump(unittest.TestCase):
+ """Test for the metaobjectdump tool. Compares the output of metaobjectdump.py for some
+ example files in compact format."""
+
+ def setUp(self):
+ super().setUp()
+ self._dir = Path(__file__).parent.resolve()
+ pyside_root = self._dir.parents[4]
+ self._metaobjectdump_tool = pyside_root / "sources" / "pyside-tools" / "metaobjectdump.py"
+ self._examples_dir = (pyside_root / "examples" /
+ "qml" / "tutorials" / "extending-qml-advanced")
+
+ # Compile a list of examples (tuple [file, base line, command])
+ examples = []
+ for d in ["advanced2-Inheritance-and-coercion", "advanced3-Default-properties"]:
+ example_dir = self._examples_dir / d
+ examples.append(example_dir / "birthdayparty.py")
+ examples.append(example_dir / "person.py")
+ # Example with slot
+ examples.append(self._examples_dir / "advanced6-Property-value-source"
+ / "happybirthdaysong.py")
+
+ metaobjectdump_cmd_root = [sys.executable, os.fspath(self._metaobjectdump_tool), "-c", "-s"]
+ self._examples = []
+ for example in examples:
+ name = example.parent.name
+ # Simplify "advanced2-Inheritance-and-coercion" -> "inheritance"
+ short_name = name.split("-")[1].lower()
+ baseline_name = f"baseline_{short_name}_{example.stem}.json"
+ baseline_path = self._dir / baseline_name
+ cmd = metaobjectdump_cmd_root + [os.fspath(example)]
+ self._examples.append((example, baseline_path, cmd))
+ print(msg_regenerate(cmd, baseline_path))
+
+ def testMetaObjectDump(self):
+ self.assertTrue(self._examples_dir.is_dir())
+ self.assertTrue(self._metaobjectdump_tool.is_file())
+
+ for example, baseline, cmd in self._examples:
+ self.assertTrue(example.is_file())
+ self.assertTrue(baseline.is_file())
+ baseline_data = baseline.read_text()
+
+ popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ actual = popen.communicate()[0].decode("UTF-8")
+ self.assertEqual(popen.returncode, 0)
+ self.assertEqual(baseline_data, actual)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/CMakeLists.txt b/sources/pyside6/tests/tools/pyside6-android-deploy/CMakeLists.txt
new file mode 100644
index 000000000..c32d636ed
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-android-deploy/CMakeLists.txt
@@ -0,0 +1,3 @@
+if(CMAKE_HOST_UNIX)
+ PYSIDE_TEST(test_pyside6_android_deploy.py)
+endif()
diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/extensive_android_deploy_test.py b/sources/pyside6/tests/tools/pyside6-android-deploy/extensive_android_deploy_test.py
new file mode 100644
index 000000000..271f8eebd
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-android-deploy/extensive_android_deploy_test.py
@@ -0,0 +1,88 @@
+# 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
+
+"""
+ Extensive manual test of pyside6-android-deploy
+
+ Note: Not to be added into the CI
+"""
+
+import logging
+import unittest
+import tempfile
+import shutil
+import sys
+import os
+import importlib
+from pathlib import Path
+
+
+class TestPySide6Deploy(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.pyside_root = Path(__file__).parents[5].resolve()
+ example_root = cls.pyside_root / "examples"
+ example_application = example_root / "gui" / "analogclock"
+ cls.temp_dir = tempfile.mkdtemp()
+ cls.temp_example = Path(
+ shutil.copytree(example_application, Path(cls.temp_dir) / "analogclock")
+ ).resolve()
+ cls.current_dir = Path.cwd()
+
+ sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools"))
+ cls.deploy_lib = importlib.import_module("deploy_lib")
+ cls.android_deploy = importlib.import_module("android_deploy")
+ sys.modules["android_deploy"] = cls.android_deploy
+
+ if os.environ.get("WHEEL_PYSIDE") is not None:
+ cls.pyside_wheel = Path(os.environ.get("WHEEL_PYSIDE")).resolve()
+ else:
+ raise Exception("Environment variable WHEEL_PYSIDE does not exist")
+
+ if os.environ.get("WHEEL_SHIBOKEN") is not None:
+ cls.shiboken_wheel = Path(os.environ.get("WHEEL_SHIBOKEN")).resolve()
+ else:
+ raise Exception("Environment variable WHEEL_SHIBOKEN does not exist")
+
+ def setUp(self):
+ os.chdir(self.temp_example)
+ self.config_file = self.temp_example / "pysidedeploy.spec"
+
+ def testDeployment(self):
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, keep_deployment_files=True,
+ loglevel=logging.INFO, force=True)
+
+ print("Testing with config file")
+ self.android_deploy.main(name="android_app", config_file=self.config_file,
+ loglevel=logging.INFO, force=True)
+
+ def testWithNdkSdk(self):
+ if os.environ.get("ANDROID_SDK_ROOT") is not None:
+ android_sdk_root = Path(os.environ.get("ANDROID_SDK_ROOT")).resolve()
+ else:
+ raise Exception("Environment variable ANDROID_SDK_ROOT does not exist")
+
+ if os.environ.get("ANDROID_NDK_ROOT") is not None:
+ android_ndk_root = Path(os.environ.get("ANDROID_NDK_ROOT")).resolve()
+ else:
+ raise Exception("Environment variable ANDROID_NDK_ROOT does not exist")
+
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel,
+ ndk_path=android_ndk_root,
+ sdk_path=android_sdk_root,
+ keep_deployment_files=True,
+ loglevel=logging.INFO, force=True)
+
+ def tearDown(self) -> None:
+ super().tearDown()
+ os.chdir(self.current_dir)
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ shutil.rmtree(Path(cls.temp_dir))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
new file mode 100644
index 000000000..ec575e923
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py
@@ -0,0 +1,274 @@
+# 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
+
+import importlib
+import os
+import re
+import shutil
+import sys
+import tempfile
+import unittest
+import subprocess
+from pathlib import Path
+from unittest import mock
+from unittest.mock import patch
+
+sys.path.append(os.fspath(Path(__file__).resolve().parents[2]))
+from init_paths import init_test_paths # noqa: E402
+init_test_paths(False)
+
+
+class DeployTestBase(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.pyside_root = Path(__file__).parents[5].resolve()
+ cls.example_root = cls.pyside_root / "examples"
+ cls.temp_dir = tempfile.mkdtemp()
+ cls.current_dir = Path.cwd()
+ cls.pyside_wheel = Path("/tmp/PySide6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl")
+ cls.shiboken_wheel = Path("/tmp/shiboken6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl")
+ cls.ndk_path = Path("/tmp/android_sdk/ndk/25.2.9519653")
+ cls.sdk_path = Path("/tmp/android_sdk")
+ pyside_tools = cls.pyside_root / "sources" / "pyside-tools"
+
+ # install extra python dependencies
+ android_requirements_file = pyside_tools / "requirements-android.txt"
+ with open(android_requirements_file, 'r', encoding='UTF-8') as file:
+ while line := file.readline():
+ dependent_package = line.rstrip()
+ if not bool(importlib.util.find_spec(dependent_package)):
+ command = [sys.executable, "-m", "pip", "install", dependent_package]
+ subprocess.run(command)
+
+ sys.path.append(str(pyside_tools))
+ cls.deploy_lib = importlib.import_module("deploy_lib")
+ cls.android_deploy = importlib.import_module("android_deploy")
+ sys.modules["android_deploy"] = cls.android_deploy
+
+ # required for comparing long strings
+ cls.maxDiff = None
+
+ # print no outputs to stdout
+ sys.stdout = mock.MagicMock()
+
+ def tearDown(self) -> None:
+ super().tearDown()
+ os.chdir(self.current_dir)
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ shutil.rmtree(Path(cls.temp_dir))
+
+
+@patch("deploy_lib.android.android_config.extract_and_copy_jar")
+class TestPySide6AndroidDeployWidgets(DeployTestBase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ example_widget_application = cls.example_root / "gui" / "analogclock"
+ cls.temp_example = Path(
+ shutil.copytree(example_widget_application, Path(cls.temp_dir) / "analogclock")
+ ).resolve()
+
+ def setUp(self):
+ os.chdir(self.temp_example)
+ self.config_file = self.temp_example / "pysidedeploy.spec"
+ self.buildozer_config = self.temp_example / "buildozer.spec"
+
+ @patch("deploy_lib.android.android_config.AndroidConfig._find_local_libs")
+ @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules")
+ @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel")
+ def test_dry_run(self, mock_qtlibs, mock_extraqtmodules, mock_local_libs, mock_extract_jar):
+ mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib"
+ mock_extraqtmodules.return_value = []
+ dependent_plugins = ["platforms_qtforandroid",
+ "platforminputcontexts_qtvirtualkeyboardplugin",
+ "iconengines_qsvgicon"]
+ mock_local_libs.return_value = [], dependent_plugins
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path,
+ dry_run=True, force=True)
+
+ self.assertEqual(mock_extract_jar.call_count, 0)
+ self.assertEqual(mock_qtlibs.call_count, 1)
+ self.assertEqual(mock_extraqtmodules.call_count, 1)
+ self.assertEqual(mock_local_libs.call_count, 1)
+
+ @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars")
+ @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist")
+ @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules")
+ @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel")
+ def test_config(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist, mock_find_jars,
+ mock_extract_jar):
+ jar_dir = "tmp/jar/PySide6/jar"
+ mock_extract_jar.return_value = Path(jar_dir)
+ mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib"
+ mock_extraqtmodules.return_value = []
+ mock_recipes_exist.return_value = True
+ jars, init_classes = ["/tmp/jar/PySide6/jar/Qt6Android.jar",
+ "/tmp/jar/PySide6/jar/Qt6AndroidBindings.jar"], []
+ mock_find_jars.return_value = jars, init_classes
+
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path,
+ init=True, force=True, keep_deployment_files=True)
+
+ self.assertEqual(mock_extract_jar.call_count, 1)
+ self.assertEqual(mock_qtlibs.call_count, 1)
+ self.assertEqual(mock_extraqtmodules.call_count, 1)
+ self.assertEqual(mock_recipes_exist.call_count, 1)
+ self.assertEqual(mock_find_jars.call_count, 1)
+ self.assertTrue(self.config_file.exists())
+ self.assertTrue(self.buildozer_config.exists())
+
+ # test config file contents
+ config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
+ self.assertEqual(config_obj.get_value("app", "input_file"), "main.py")
+ self.assertEqual(config_obj.get_value("python", "android_packages"),
+ "buildozer==1.5.0,cython==0.29.33")
+ self.assertEqual(config_obj.get_value("android", "wheel_pyside"),
+ str(self.pyside_wheel.resolve()))
+ self.assertEqual(config_obj.get_value("android", "wheel_shiboken"),
+ str(self.shiboken_wheel.resolve()))
+ self.assertEqual(config_obj.get_value("buildozer", "mode"), "debug")
+ self.assertEqual(config_obj.get_value("buildozer", "recipe_dir"),
+ '')
+ self.assertEqual(config_obj.get_value("buildozer", "jars_dir"),
+ str(self.temp_example / jar_dir))
+ self.assertIn(str(self.ndk_path), config_obj.get_value("buildozer", "ndk_path"))
+ self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), '')
+ expected_modules = {"Core", "Gui"}
+ obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
+ self.assertEqual(obtained_modules, expected_modules)
+ expected_local_libs = ""
+ self.assertEqual(config_obj.get_value("buildozer", "local_libs"),
+ expected_local_libs)
+ self.assertEqual(config_obj.get_value("buildozer", "arch"), "x86_64")
+
+ # test buildozer config file contents
+ buildozer_config_obj = self.deploy_lib.BaseConfig(config_file=self.buildozer_config)
+ obtained_jars = set(buildozer_config_obj.get_value("app", "android.add_jars").split(','))
+ expected_jars = set(jars)
+ self.assertEqual(obtained_jars, expected_jars)
+ obtained_extra_args = buildozer_config_obj.get_value("app", "p4a.extra_args")
+ extra_args_patrn = re.compile("--qt-libs=(?P<modules>.*) --load-local-libs="
+ "(?P<local_libs>.*) --init-classes=(?P<init_classes>.*)")
+ match = extra_args_patrn.search(obtained_extra_args)
+ obtained_modules = match.group("modules").split(',')
+ obtained_local_libs = match.group("local_libs")
+ obtained_init_classes = match.group("init_classes")
+ self.assertEqual(set(obtained_modules), expected_modules)
+ self.assertEqual(obtained_local_libs, expected_local_libs)
+ self.assertEqual(obtained_init_classes, '')
+ expected_include_exts = "py,png,jpg,kv,atlas,qml,js"
+ obtained_include_exts = buildozer_config_obj.get_value("app", "source.include_exts")
+ self.assertEqual(expected_include_exts, obtained_include_exts)
+
+ self.config_file.unlink()
+ self.buildozer_config.unlink()
+
+ def test_errors(self, mock_extract_jar):
+ # test if error raises for non existing NDK
+ with self.assertRaises(FileNotFoundError) as context:
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, force=True)
+ self.assertTrue("Unable to find Android NDK" in str(context.exception))
+
+ # test when cwd() is not project_dir
+ os.chdir(self.current_dir)
+ with self.assertRaises(RuntimeError) as context:
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, init=True, force=True)
+ self.assertTrue("For Android deployment to work" in str(context.exception))
+
+
+@patch("deploy_lib.config.run_qmlimportscanner")
+@patch("deploy_lib.android.android_config.extract_and_copy_jar")
+class TestPySide6AndroidDeployQml(DeployTestBase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ # setting up example
+ example_qml_application = cls.example_root / "quick" / "models" / "stringlistmodel"
+ cls.temp_qml_example = Path(
+ shutil.copytree(example_qml_application, Path(cls.temp_dir) / "stringlistmodel")
+ ).resolve()
+
+ def setUp(self):
+ os.chdir(self.temp_qml_example)
+ self.config_file = self.temp_qml_example / "pysidedeploy.spec"
+ self.buildozer_config_file = self.temp_qml_example / "buildozer.spec"
+ (self.temp_qml_example / "stringlistmodel.py").rename(self.temp_qml_example / "main.py")
+ (self.temp_qml_example / "stringlistmodel.pyproject").unlink()
+
+ @patch("deploy_lib.android.android_config.AndroidConfig._find_local_libs")
+ @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars")
+ @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist")
+ @patch("deploy_lib.android.android_config.AndroidConfig._find_dependent_qt_modules")
+ @patch("deploy_lib.android.android_config.find_qtlibs_in_wheel")
+ def test_config_with_Qml(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist,
+ mock_find_jars, mock_local_libs, mock_extract_jar,
+ mock_qmlimportscanner):
+ # setting up mocks
+ jar_dir = "tmp/jar/PySide6/jar"
+ mock_extract_jar.return_value = Path(jar_dir)
+ mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib"
+ mock_extraqtmodules.return_value = ['Qml', 'Network', 'QmlModels', 'OpenGL']
+ mock_recipes_exist.return_value = True
+ jars, init_classes = ["/tmp/jar/PySide6/jar/Qt6Android.jar",
+ "/tmp/jar/PySide6/jar/Qt6AndroidBindings.jar",
+ "/tmp/jar/PySide6/jar/Qt6AndroidNetworkInformationBackend.jar",
+ "/tmp/jar/PySide6/jar/Qt6AndroidNetwork.jar"], []
+ mock_find_jars.return_value = jars, init_classes
+ dependent_plugins = ["platforms_qtforandroid",
+ "platforminputcontexts_qtvirtualkeyboardplugin",
+ "iconengines_qsvgicon"]
+ mock_local_libs.return_value = [], dependent_plugins
+ mock_qmlimportscanner.return_value = ["QtQuick"]
+
+ self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel,
+ pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path,
+ init=True, force=True, keep_deployment_files=True)
+
+ self.assertEqual(mock_extract_jar.call_count, 1)
+ self.assertEqual(mock_qtlibs.call_count, 1)
+ self.assertEqual(mock_extraqtmodules.call_count, 1)
+ self.assertEqual(mock_recipes_exist.call_count, 1)
+ self.assertEqual(mock_find_jars.call_count, 1)
+ self.assertEqual(mock_qmlimportscanner.call_count, 1)
+ self.assertTrue(self.config_file.exists())
+ self.assertTrue(self.buildozer_config_file.exists())
+
+ config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
+ expected_modules = {"Quick", "Core", "Gui", "Network", "Qml", "QmlModels", "OpenGL"}
+ obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
+ self.assertEqual(obtained_modules, expected_modules)
+ expected_local_libs = ""
+ self.assertEqual(config_obj.get_value("buildozer", "local_libs"),
+ expected_local_libs)
+ expected_qt_plugins = set(dependent_plugins)
+ obtained_qt_plugins = set(config_obj.get_value("android", "plugins").split(","))
+ self.assertEqual(expected_qt_plugins, obtained_qt_plugins)
+
+ # test buildozer config file contents
+ buildozer_config_obj = self.deploy_lib.BaseConfig(config_file=self.buildozer_config_file)
+ obtained_jars = set(buildozer_config_obj.get_value("app", "android.add_jars").split(','))
+ expected_jars = set(jars)
+ self.assertEqual(obtained_jars, expected_jars)
+ obtained_extra_args = buildozer_config_obj.get_value("app", "p4a.extra_args")
+ extra_args_patrn = re.compile("--qt-libs=(?P<modules>.*) --load-local-libs="
+ "(?P<local_libs>.*) --init-classes=(?P<init_classes>.*)")
+ match = extra_args_patrn.search(obtained_extra_args)
+ obtained_modules = match.group("modules").split(',')
+ obtained_local_libs = match.group("local_libs")
+ obtained_init_classes = match.group("init_classes")
+ self.assertEqual(set(obtained_modules), expected_modules)
+ self.assertEqual(obtained_local_libs, expected_local_libs)
+ self.assertEqual(obtained_init_classes, '')
+
+ self.config_file.unlink()
+ self.buildozer_config_file.unlink()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/pyside6/tests/tools/pyside6-deploy/CMakeLists.txt b/sources/pyside6/tests/tools/pyside6-deploy/CMakeLists.txt
new file mode 100644
index 000000000..7f010fbd6
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-deploy/CMakeLists.txt
@@ -0,0 +1 @@
+PYSIDE_TEST(test_pyside6_deploy.py)
diff --git a/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py b/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py
new file mode 100644
index 000000000..40afc7f5c
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py
@@ -0,0 +1,88 @@
+# 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
+
+"""
+ Extensive manual test of pyside6-deploy
+
+ Note: Not to be added into the CI
+"""
+
+import logging
+import unittest
+import tempfile
+import shutil
+import sys
+import os
+import importlib
+from pathlib import Path
+
+
+class TestPySide6Deploy(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.pyside_root = Path(__file__).parents[5].resolve()
+ example_root = cls.pyside_root / "examples"
+ example_widgets = example_root / "widgets" / "widgets" / "tetrix"
+ example_qml = example_root / "qml" / "editingmodel"
+ example_webenginequick = example_root / "webenginequick" / "nanobrowser"
+ cls.temp_dir = tempfile.mkdtemp()
+ cls.temp_example_widgets = Path(
+ shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix")
+ ).resolve()
+ cls.temp_example_qml = Path(
+ shutil.copytree(example_qml, Path(cls.temp_dir) / "editingmodel")
+ ).resolve()
+ cls.temp_example_webenginequick = Path(
+ shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser")
+ ).resolve()
+ cls.current_dir = Path.cwd()
+ cls.linux_onefile_icon = (
+ cls.pyside_root / "sources" / "pyside-tools" / "deploy_lib" / "pyside_icon.jpg"
+ )
+
+ sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools"))
+ cls.deploy_lib = importlib.import_module("deploy_lib")
+ cls.deploy = importlib.import_module("deploy")
+ sys.modules["deploy"] = cls.deploy
+
+ def setUpWidgets(self):
+ os.chdir(self.temp_example_widgets)
+ self.main_file = self.temp_example_widgets / "tetrix.py"
+ self.config_file = self.temp_example_widgets / "pysidedeploy.spec"
+
+ def testWidget(self):
+ self.setUpWidgets()
+ self.deploy.main(self.main_file, name="widget_app", loglevel=logging.INFO,
+ keep_deployment_files=True, force=True)
+
+ print("Now testing Widget with config file")
+ self.deploy.main(self.main_file, config_file=self.config_file, loglevel=logging.INFO,
+ force=True)
+
+ def setUpQml(self):
+ os.chdir(self.temp_example_qml)
+ self.main_file = self.temp_example_qml / "main.py"
+ self.config_file = self.temp_example_qml / "pysidedeploy.spec"
+
+ def testQml(self):
+ self.setUpQml()
+ self.deploy.main(self.main_file, name="qml_app", loglevel=logging.INFO,
+ keep_deployment_files=True, force=True)
+
+ def testWebEngineQuickDryRun(self):
+ os.chdir(self.temp_example_webenginequick)
+ main_file = self.temp_example_webenginequick / "quicknanobrowser.py"
+ self.deploy.main(main_file, name="qml_app", keep_deployment_files=True,
+ loglevel=logging.INFO, force=True)
+
+ def tearDown(self) -> None:
+ super().tearDown()
+ os.chdir(self.current_dir)
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ shutil.rmtree(Path(cls.temp_dir))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
new file mode 100644
index 000000000..db60c8c3f
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
@@ -0,0 +1,400 @@
+# 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 unittest
+import tempfile
+import shutil
+import sys
+import os
+import importlib
+import platform
+from pathlib import Path
+from unittest.mock import patch
+from unittest import mock
+
+sys.path.append(os.fspath(Path(__file__).resolve().parents[2]))
+from init_paths import init_test_paths, _get_qt_lib_dir # noqa: E402
+init_test_paths(False)
+
+
+def is_pyenv_python():
+ pyenv_root = os.environ.get("PYENV_ROOT")
+
+ if pyenv_root and (resolved_exe := str(Path(sys.executable).resolve())):
+ return resolved_exe.startswith(pyenv_root)
+ return False
+
+
+class LongSortedOptionTest(unittest.TestCase):
+ @staticmethod
+ def _option_prepare(s):
+ """
+ Take a string and return a list obtained by text.split().
+ Options starting with "--" are also sorted."
+ """
+ items = s.split()
+ for idx in range(len(items)):
+ if items[idx].startswith("--"):
+ return items[:idx] + sorted(items[idx:])
+ return items
+
+ def assertEqual(self, text_a, text_b):
+ if (not isinstance(text_a, str) or not isinstance(text_b, str)
+ or (len(text_a) < 50 and len(text_b) < 50)):
+ return super().assertEqual(text_a, text_b)
+ sort_a = self._option_prepare(text_a)
+ sort_b = self._option_prepare(text_b)
+ return super().assertEqual(sort_a, sort_b)
+
+
+class DeployTestBase(LongSortedOptionTest):
+ @classmethod
+ def setUpClass(cls):
+ cls.pyside_root = Path(__file__).parents[5].resolve()
+ cls.example_root = cls.pyside_root / "examples"
+ cls.temp_dir = tempfile.mkdtemp()
+ cls.current_dir = Path.cwd()
+ tools_path = cls.pyside_root / "sources" / "pyside-tools"
+ cls.win_icon = tools_path / "deploy_lib" / "pyside_icon.ico"
+ cls.linux_icon = tools_path / "deploy_lib" / "pyside_icon.jpg"
+ cls.macos_icon = tools_path / "deploy_lib" / "pyside_icon.icns"
+ if tools_path not in sys.path:
+ sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools"))
+ cls.deploy_lib = importlib.import_module("deploy_lib")
+ cls.deploy = importlib.import_module("deploy")
+ sys.modules["deploy"] = cls.deploy
+ files_to_ignore = [".cpp.o", ".qsb", ".webp"]
+ cls.dlls_ignore_nuitka = " ".join([f"--noinclude-dlls=*{file}"
+ for file in files_to_ignore])
+
+ # required for comparing long strings
+ cls.maxDiff = None
+
+ # print no outputs to stdout
+ sys.stdout = mock.MagicMock()
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ shutil.rmtree(Path(cls.temp_dir))
+
+ def tearDown(self) -> None:
+ super().tearDown()
+ os.chdir(self.current_dir)
+
+
+@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
+ "Test only works on macOS version 12+")
+@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
+class TestPySide6DeployWidgets(DeployTestBase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ example_widgets = cls.example_root / "widgets" / "widgets" / "tetrix"
+ cls.temp_example_widgets = Path(
+ shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix")
+ ).resolve()
+
+ def setUp(self):
+ os.chdir(self.temp_example_widgets)
+ self.main_file = self.temp_example_widgets / "tetrix.py"
+ self.deployment_files = self.temp_example_widgets / "deployment"
+ # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles
+ # some plugins by default
+ self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "platforminputcontexts", "platforms",
+ "platforms/darwin", "platformthemes", "styles", "xcbglintegrations"]
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,platforminputcontexts,platforms/darwin")
+ self.expected_run_cmd = (
+ f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
+ f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
+ f" --noinclude-qt-translations"
+ f" --include-qt-plugins={plugins_nuitka}"
+ f" {self.dlls_ignore_nuitka}"
+ )
+ if sys.platform.startswith("linux"):
+ self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile"
+ elif sys.platform == "darwin":
+ self.expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}"
+ " --macos-create-app-bundle --standalone")
+ elif sys.platform == "win32":
+ self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile"
+
+ if is_pyenv_python():
+ self.expected_run_cmd += " --static-libpython=no"
+ self.config_file = self.temp_example_widgets / "pysidedeploy.spec"
+
+ def testWidgetDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
+ # Checking for dry run commands is equivalent to mocking the
+ # subprocess.check_call() in commands.py as the the dry run command
+ # is the command being run.
+ original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
+ self.assertEqual(original_output, self.expected_run_cmd)
+
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testWidgetConfigFile(self, mock_sitepackages, mock_plugins):
+ mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ mock_plugins.return_value = self.all_plugins
+ # includes both dry run and config_file tests
+ # init
+ init_result = self.deploy.main(self.main_file, init=True, force=True)
+ self.assertEqual(init_result, None)
+
+ # test with config
+ original_output = self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
+ self.assertEqual(original_output, self.expected_run_cmd)
+
+ # # test config file contents
+ config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
+ self.assertEqual(config_obj.get_value("app", "input_file"), "tetrix.py")
+ self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
+ self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
+ self.assertEqual(config_obj.get_value("python", "packages"),
+ "Nuitka==2.1")
+ self.assertEqual(config_obj.get_value("qt", "qml_files"), "")
+ equ_base = "--quiet --noinclude-qt-translations"
+ equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
+ self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value)
+ self.assertEqual(config_obj.get_value("qt", "excluded_qml_plugins"), "")
+ expected_modules = {"Core", "Gui", "Widgets"}
+ if sys.platform != "win32":
+ expected_modules.add("DBus")
+ obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
+ self.assertEqual(obtained_modules, expected_modules)
+ obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",")
+ self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort())
+ self.config_file.unlink()
+
+ def testErrorReturns(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
+ # main file and config file does not exists
+ fake_main_file = self.main_file.parent / "main.py"
+ with self.assertRaises(RuntimeError) as context:
+ self.deploy.main(main_file=fake_main_file, config_file=self.config_file)
+ self.assertTrue("Directory does not contain main.py file." in str(context.exception))
+
+
+@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
+ "Test only works on macOS version 12+")
+@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
+class TestPySide6DeployQml(DeployTestBase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ example_qml = cls.example_root / "qml" / "editingmodel"
+ cls.temp_example_qml = Path(
+ shutil.copytree(example_qml, Path(cls.temp_dir) / "editingmodel")
+ ).resolve()
+
+ def setUp(self):
+ os.chdir(self.temp_example_qml)
+ self.main_file = self.temp_example_qml / "main.py"
+ self.deployment_files = self.temp_example_qml / "deployment"
+ self.first_qml_file = "main.qml"
+ self.second_qml_file = "MovingRectangle.qml"
+
+ # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles
+ # some plugins by default
+ self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "networkaccess", "networkinformation",
+ "platforminputcontexts", "platforms", "platforms/darwin",
+ "platformthemes", "qmltooling", "scenegraph", "tls",
+ "xcbglintegrations"]
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts,"
+ "platforms/darwin,qml,qmltooling,scenegraph")
+ self.expected_run_cmd = (
+ f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
+ f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
+ f" --noinclude-qt-translations"
+ f" {self.dlls_ignore_nuitka}"
+ " --noinclude-dlls=*/qml/QtQuickEffectMaker/*"
+ f" --include-qt-plugins={plugins_nuitka}"
+ f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}="
+ f"./main.qml --include-data-files="
+ f"{str(self.temp_example_qml / self.second_qml_file)}=./MovingRectangle.qml"
+ )
+
+ if sys.platform != "win32":
+ self.expected_run_cmd += (
+ " --noinclude-dlls=libQt6Charts*"
+ " --noinclude-dlls=libQt6Quick3D* --noinclude-dlls=libQt6Sensors*"
+ " --noinclude-dlls=libQt6Test* --noinclude-dlls=libQt6WebEngine*"
+ )
+ else:
+ self.expected_run_cmd += (
+ " --noinclude-dlls=Qt6Charts*"
+ " --noinclude-dlls=Qt6Quick3D* --noinclude-dlls=Qt6Sensors*"
+ " --noinclude-dlls=Qt6Test* --noinclude-dlls=Qt6WebEngine*"
+ )
+
+ if sys.platform.startswith("linux"):
+ self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile"
+ elif sys.platform == "darwin":
+ self.expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}"
+ " --macos-create-app-bundle --standalone")
+ elif sys.platform == "win32":
+ self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile"
+
+ if is_pyenv_python():
+ self.expected_run_cmd += " --static-libpython=no"
+ self.config_file = self.temp_example_qml / "pysidedeploy.spec"
+
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testQmlConfigFile(self, mock_sitepackages, mock_plugins):
+ mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ mock_plugins.return_value = self.all_plugins
+ # create config file
+ with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
+ mock_qmlimportscanner.return_value = ["QtQuick"]
+ init_result = self.deploy.main(self.main_file, init=True, force=True)
+ self.assertEqual(init_result, None)
+
+ # test config file contents
+ config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
+ self.assertEqual(config_obj.get_value("app", "input_file"), "main.py")
+ self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
+ self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
+ self.assertEqual(config_obj.get_value("python", "packages"),
+ "Nuitka==2.1")
+ self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml")
+ equ_base = "--quiet --noinclude-qt-translations"
+ equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
+ self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value)
+ self.assertEqual(
+ config_obj.get_value("qt", "excluded_qml_plugins"),
+ "QtCharts,QtQuick3D,QtSensors,QtTest,QtWebEngine",
+ )
+ expected_modules = {"Core", "Gui", "Qml", "Quick", "Network", "OpenGL", "QmlModels"}
+ if sys.platform != "win32":
+ expected_modules.add("DBus")
+ obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
+ self.assertEqual(obtained_modules, expected_modules)
+ obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",")
+ self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort())
+ self.config_file.unlink()
+
+ def testQmlDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
+ with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
+ mock_qmlimportscanner.return_value = ["QtQuick"]
+ original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
+ self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertEqual(mock_qmlimportscanner.call_count, 1)
+
+ def testMainFileDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
+ with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
+ mock_qmlimportscanner.return_value = ["QtQuick"]
+ original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True)
+ self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertEqual(mock_qmlimportscanner.call_count, 1)
+
+
+@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
+ "Test only works on macOS version 12+")
+class TestPySide6DeployWebEngine(DeployTestBase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ example_webenginequick = cls.example_root / "webenginequick" / "nanobrowser"
+ cls.temp_example_webenginequick = Path(
+ shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser")
+ ).resolve()
+
+ @patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testWebEngineQuickDryRun(self, mock_sitepackages, mock_plugins):
+ mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "networkaccess", "networkinformation",
+ "platforminputcontexts", "platforms", "platforms/darwin",
+ "platformthemes", "qmltooling", "scenegraph", "tls",
+ "xcbglintegrations"]
+ mock_plugins.return_value = all_plugins
+ # this test case retains the QtWebEngine dlls
+ # setup
+ os.chdir(self.temp_example_webenginequick)
+ main_file = self.temp_example_webenginequick / "quicknanobrowser.py"
+ deployment_files = self.temp_example_webenginequick / "deployment"
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts,"
+ "platforms/darwin,qml,qmltooling,scenegraph")
+ qml_files = [
+ "ApplicationRoot.qml",
+ "BrowserDialog.qml",
+ "BrowserWindow.qml",
+ "DownloadView.qml",
+ "FindBar.qml",
+ "FullScreenNotification.qml",
+ ]
+ data_files_cmd = " ".join(
+ [
+ f"--include-data-files={str(self.temp_example_webenginequick / file)}=./{file}"
+ for file in qml_files
+ ]
+ )
+ expected_run_cmd = (
+ f"{sys.executable} -m nuitka {str(main_file)} --follow-imports"
+ f" --enable-plugin=pyside6 --output-dir={str(deployment_files)} --quiet"
+ f" --noinclude-qt-translations --include-qt-plugins=all"
+ f" {data_files_cmd}"
+ f" --include-qt-plugins={plugins_nuitka}"
+ f" {self.dlls_ignore_nuitka}"
+ " --noinclude-dlls=*/qml/QtQuickEffectMaker/*"
+ )
+
+ if sys.platform != "win32":
+ expected_run_cmd += (
+ " --noinclude-dlls=libQt6Charts*"
+ " --noinclude-dlls=libQt6Quick3D* --noinclude-dlls=libQt6Sensors*"
+ " --noinclude-dlls=libQt6Test*"
+ )
+ else:
+ expected_run_cmd += (
+ " --noinclude-dlls=Qt6Charts*"
+ " --noinclude-dlls=Qt6Quick3D* --noinclude-dlls=Qt6Sensors*"
+ " --noinclude-dlls=Qt6Test*"
+ )
+
+ if sys.platform.startswith("linux"):
+ expected_run_cmd += f" --linux-icon={str(self.linux_icon)} --onefile"
+ elif sys.platform == "darwin":
+ expected_run_cmd += (f" --macos-app-icon={str(self.macos_icon)}"
+ " --macos-create-app-bundle --standalone")
+ elif sys.platform == "win32":
+ expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)} --onefile"
+
+ config_file = self.temp_example_webenginequick / "pysidedeploy.spec"
+
+ # create config file
+ with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
+ mock_qmlimportscanner.return_value = ["QtQuick", "QtWebEngine"]
+ init_result = self.deploy.main(main_file, init=True, force=True)
+ self.assertEqual(init_result, None)
+
+ # run dry_run
+ original_output = self.deploy.main(main_file, dry_run=True, force=True)
+ self.assertTrue(original_output, expected_run_cmd)
+ self.assertEqual(mock_qmlimportscanner.call_count, 2)
+
+ # test config file contents
+ config_obj = self.deploy_lib.BaseConfig(config_file=config_file)
+ self.assertEqual(config_obj.get_value("app", "input_file"), "quicknanobrowser.py")
+ self.assertEqual(config_obj.get_value("qt", "qml_files"), ",".join(qml_files))
+ self.assertEqual(
+ config_obj.get_value("qt", "excluded_qml_plugins"),
+ "QtCharts,QtQuick3D,QtSensors,QtTest",
+ )
+ expected_modules = {"Core", "Gui", "Quick", "Qml", "WebEngineQuick", "Network", "OpenGL",
+ "Positioning", "WebEngineCore", "WebChannel", "WebChannelQuick",
+ "QmlModels"}
+ if sys.platform != "win32":
+ expected_modules.add("DBus")
+ obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
+ self.assertEqual(obtained_modules, expected_modules)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/sources/pyside6/tests/tools/pyside6-qml/CMakeLists.txt b/sources/pyside6/tests/tools/pyside6-qml/CMakeLists.txt
new file mode 100644
index 000000000..4d801264a
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-qml/CMakeLists.txt
@@ -0,0 +1 @@
+PYSIDE_TEST(test_pyside6_qml.py)
diff --git a/sources/pyside6/tests/tools/pyside6-qml/test_pyside6_qml.py b/sources/pyside6/tests/tools/pyside6-qml/test_pyside6_qml.py
new file mode 100644
index 000000000..fdaf3d471
--- /dev/null
+++ b/sources/pyside6/tests/tools/pyside6-qml/test_pyside6_qml.py
@@ -0,0 +1,75 @@
+# Copyright (C) 2018 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
+
+"""Test for pyside6-qml
+
+The tests does a unittest and some integration tests for pyside6-qml."""
+
+from asyncio.subprocess import PIPE
+import os
+import sys
+import unittest
+import subprocess
+import importlib.util
+
+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)
+
+
+class TestPySide6QmlUnit(unittest.TestCase):
+ def setUp(self) -> None:
+ super().setUp()
+ self._dir = Path(__file__).parent.resolve()
+ self.pyside_root = self._dir.parents[4]
+
+ self.pyqml_path = self.pyside_root / "sources" / "pyside-tools" / "qml.py"
+ self.core_qml_path = (self.pyside_root / "examples" / "qml" /
+ "tutorials" / "extending-qml-advanced" / "adding")
+
+ self.pyqml_run_cmd = [sys.executable, os.fspath(self.pyqml_path)]
+
+ # self.pyqml_path will not abe able to find pyside and other related binaries, if not added
+ # to path explicitly. The following lines does that.
+ self.test_env = os.environ.copy()
+ self.test_env["PYTHONPATH"] = os.pathsep + os.pathsep.join(sys.path)
+
+ def testImportQmlModules(self):
+
+ # because pyside-tools has a hyphen, a normal 'from pyside-tools import qml' cannot be done
+ spec = importlib.util.spec_from_file_location("qml", self.pyqml_path)
+ pyqml = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(pyqml)
+ pyqml.import_qml_modules(self.core_qml_path)
+
+ # path added to sys.path
+ self.assertIn(str(self.core_qml_path), sys.path)
+
+ # module is imported
+ self.assertIn("person", sys.modules.keys())
+
+ # remove the imported modules
+ sys.path.remove(str(self.core_qml_path))
+ del sys.modules["person"]
+
+ # test with module_paths - dir
+ self.person_path = self.core_qml_path / "person.py"
+ pyqml.import_qml_modules(self.core_qml_path, module_paths=[self.core_qml_path])
+ self.assertIn(str(self.core_qml_path), sys.path)
+ self.assertIn("person", sys.modules.keys())
+
+ # test with module_paths - file - in testCoreApplication(self)
+
+ def testCoreApplication(self):
+ self.pyqml_run_cmd.extend(["--apptype", "core"])
+ self.pyqml_run_cmd.append(str(self.core_qml_path / "People" / "Main.qml"))
+ self.pyqml_run_cmd.extend(["-I", str(self.core_qml_path / "person.py")])
+
+ result = subprocess.run(self.pyqml_run_cmd, stdout=PIPE, env=self.test_env)
+ self.assertEqual(result.returncode, 0)
+ self.assertEqual(result.stdout.rstrip(), b"{'_name': 'Bob Jones', '_shoe_size': 12}")
+
+
+if __name__ == '__main__':
+ unittest.main()