aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py')
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py321
1 files changed, 236 insertions, 85 deletions
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 f730eed40..46b644584 100644
--- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
+++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
@@ -7,49 +7,65 @@ import shutil
import sys
import os
import importlib
+import platform
from pathlib import Path
-from configparser import ConfigParser
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)
-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")
+ if pyenv_root and (resolved_exe := str(Path(sys.executable).resolve())):
+ return resolved_exe.startswith(pyenv_root)
+ return False
-class TestPySide6Deploy(unittest.TestCase):
+
+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()
- 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.example_root = cls.pyside_root / "examples"
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"))
+ 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
@@ -57,89 +73,188 @@ class TestPySide6Deploy(unittest.TestCase):
# print no outputs to stdout
sys.stdout = mock.MagicMock()
- def setUpWidgets(self):
+ @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 --onefile"
+ 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=True"
+ 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-onefile-icon={str(self.linux_onefile_icon)}"
+ 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):
+ 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.
- self.setUpWidgets()
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
self.assertEqual(original_output, self.expected_run_cmd)
- self.config_file.unlink()
- def testWidgetConfigFile(self):
+ @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
-
- self.setUpWidgets()
# init
init_result = self.deploy.main(self.main_file, init=True, force=True)
self.assertEqual(init_result, None)
# test with config
- config_path = self.temp_example_widgets / "pysidedeploy.spec"
- original_output = self.deploy.main(config_file=config_path, dry_run=True, force=True)
+ 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 = 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"), ".")
- self.assertEqual(config_obj.get_value("python", "packages"), "nuitka,ordered_set,zstandard")
+ self.assertEqual(config_obj.get_value("python", "packages"),
+ "Nuitka==2.3.2")
self.assertEqual(config_obj.get_value("qt", "qml_files"), "")
- self.assertEqual(
- config_obj.get_value("nuitka", "extra_args"), "--quiet --noinclude-qt-translations=True"
- )
+ 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 setUpQml(self):
+ 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))
+
+ def testStandaloneMode(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
+ # remove --onefile from self.expected_run_cmd and replace it with --standalone
+ self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone")
+ # test standalone mode
+ original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True,
+ force=True)
+
+ self.assertEqual(original_output, self.expected_run_cmd)
+
+
+@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 --onefile"
+ 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=True --include-qt-plugins=all"
+ 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"
- )
+ 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-onefile-icon={str(self.linux_onefile_icon)}"
+ 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"
- def testQmlConfigFile(self):
- self.setUpQml()
-
+ @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"]
@@ -147,45 +262,75 @@ class TestPySide6Deploy(unittest.TestCase):
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"), ".")
- self.assertEqual(config_obj.get_value("python", "packages"), "nuitka,ordered_set,zstandard")
+ self.assertEqual(config_obj.get_value("python", "packages"),
+ "Nuitka==2.3.2")
self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml")
- self.assertEqual(
- config_obj.get_value("nuitka", "extra_args"), "--quiet --noinclude-qt-translations=True"
- )
+ 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):
- self.setUpQml()
+ 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)
- self.config_file.unlink()
- def testMainFileDryRun(self):
- self.setUpQml()
+ 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)
- self.config_file.unlink()
- # this test case retains the QtWebEngine dlls
- def testWebEngineQuickDryRun(self):
+
+@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",
@@ -196,15 +341,18 @@ class TestPySide6Deploy(unittest.TestCase):
]
data_files_cmd = " ".join(
[
- f"--include-data-files={str(self.temp_example_webenginequick/file)}=./{file}"
+ 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 --onefile"
+ f"{sys.executable} -m nuitka {str(main_file)} --follow-imports"
f" --enable-plugin=pyside6 --output-dir={str(deployment_files)} --quiet"
- f" --noinclude-qt-translations=True --include-qt-plugins=all"
+ 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":
@@ -212,16 +360,21 @@ class TestPySide6Deploy(unittest.TestCase):
" --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-onefile-icon={str(self.linux_onefile_icon)}"
+ 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"
@@ -234,25 +387,23 @@ class TestPySide6Deploy(unittest.TestCase):
# 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, 1)
+ 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(
config_obj.get_value("qt", "excluded_qml_plugins"),
"QtCharts,QtQuick3D,QtSensors,QtTest",
)
- config_file.unlink()
-
- def tearDown(self) -> None:
- super().tearDown()
- os.chdir(self.current_dir)
-
- @classmethod
- def tearDownClass(cls) -> None:
- shutil.rmtree(Path(cls.temp_dir))
+ 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__":