diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 8e437e5cb2b90c..955e540f96e627 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -5,6 +5,8 @@ import pathlib import posixpath import struct +import subprocess +import sys import time import unittest import zipfile @@ -2443,6 +2445,44 @@ def build_alpharep_fixture(): return zf +class TestExecutablePrependedZip(unittest.TestCase): + """Test our ability to open zip files with an executable prepended.""" + + def setUp(self): + self.exe_zip = findfile('exe_with_zip', subdir='ziptestdata') + self.exe_zip64 = findfile('exe_with_z64', subdir='ziptestdata') + + def _test_zip_works(self, name): + # bpo-28494 sanity check: ensure is_zipfile works on these. + self.assertTrue(zipfile.is_zipfile(name), + f'is_zipfile failed on {name}') + # Ensure we can operate on these via ZipFile. + with zipfile.ZipFile(name) as zipfp: + for n in zipfp.namelist(): + data = zipfp.read(n) + self.assertIn(b'FAVORITE_NUMBER', data) + + def test_read_zip_with_exe_prepended(self): + self._test_zip_works(self.exe_zip) + + def test_read_zip64_with_exe_prepended(self): + self._test_zip_works(self.exe_zip64) + + @unittest.skipUnless(sys.executable, 'sys.executable required.') + @unittest.skipUnless(os.access('/bin/bash', os.X_OK), + 'Test relies on #!/bin/bash working.') + def test_execute_zip2(self): + output = subprocess.check_output([self.exe_zip, sys.executable]) + self.assertIn(b'number in executable: 5', output) + + @unittest.skipUnless(sys.executable, 'sys.executable required.') + @unittest.skipUnless(os.access('/bin/bash', os.X_OK), + 'Test relies on #!/bin/bash working.') + def test_execute_zip64(self): + output = subprocess.check_output([self.exe_zip64, sys.executable]) + self.assertIn(b'number in executable: 5', output) + + class TestPath(unittest.TestCase): def setUp(self): self.fixtures = contextlib.ExitStack() diff --git a/Lib/test/ziptestdata/README.md b/Lib/test/ziptestdata/README.md new file mode 100644 index 00000000000000..6b9147db76e178 --- /dev/null +++ b/Lib/test/ziptestdata/README.md @@ -0,0 +1,35 @@ +# Test data for `test_zipfile` + +The test executables in this directory are created manually from header.sh and +the `testdata_module_inside_zip.py` file. You must have infozip's zip utility +installed (`apt install zip` on Debian). + +## Purpose + +These are used to test executable files with an appended zipfile, in a scenario +where the executable is _not_ a Python interpreter itself so our automatic +zipimport machinery (that'd look for `__main__.py`) is not being used. + +## Updating the test executables + +If you update header.sh or the testdata_module_inside_zip.py file, rerun the +commands below. These are expected to be rarely changed, if ever. + +### Standard old format (2.0) zip file + +``` +zip -0 zip2.zip testdata_module_inside_zip.py +cat header.sh zip2.zip >exe_with_zip +rm zip2.zip +``` + +### Modern format (4.5) zip64 file + +Redirecting from stdin forces infozip's zip tool to create a zip64. + +``` +zip -0 zip64.zip +cat header.sh zip64.zip >exe_with_z64 +rm zip64.zip +``` + diff --git a/Lib/test/ziptestdata/exe_with_z64 b/Lib/test/ziptestdata/exe_with_z64 new file mode 100755 index 00000000000000..82b03cf39d919d Binary files /dev/null and b/Lib/test/ziptestdata/exe_with_z64 differ diff --git a/Lib/test/ziptestdata/exe_with_zip b/Lib/test/ziptestdata/exe_with_zip new file mode 100755 index 00000000000000..c833cdf9f934b0 Binary files /dev/null and b/Lib/test/ziptestdata/exe_with_zip differ diff --git a/Lib/test/ziptestdata/header.sh b/Lib/test/ziptestdata/header.sh new file mode 100755 index 00000000000000..52dc91acf7400c --- /dev/null +++ b/Lib/test/ziptestdata/header.sh @@ -0,0 +1,24 @@ +#!/bin/bash +INTERPRETER_UNDER_TEST="$1" +if [[ ! -x "${INTERPRETER_UNDER_TEST}" ]]; then + echo "Interpreter must be the command line argument." + exit 4 +fi +EXECUTABLE="$0" exec "${INTERPRETER_UNDER_TEST}" -E - <