Skip to content

Commit

Permalink
bpo-28494: Test existing zipfile working behavior. (pythonGH-15853)
Browse files Browse the repository at this point in the history
Add unittests for executables with a zipfile appended to test_zipfile, as zipfile.is_zipfile and zipfile.ZipFile work properly on these today.
  • Loading branch information
gpshead authored and Yhg1s committed Sep 10, 2019
1 parent afdeb18 commit 3f4db4a
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 0 deletions.
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 @@ -2470,6 +2472,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

0 comments on commit 3f4db4a

Please sign in to comment.