Skip to content

Commit 74b0291

Browse files
bpo-28494: Test existing zipfile working behavior. (GH-15853)
Add unittests for executables with a zipfile appended to test_zipfile, as zipfile.is_zipfile and zipfile.ZipFile work properly on these today. (cherry picked from commit 3f4db4a) Co-authored-by: Gregory P. Smith <[email protected]>
1 parent 98224d2 commit 74b0291

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

Lib/test/test_zipfile.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import pathlib
66
import posixpath
77
import struct
8+
import subprocess
9+
import sys
810
import time
911
import unittest
1012
import zipfile
@@ -2443,6 +2445,44 @@ def build_alpharep_fixture():
24432445
return zf
24442446

24452447

2448+
class TestExecutablePrependedZip(unittest.TestCase):
2449+
"""Test our ability to open zip files with an executable prepended."""
2450+
2451+
def setUp(self):
2452+
self.exe_zip = findfile('exe_with_zip', subdir='ziptestdata')
2453+
self.exe_zip64 = findfile('exe_with_z64', subdir='ziptestdata')
2454+
2455+
def _test_zip_works(self, name):
2456+
# bpo-28494 sanity check: ensure is_zipfile works on these.
2457+
self.assertTrue(zipfile.is_zipfile(name),
2458+
f'is_zipfile failed on {name}')
2459+
# Ensure we can operate on these via ZipFile.
2460+
with zipfile.ZipFile(name) as zipfp:
2461+
for n in zipfp.namelist():
2462+
data = zipfp.read(n)
2463+
self.assertIn(b'FAVORITE_NUMBER', data)
2464+
2465+
def test_read_zip_with_exe_prepended(self):
2466+
self._test_zip_works(self.exe_zip)
2467+
2468+
def test_read_zip64_with_exe_prepended(self):
2469+
self._test_zip_works(self.exe_zip64)
2470+
2471+
@unittest.skipUnless(sys.executable, 'sys.executable required.')
2472+
@unittest.skipUnless(os.access('/bin/bash', os.X_OK),
2473+
'Test relies on #!/bin/bash working.')
2474+
def test_execute_zip2(self):
2475+
output = subprocess.check_output([self.exe_zip, sys.executable])
2476+
self.assertIn(b'number in executable: 5', output)
2477+
2478+
@unittest.skipUnless(sys.executable, 'sys.executable required.')
2479+
@unittest.skipUnless(os.access('/bin/bash', os.X_OK),
2480+
'Test relies on #!/bin/bash working.')
2481+
def test_execute_zip64(self):
2482+
output = subprocess.check_output([self.exe_zip64, sys.executable])
2483+
self.assertIn(b'number in executable: 5', output)
2484+
2485+
24462486
class TestPath(unittest.TestCase):
24472487
def setUp(self):
24482488
self.fixtures = contextlib.ExitStack()

Lib/test/ziptestdata/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Test data for `test_zipfile`
2+
3+
The test executables in this directory are created manually from header.sh and
4+
the `testdata_module_inside_zip.py` file. You must have infozip's zip utility
5+
installed (`apt install zip` on Debian).
6+
7+
## Purpose
8+
9+
These are used to test executable files with an appended zipfile, in a scenario
10+
where the executable is _not_ a Python interpreter itself so our automatic
11+
zipimport machinery (that'd look for `__main__.py`) is not being used.
12+
13+
## Updating the test executables
14+
15+
If you update header.sh or the testdata_module_inside_zip.py file, rerun the
16+
commands below. These are expected to be rarely changed, if ever.
17+
18+
### Standard old format (2.0) zip file
19+
20+
```
21+
zip -0 zip2.zip testdata_module_inside_zip.py
22+
cat header.sh zip2.zip >exe_with_zip
23+
rm zip2.zip
24+
```
25+
26+
### Modern format (4.5) zip64 file
27+
28+
Redirecting from stdin forces infozip's zip tool to create a zip64.
29+
30+
```
31+
zip -0 <testdata_module_inside_zip.py >zip64.zip
32+
cat header.sh zip64.zip >exe_with_z64
33+
rm zip64.zip
34+
```
35+

Lib/test/ziptestdata/exe_with_z64

978 Bytes
Binary file not shown.

Lib/test/ziptestdata/exe_with_zip

990 Bytes
Binary file not shown.

Lib/test/ziptestdata/header.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
INTERPRETER_UNDER_TEST="$1"
3+
if [[ ! -x "${INTERPRETER_UNDER_TEST}" ]]; then
4+
echo "Interpreter must be the command line argument."
5+
exit 4
6+
fi
7+
EXECUTABLE="$0" exec "${INTERPRETER_UNDER_TEST}" -E - <<END_OF_PYTHON
8+
import os
9+
import zipfile
10+
11+
namespace = {}
12+
13+
filename = os.environ['EXECUTABLE']
14+
print(f'Opening {filename} as a zipfile.')
15+
with zipfile.ZipFile(filename, mode='r') as exe_zip:
16+
for file_info in exe_zip.infolist():
17+
data = exe_zip.read(file_info)
18+
exec(data, namespace, namespace)
19+
break # Only use the first file in the archive.
20+
21+
print('Favorite number in executable:', namespace["FAVORITE_NUMBER"])
22+
23+
### Archive contents will be appended after this file. ###
24+
END_OF_PYTHON
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Test data file to be stored within a zip file.
2+
FAVORITE_NUMBER = 5

0 commit comments

Comments
 (0)