diff options
| author | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2023-10-10 15:23:03 +0200 |
|---|---|---|
| committer | Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> | 2023-12-08 16:17:43 +0100 |
| commit | 56aeec46a03df8cdee14b99b0389e8363bd608fc (patch) | |
| tree | 5fb086cba659efb9eaa508312a5fa96e79215c0a /sources/pyside6 | |
| parent | 20a36aedeaece97502d33862c4736d2cd1b735ef (diff) | |
Deployment: Update Tests
- Use existing `BaseConfig` class instead of created a new class based
on 'Configparser' for parsing .spec files.
- Update and add Android deployment tests to CI.
Pick-to: 6.6
Task-number: PYSIDE-1612
Change-Id: I32cd16e08781c71fb534bbfe7e3726818475366b
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'sources/pyside6')
3 files changed, 214 insertions, 122 deletions
diff --git a/sources/pyside6/tests/CMakeLists.txt b/sources/pyside6/tests/CMakeLists.txt index e18b62543..c581a8d0f 100644 --- a/sources/pyside6/tests/CMakeLists.txt +++ b/sources/pyside6/tests/CMakeLists.txt @@ -54,6 +54,10 @@ add_subdirectory(support) add_subdirectory(tools/metaobjectdump) add_subdirectory(tools/pyside6-deploy) +if(UNIX AND NOT APPLE) + add_subdirectory(tools/pyside6-android-deploy) +endif() + if (NOT DISABLE_QtQuick) add_subdirectory(tools/pyside6-qml) endif() 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 index 5801c6fdc..47b8bf65d 100644 --- 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 @@ -1,45 +1,47 @@ # 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 unittest -import tempfile +import importlib +import os +import re import shutil import sys -import os -import importlib +import tempfile +import unittest +import subprocess from pathlib import Path -from configparser import ConfigParser -from unittest.mock import patch from unittest import mock +from unittest.mock import patch - -class ConfigFile: - def __init__(self, config_file: Path) -> None: - self.config_file = config_file - self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True) - self.parser.read(self.config_file) - - def get_value(self, section: str, key: str): - return str(self.parser.get(section, key)) +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 TestPySide6AndroidDeploy(unittest.TestCase): +class DeployTestBase(unittest.TestCase): @classmethod def setUpClass(cls): cls.pyside_root = Path(__file__).parents[5].resolve() cls.example_root = cls.pyside_root / "examples" - example_widget_application = cls.example_root / "gui" / "analogclock" cls.temp_dir = tempfile.mkdtemp() - cls.temp_example = Path( - shutil.copytree(example_widget_application, Path(cls.temp_dir) / "analogclock") - ).resolve() 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") - - sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools")) + 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 @@ -50,40 +52,66 @@ class TestPySide6AndroidDeploy(unittest.TestCase): # 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("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_dry_run(self, mock_jar, mock_wheel): - mock_wheel.version = "6.5.0a1" - - # test if dry_run works without errors + def test_dry_run(self, mock_extract_jar): self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, dry_run=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertFalse(self.config_file.exists()) - - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config(self, mock_jar, mock_wheel): - ''' - Tests config options from the dynamically created buildozer.spec and pysidedeploy.spec - ''' - mock_wheel.version = "6.5.0a1" + pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, + dry_run=True, force=True) + self.assertEqual(mock_extract_jar.call_count, 0) + + @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars") + @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist") + @patch("deploy_lib.android.buildozer.BuildozerConfig." + "_BuildozerConfig__find_dependent_qt_modules") + @patch("deploy_lib.android.buildozer.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, init=True, force=True) + pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, + init=True, force=True, keep_deployment_files=True) - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) + 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 = ConfigFile(config_file=self.config_file) + 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") @@ -93,84 +121,144 @@ class TestPySide6AndroidDeploy(unittest.TestCase): str(self.shiboken_wheel.resolve())) self.assertEqual(config_obj.get_value("buildozer", "mode"), "debug") self.assertEqual(config_obj.get_value("buildozer", "recipe_dir"), - str(self.temp_example / "deployment" / "recipes")) + '') self.assertEqual(config_obj.get_value("buildozer", "jars_dir"), - str(self.temp_example / "deployment" / "jar" / "PySide6" / "jar")) - self.assertEqual(config_obj.get_value("buildozer", "ndk_path"), "") - self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), "") - self.assertEqual(config_obj.get_value("buildozer", "modules"), "Core,Gui,Widgets") + 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("buildozer", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + expected_local_libs = "plugins_platforms_qtforandroid" self.assertEqual(config_obj.get_value("buildozer", "local_libs"), - "plugins_platforms_qtforandroid") + expected_local_libs) self.assertEqual(config_obj.get_value("buildozer", "arch"), "x86_64") - self.config_file.unlink() - - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config_with_ndk_sdk(self, mock_jar, mock_wheel): - mock_wheel.version = "6.5.0a1" - self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, - sdk_path=self.sdk_path, init=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertTrue(self.config_file.exists()) + # 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) - # test config file contents - config_obj = ConfigFile(config_file=self.config_file) - self.assertEqual(config_obj.get_value("buildozer", "ndk_path"), - str(self.ndk_path.resolve())) - self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), - str(self.sdk_path.resolve())) self.config_file.unlink() + self.buildozer_config.unlink() - def test_error_pwd_not_projectdir(self): - os.chdir(self.current_dir) - with self.assertRaises(RuntimeError): + 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, init=True, force=True) + pyside_wheel=self.pyside_wheel, force=True) + self.assertTrue("Unable to find Android NDK" in str(context.exception)) - def test_error_no_wheels(self): + # test when cwd() is not project_dir os.chdir(self.current_dir) - with self.assertRaises(RuntimeError): - self.android_deploy.main(name="android_app", shiboken_wheel=None, + 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("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config_with_Qml(self, mock_jar, mock_wheel): - example_qml_application = self.example_root / "quick" / "models" / "stringlistmodel" - temp_qml_example = Path( - shutil.copytree(example_qml_application, Path(self.temp_dir) / "stringlistmodel") + +@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() - config_file = temp_qml_example / "pysidedeploy.spec" - (temp_qml_example / "stringlistmodel.py").rename(temp_qml_example / "main.py") - (temp_qml_example / "stringlistmodel.pyproject").unlink() - os.chdir(temp_qml_example) - mock_wheel.version = "6.5.0a1" + 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.buildozer.BuildozerConfig._BuildozerConfig__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.buildozer.BuildozerConfig." + "_BuildozerConfig__find_dependent_qt_modules") + @patch("deploy_lib.android.buildozer.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, init=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertTrue(config_file.exists()) + 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()) # test config file contents - config_obj = ConfigFile(config_file=config_file) - self.assertEqual(config_obj.get_value("buildozer", "modules"), - "Core,Gui,Widgets,Network,OpenGL,Qml,Quick,QuickControls2") - config_file.unlink() - - def tearDown(self) -> None: - super().tearDown() - os.chdir(self.current_dir) + 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("buildozer", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + expected_local_libs = "plugins_platforms_qtforandroid" + 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("qt", "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, '') - @classmethod - def tearDownClass(cls) -> None: - shutil.rmtree(Path(cls.temp_dir)) + self.config_file.unlink() + self.buildozer_config_file.unlink() if __name__ == "__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 index bed4ea14f..565aed997 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -8,21 +8,10 @@ import sys import os import importlib from pathlib import Path -from configparser import ConfigParser from unittest.mock import patch from unittest import mock -class ConfigFile: - def __init__(self, config_file: Path) -> None: - self.config_file = config_file - self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True) - self.parser.read(self.config_file) - - def get_value(self, section: str, key: str): - return str(self.parser.get(section, key)) - - def is_pyenv_python(): pyenv_root = os.environ.get("PYENV_ROOT") @@ -133,7 +122,7 @@ class TestPySide6DeployWidgets(DeployTestBase): self.assertEqual(original_output, self.expected_run_cmd) # # test config file contents - config_obj = ConfigFile(config_file=self.config_file) + 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"), ".") @@ -145,6 +134,13 @@ class TestPySide6DeployWidgets(DeployTestBase): self.assertEqual(config_obj.get_value("qt", "excluded_qml_plugins"), "") self.config_file.unlink() + def testErrorReturns(self): + # 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)) + class TestPySide6DeployQml(DeployTestBase): @classmethod @@ -202,7 +198,7 @@ class TestPySide6DeployQml(DeployTestBase): self.assertEqual(init_result, None) # test config file contents - config_obj = ConfigFile(config_file=self.config_file) + 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"), ".") @@ -283,6 +279,10 @@ class TestPySide6DeployWebEngine(DeployTestBase): if sys.platform.startswith("linux"): expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + elif sys.platform == "darwin": + expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + elif sys.platform == "win32": + expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" config_file = self.temp_example_webenginequick / "pysidedeploy.spec" @@ -298,7 +298,7 @@ class TestPySide6DeployWebEngine(DeployTestBase): self.assertEqual(mock_qmlimportscanner.call_count, 2) # test config file contents - config_obj = ConfigFile(config_file=config_file) + 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( |
