Skip to content

[3.8] bpo-28494: Test existing zipfile working behavior. (GH-15853) #15891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Lib/test/test_zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import pathlib
import posixpath
import struct
import subprocess
import sys
import time
import unittest
import zipfile
Expand Down Expand Up @@ -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()
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/ziptestdata/README.md
Original file line number Diff line number Diff line change
@@ -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 <testdata_module_inside_zip.py >zip64.zip
cat header.sh zip64.zip >exe_with_z64
rm zip64.zip
```

Binary file added Lib/test/ziptestdata/exe_with_z64
Binary file not shown.
Binary file added Lib/test/ziptestdata/exe_with_zip
Binary file not shown.
24 changes: 24 additions & 0 deletions Lib/test/ziptestdata/header.sh
Original file line number Diff line number Diff line change
@@ -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 - <<END_OF_PYTHON
import os
import zipfile

namespace = {}

filename = os.environ['EXECUTABLE']
print(f'Opening {filename} as a zipfile.')
with zipfile.ZipFile(filename, mode='r') as exe_zip:
for file_info in exe_zip.infolist():
data = exe_zip.read(file_info)
exec(data, namespace, namespace)
break # Only use the first file in the archive.

print('Favorite number in executable:', namespace["FAVORITE_NUMBER"])

### Archive contents will be appended after this file. ###
END_OF_PYTHON
2 changes: 2 additions & 0 deletions Lib/test/ziptestdata/testdata_module_inside_zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Test data file to be stored within a zip file.
FAVORITE_NUMBER = 5