xref: /aosp_15_r20/system/libziparchive/test_ziparchive_large.py (revision adcb0a6279ea715828f9bc5e351569419e478774)
1*adcb0a62SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*adcb0a62SAndroid Build Coastguard Worker#
3*adcb0a62SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project
4*adcb0a62SAndroid Build Coastguard Worker#
5*adcb0a62SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*adcb0a62SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*adcb0a62SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*adcb0a62SAndroid Build Coastguard Worker#
9*adcb0a62SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*adcb0a62SAndroid Build Coastguard Worker#
11*adcb0a62SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*adcb0a62SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*adcb0a62SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*adcb0a62SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*adcb0a62SAndroid Build Coastguard Worker# limitations under the License.
16*adcb0a62SAndroid Build Coastguard Worker#
17*adcb0a62SAndroid Build Coastguard Worker
18*adcb0a62SAndroid Build Coastguard Worker"""Unittests for parsing files in zip64 format"""
19*adcb0a62SAndroid Build Coastguard Worker
20*adcb0a62SAndroid Build Coastguard Workerimport os
21*adcb0a62SAndroid Build Coastguard Workerimport subprocess
22*adcb0a62SAndroid Build Coastguard Workerimport tempfile
23*adcb0a62SAndroid Build Coastguard Workerimport unittest
24*adcb0a62SAndroid Build Coastguard Workerimport zipfile
25*adcb0a62SAndroid Build Coastguard Workerimport time
26*adcb0a62SAndroid Build Coastguard Worker
27*adcb0a62SAndroid Build Coastguard Workerclass Zip64Test(unittest.TestCase):
28*adcb0a62SAndroid Build Coastguard Worker  @staticmethod
29*adcb0a62SAndroid Build Coastguard Worker  def _WriteFile(path, size_in_kib):
30*adcb0a62SAndroid Build Coastguard Worker    contents = b'X' * 1024
31*adcb0a62SAndroid Build Coastguard Worker    with open(path, 'wb') as f:
32*adcb0a62SAndroid Build Coastguard Worker      for i in range(size_in_kib):
33*adcb0a62SAndroid Build Coastguard Worker        f.write(contents)
34*adcb0a62SAndroid Build Coastguard Worker
35*adcb0a62SAndroid Build Coastguard Worker  @staticmethod
36*adcb0a62SAndroid Build Coastguard Worker  def _AddEntriesToZip(output_zip, entries_dict=None):
37*adcb0a62SAndroid Build Coastguard Worker    contents = b'X' * 1024
38*adcb0a62SAndroid Build Coastguard Worker    for name, size in entries_dict.items():
39*adcb0a62SAndroid Build Coastguard Worker      # Need to pass a ZipInfo with a file_size
40*adcb0a62SAndroid Build Coastguard Worker      # to .open() so that it adds the Zip64 header
41*adcb0a62SAndroid Build Coastguard Worker      # on larger files
42*adcb0a62SAndroid Build Coastguard Worker      info = zipfile.ZipInfo(name)
43*adcb0a62SAndroid Build Coastguard Worker      info.file_size = size * 1024
44*adcb0a62SAndroid Build Coastguard Worker      with output_zip.open(info, mode='w') as f:
45*adcb0a62SAndroid Build Coastguard Worker        for i in range(size):
46*adcb0a62SAndroid Build Coastguard Worker          f.write(contents)
47*adcb0a62SAndroid Build Coastguard Worker
48*adcb0a62SAndroid Build Coastguard Worker  def _getEntryNames(self, zip_name):
49*adcb0a62SAndroid Build Coastguard Worker    cmd = ['ziptool', 'zipinfo', '-1', zip_name]
50*adcb0a62SAndroid Build Coastguard Worker    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
51*adcb0a62SAndroid Build Coastguard Worker    output, _ = proc.communicate()
52*adcb0a62SAndroid Build Coastguard Worker    self.assertEqual(0, proc.returncode)
53*adcb0a62SAndroid Build Coastguard Worker    self.assertNotEqual(None, output)
54*adcb0a62SAndroid Build Coastguard Worker    return output.decode('utf-8').split()
55*adcb0a62SAndroid Build Coastguard Worker
56*adcb0a62SAndroid Build Coastguard Worker  def _ExtractEntries(self, zip_name):
57*adcb0a62SAndroid Build Coastguard Worker    with tempfile.TemporaryDirectory() as temp_dir:
58*adcb0a62SAndroid Build Coastguard Worker      cmd = ['ziptool', 'unzip', '-d', temp_dir, zip_name]
59*adcb0a62SAndroid Build Coastguard Worker      proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
60*adcb0a62SAndroid Build Coastguard Worker      proc.communicate()
61*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(0, proc.returncode)
62*adcb0a62SAndroid Build Coastguard Worker
63*adcb0a62SAndroid Build Coastguard Worker  def test_entriesSmallerThan2G(self):
64*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
65*adcb0a62SAndroid Build Coastguard Worker      # Add a few entries with each of them smaller than 2GiB. But the entire zip file is larger
66*adcb0a62SAndroid Build Coastguard Worker      # than 4GiB in size.
67*adcb0a62SAndroid Build Coastguard Worker      with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
68*adcb0a62SAndroid Build Coastguard Worker        entry_dict = {'a.txt': 1025 * 1024, 'b.txt': 1025 * 1024, 'c.txt': 1025 * 1024,
69*adcb0a62SAndroid Build Coastguard Worker                      'd.txt': 1025 * 1024, 'e.txt': 1024}
70*adcb0a62SAndroid Build Coastguard Worker        self._AddEntriesToZip(output_zip, entry_dict)
71*adcb0a62SAndroid Build Coastguard Worker
72*adcb0a62SAndroid Build Coastguard Worker      read_names = self._getEntryNames(zip_path.name)
73*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(sorted(entry_dict.keys()), sorted(read_names))
74*adcb0a62SAndroid Build Coastguard Worker      self._ExtractEntries(zip_path.name)
75*adcb0a62SAndroid Build Coastguard Worker
76*adcb0a62SAndroid Build Coastguard Worker
77*adcb0a62SAndroid Build Coastguard Worker  def test_largeNumberOfEntries(self):
78*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
79*adcb0a62SAndroid Build Coastguard Worker      entry_dict = {}
80*adcb0a62SAndroid Build Coastguard Worker      # Add 100k entries (more than 65535|UINT16_MAX).
81*adcb0a62SAndroid Build Coastguard Worker      # We use empty files so that we don't hit any of the _other_ EOCD limits
82*adcb0a62SAndroid Build Coastguard Worker      # and appear to be testing the file count when we're actually not.
83*adcb0a62SAndroid Build Coastguard Worker      for num in range(0, 100 * 1024):
84*adcb0a62SAndroid Build Coastguard Worker        entry_dict[str(num)] = 0
85*adcb0a62SAndroid Build Coastguard Worker
86*adcb0a62SAndroid Build Coastguard Worker      with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
87*adcb0a62SAndroid Build Coastguard Worker        self._AddEntriesToZip(output_zip, entry_dict)
88*adcb0a62SAndroid Build Coastguard Worker
89*adcb0a62SAndroid Build Coastguard Worker      read_names = self._getEntryNames(zip_path.name)
90*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(sorted(entry_dict.keys()), sorted(read_names))
91*adcb0a62SAndroid Build Coastguard Worker      self._ExtractEntries(zip_path.name)
92*adcb0a62SAndroid Build Coastguard Worker
93*adcb0a62SAndroid Build Coastguard Worker
94*adcb0a62SAndroid Build Coastguard Worker  def test_largeCompressedEntriesSmallerThan4G(self):
95*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
96*adcb0a62SAndroid Build Coastguard Worker      with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED,
97*adcb0a62SAndroid Build Coastguard Worker                           allowZip64=True) as output_zip:
98*adcb0a62SAndroid Build Coastguard Worker        # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
99*adcb0a62SAndroid Build Coastguard Worker        # sizes in the extra field. Test if our ziptool should be able to parse it.
100*adcb0a62SAndroid Build Coastguard Worker        entry_dict = {'e.txt': 4095 * 1024, 'f.txt': 4095 * 1024}
101*adcb0a62SAndroid Build Coastguard Worker        self._AddEntriesToZip(output_zip, entry_dict)
102*adcb0a62SAndroid Build Coastguard Worker
103*adcb0a62SAndroid Build Coastguard Worker      read_names = self._getEntryNames(zip_path.name)
104*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(sorted(entry_dict.keys()), sorted(read_names))
105*adcb0a62SAndroid Build Coastguard Worker      self._ExtractEntries(zip_path.name)
106*adcb0a62SAndroid Build Coastguard Worker
107*adcb0a62SAndroid Build Coastguard Worker
108*adcb0a62SAndroid Build Coastguard Worker  def test_forceDataDescriptor(self):
109*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.txt') as file_path:
110*adcb0a62SAndroid Build Coastguard Worker      self._WriteFile(file_path.name, 5000 * 1024)
111*adcb0a62SAndroid Build Coastguard Worker
112*adcb0a62SAndroid Build Coastguard Worker      with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
113*adcb0a62SAndroid Build Coastguard Worker        with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
114*adcb0a62SAndroid Build Coastguard Worker          pass
115*adcb0a62SAndroid Build Coastguard Worker        # The fd option force writes a data descriptor
116*adcb0a62SAndroid Build Coastguard Worker        cmd = ['zip', '-fd', zip_path.name, file_path.name]
117*adcb0a62SAndroid Build Coastguard Worker        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
118*adcb0a62SAndroid Build Coastguard Worker        proc.communicate()
119*adcb0a62SAndroid Build Coastguard Worker        read_names = self._getEntryNames(zip_path.name)
120*adcb0a62SAndroid Build Coastguard Worker        self.assertEqual([file_path.name[1:]], read_names)
121*adcb0a62SAndroid Build Coastguard Worker        self._ExtractEntries(zip_path.name)
122*adcb0a62SAndroid Build Coastguard Worker
123*adcb0a62SAndroid Build Coastguard Worker
124*adcb0a62SAndroid Build Coastguard Worker  def test_largeUncompressedEntriesLargerThan4G(self):
125*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
126*adcb0a62SAndroid Build Coastguard Worker      with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_STORED,
127*adcb0a62SAndroid Build Coastguard Worker                           allowZip64=True) as output_zip:
128*adcb0a62SAndroid Build Coastguard Worker        # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
129*adcb0a62SAndroid Build Coastguard Worker        # sizes in the extra field. Test if our ziptool should be able to parse it.
130*adcb0a62SAndroid Build Coastguard Worker        entry_dict = {'g.txt': 5000 * 1024, 'h.txt': 6000 * 1024}
131*adcb0a62SAndroid Build Coastguard Worker        self._AddEntriesToZip(output_zip, entry_dict)
132*adcb0a62SAndroid Build Coastguard Worker
133*adcb0a62SAndroid Build Coastguard Worker      read_names = self._getEntryNames(zip_path.name)
134*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(sorted(entry_dict.keys()), sorted(read_names))
135*adcb0a62SAndroid Build Coastguard Worker      self._ExtractEntries(zip_path.name)
136*adcb0a62SAndroid Build Coastguard Worker
137*adcb0a62SAndroid Build Coastguard Worker
138*adcb0a62SAndroid Build Coastguard Worker  def test_largeCompressedEntriesLargerThan4G(self):
139*adcb0a62SAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_path:
140*adcb0a62SAndroid Build Coastguard Worker      with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED,
141*adcb0a62SAndroid Build Coastguard Worker                           allowZip64=True) as output_zip:
142*adcb0a62SAndroid Build Coastguard Worker        # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
143*adcb0a62SAndroid Build Coastguard Worker        # sizes in the extra field. Test if our ziptool should be able to parse it.
144*adcb0a62SAndroid Build Coastguard Worker        entry_dict = {'i.txt': 4096 * 1024, 'j.txt': 7000 * 1024}
145*adcb0a62SAndroid Build Coastguard Worker        self._AddEntriesToZip(output_zip, entry_dict)
146*adcb0a62SAndroid Build Coastguard Worker
147*adcb0a62SAndroid Build Coastguard Worker      read_names = self._getEntryNames(zip_path.name)
148*adcb0a62SAndroid Build Coastguard Worker      self.assertEqual(sorted(entry_dict.keys()), sorted(read_names))
149*adcb0a62SAndroid Build Coastguard Worker      self._ExtractEntries(zip_path.name)
150*adcb0a62SAndroid Build Coastguard Worker
151*adcb0a62SAndroid Build Coastguard Worker
152*adcb0a62SAndroid Build Coastguard Workerif __name__ == '__main__':
153*adcb0a62SAndroid Build Coastguard Worker  unittest.main(verbosity=2)
154