From 71f576f5939b2c84ada789f66f939e141409dfd0 Mon Sep 17 00:00:00 2001 From: Russell Martin Date: Thu, 25 May 2023 08:46:54 -0400 Subject: [PATCH 1/2] Enable new tarfile unpacking protections in Python 3.12 --- changes/1290.misc.rst | 1 + pyproject.toml | 1 + src/briefcase/commands/create.py | 10 ++++++++++ src/briefcase/integrations/android_sdk.py | 7 ++++++- .../create/test_install_app_support_package.py | 3 +++ .../AndroidSDK/test_verify_emulator_skin.py | 3 +++ 6 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 changes/1290.misc.rst diff --git a/changes/1290.misc.rst b/changes/1290.misc.rst new file mode 100644 index 000000000..e864781a2 --- /dev/null +++ b/changes/1290.misc.rst @@ -0,0 +1 @@ +The test suite is now compatible with Python 3.12. diff --git a/pyproject.toml b/pyproject.toml index cc1acc85e..362a55ddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ no-cover-if-is-macos = "'darwin' == os_environ.get('COVERAGE_PLATFORM', sys_plat no-cover-if-not-macos = "'darwin' != os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" no-cover-if-is-windows = "'win32' == os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" no-cover-if-not-windows = "'win32' != os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" +no-cover-if-lt-py312 = "sys_version_info < (3, 12) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-is-py310 = "python_version == '3.10' and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-lt-py310 = "sys_version_info < (3, 10) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-gte-py310 = "sys_version_info > (3, 10) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py index 3a165c7ca..7d7585bed 100644 --- a/src/briefcase/commands/create.py +++ b/src/briefcase/commands/create.py @@ -263,12 +263,22 @@ def _unpack_support_package(self, support_file_path, support_path): :param support_file_path: The path to the support file to be unpacked. :param support_path: The path where support files should be unpacked. """ + # Additional protections for unpacking tar files were introduced in Python 3.12. + # This enables the behavior that will be the default in Python 3.14. + # However, the protections can only be enabled for tar files...not zip files. + is_zip = support_file_path.name.endswith("zip") + if sys.version_info >= (3, 12) and not is_zip: # pragma: no-cover-if-lt-py312 + tarfile_kwargs = {"filter": "data"} + else: + tarfile_kwargs = {} + try: with self.input.wait_bar("Unpacking support package..."): support_path.mkdir(parents=True, exist_ok=True) self.tools.shutil.unpack_archive( support_file_path, extract_dir=support_path, + **tarfile_kwargs, ) except (shutil.ReadError, EOFError) as e: raise InvalidSupportPackage(support_file_path) from e diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py index ef3d7813d..144ad8025 100644 --- a/src/briefcase/integrations/android_sdk.py +++ b/src/briefcase/integrations/android_sdk.py @@ -6,6 +6,7 @@ import shlex import shutil import subprocess +import sys import time from contextlib import suppress from datetime import datetime @@ -806,7 +807,11 @@ def verify_emulator_skin(self, skin: str): # Unpack skin archive with self.tools.input.wait_bar("Installing device skin..."): try: - self.tools.shutil.unpack_archive(skin_tgz_path, extract_dir=skin_path) + self.tools.shutil.unpack_archive( + skin_tgz_path, + extract_dir=skin_path, + **({"filter": "data"} if sys.version_info >= (3, 12) else {}), + ) except (shutil.ReadError, EOFError) as err: raise BriefcaseCommandError( f"Unable to unpack {skin} device skin." diff --git a/tests/commands/create/test_install_app_support_package.py b/tests/commands/create/test_install_app_support_package.py index 4b286ae95..3d7205ba7 100644 --- a/tests/commands/create/test_install_app_support_package.py +++ b/tests/commands/create/test_install_app_support_package.py @@ -1,5 +1,6 @@ import os import shutil +import sys from unittest import mock import pytest @@ -55,6 +56,7 @@ def test_install_app_support_package( create_command.tools.shutil.unpack_archive.assert_called_with( tmp_path / "data" / "support" / "Python-3.X-tester-support.b37.tar.gz", extract_dir=support_path, + **({"filter": "data"} if sys.version_info >= (3, 12) else {}), ) # Confirm that the full path to the support file @@ -100,6 +102,7 @@ def test_install_pinned_app_support_package( create_command.tools.shutil.unpack_archive.assert_called_with( tmp_path / "data" / "support" / "Python-3.X-Tester-support.b42.tar.gz", extract_dir=support_path, + **({"filter": "data"} if sys.version_info >= (3, 12) else {}), ) # Confirm that the full path to the support file diff --git a/tests/integrations/android_sdk/AndroidSDK/test_verify_emulator_skin.py b/tests/integrations/android_sdk/AndroidSDK/test_verify_emulator_skin.py index 14bb070d4..c67c4c000 100644 --- a/tests/integrations/android_sdk/AndroidSDK/test_verify_emulator_skin.py +++ b/tests/integrations/android_sdk/AndroidSDK/test_verify_emulator_skin.py @@ -1,3 +1,4 @@ +import sys from pathlib import Path from unittest.mock import MagicMock @@ -40,6 +41,7 @@ def test_new_skin(mock_tools, android_sdk): mock_tools.shutil.unpack_archive.assert_called_once_with( skin_tgz_path, extract_dir=android_sdk.root_path / "skins" / "pixel_X", + **({"filter": "data"} if sys.version_info >= (3, 12) else {}), ) # Original file was deleted. @@ -102,6 +104,7 @@ def test_unpack_failure(mock_tools, android_sdk, tmp_path): mock_tools.shutil.unpack_archive.assert_called_once_with( skin_tgz_path, extract_dir=android_sdk.root_path / "skins" / "pixel_X", + **({"filter": "data"} if sys.version_info >= (3, 12) else {}), ) # Original file wasn't deleted. From fc7f648cdf04fb477c437be7dea082afe46ce54a Mon Sep 17 00:00:00 2001 From: Russell Martin Date: Thu, 31 Aug 2023 23:25:08 -0400 Subject: [PATCH 2/2] Suppress CI warnings for Python 3.12 - python-dateutil uses the deprecated datetime.utcfromtimestamp(); its next release after 2.8.2 will no longer use this function. - Missing coverage is being falsely reported for an exit from a finally block; the coverage exclusion can be removed with python/cpython#105658. --- pyproject.toml | 3 +++ src/briefcase/platforms/macOS/__init__.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 362a55ddf..f8b256e24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ no-cover-if-is-macos = "'darwin' == os_environ.get('COVERAGE_PLATFORM', sys_plat no-cover-if-not-macos = "'darwin' != os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" no-cover-if-is-windows = "'win32' == os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" no-cover-if-not-windows = "'win32' != os_environ.get('COVERAGE_PLATFORM', sys_platform) and os_environ.get('COVERAGE_EXCLUDE_PLATFORM') != 'disable'" +no-cover-if-is-py312 = "python_version == '3.12' and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-lt-py312 = "sys_version_info < (3, 12) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-is-py310 = "python_version == '3.10' and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" no-cover-if-lt-py310 = "sys_version_info < (3, 10) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'" @@ -52,6 +53,8 @@ multi_line_output = 3 testpaths = ["tests"] filterwarnings = [ "error", + # suppress until python-dateutil>2.8.2 is released (https://github.com/dateutil/dateutil/issues/1284) + "ignore:.*datetime.utcfromtimestamp\\(\\) is deprecated.*:DeprecationWarning:", ] # need to ensure build directories aren't excluded from recursion diff --git a/src/briefcase/platforms/macOS/__init__.py b/src/briefcase/platforms/macOS/__init__.py index fd547ce94..065c358e3 100644 --- a/src/briefcase/platforms/macOS/__init__.py +++ b/src/briefcase/platforms/macOS/__init__.py @@ -575,7 +575,8 @@ def notarize(self, filename, team_id): finally: # Clean up house; we don't need the archive anymore. if archive_filename != filename: - self.tools.os.unlink(archive_filename) + # coverage exclusion required for https://github.com/python/cpython/issues/105658 + self.tools.os.unlink(archive_filename) # no-cover-if-is-py312 try: self.logger.info()