xref: /aosp_15_r20/external/pigweed/pw_build/py/zip_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker# Copyright 2020 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker#
3*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker# the License at
6*61c4878aSAndroid Build Coastguard Worker#
7*61c4878aSAndroid Build Coastguard Worker#     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker#
9*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker# the License.
14*61c4878aSAndroid Build Coastguard Worker"""Tests for the pw_build.zip module."""
15*61c4878aSAndroid Build Coastguard Worker
16*61c4878aSAndroid Build Coastguard Workerimport unittest
17*61c4878aSAndroid Build Coastguard Workerimport os
18*61c4878aSAndroid Build Coastguard Workerimport tempfile
19*61c4878aSAndroid Build Coastguard Workerimport pathlib
20*61c4878aSAndroid Build Coastguard Workerimport zipfile
21*61c4878aSAndroid Build Coastguard Worker
22*61c4878aSAndroid Build Coastguard Workerfrom pw_build.zip import zip_up, ZipError
23*61c4878aSAndroid Build Coastguard Worker
24*61c4878aSAndroid Build Coastguard WorkerDELIMITER = '>'
25*61c4878aSAndroid Build Coastguard WorkerIN_FILENAMES = [
26*61c4878aSAndroid Build Coastguard Worker    'file1.txt',
27*61c4878aSAndroid Build Coastguard Worker    'file2.txt',
28*61c4878aSAndroid Build Coastguard Worker    'dir1/file3.txt',
29*61c4878aSAndroid Build Coastguard Worker    'dir1/file4.txt',
30*61c4878aSAndroid Build Coastguard Worker    'dir1/dir2/file5.txt',
31*61c4878aSAndroid Build Coastguard Worker    'dir1/dir2/file6.txt',
32*61c4878aSAndroid Build Coastguard Worker]
33*61c4878aSAndroid Build Coastguard Worker
34*61c4878aSAndroid Build Coastguard Worker
35*61c4878aSAndroid Build Coastguard Workerdef make_directory(parent_path: pathlib.Path, dir_name: str, filenames: list):
36*61c4878aSAndroid Build Coastguard Worker    """Creates a directory and returns a pathlib.Path() of it's root dir.
37*61c4878aSAndroid Build Coastguard Worker
38*61c4878aSAndroid Build Coastguard Worker    Args:
39*61c4878aSAndroid Build Coastguard Worker        parent_path: Path to directory where the new directory will be made.
40*61c4878aSAndroid Build Coastguard Worker        dir_name: Name of the new directory.
41*61c4878aSAndroid Build Coastguard Worker        filenames: list of file contents of the new directory. Also allows
42*61c4878aSAndroid Build Coastguard Worker            the creation of subdirectories. Example:
43*61c4878aSAndroid Build Coastguard Worker            [
44*61c4878aSAndroid Build Coastguard Worker                'file1.txt',
45*61c4878aSAndroid Build Coastguard Worker                'subdir/file2.txt'
46*61c4878aSAndroid Build Coastguard Worker            ]
47*61c4878aSAndroid Build Coastguard Worker
48*61c4878aSAndroid Build Coastguard Worker    Returns: pathlib.Path() to the newly created directory.
49*61c4878aSAndroid Build Coastguard Worker    """
50*61c4878aSAndroid Build Coastguard Worker    root_path = pathlib.Path(parent_path / dir_name)
51*61c4878aSAndroid Build Coastguard Worker    os.mkdir(root_path)
52*61c4878aSAndroid Build Coastguard Worker    for filename in filenames:
53*61c4878aSAndroid Build Coastguard Worker        # Make the sub directories if they don't already exist.
54*61c4878aSAndroid Build Coastguard Worker        directories = filename.split('/')[:-1]
55*61c4878aSAndroid Build Coastguard Worker        for i in range(len(directories)):
56*61c4878aSAndroid Build Coastguard Worker            directory = pathlib.PurePath('/'.join(directories[: i + 1]))
57*61c4878aSAndroid Build Coastguard Worker            if not (root_path / directory).is_dir():
58*61c4878aSAndroid Build Coastguard Worker                os.mkdir(root_path / directory)
59*61c4878aSAndroid Build Coastguard Worker
60*61c4878aSAndroid Build Coastguard Worker        # Create a file at the destination.
61*61c4878aSAndroid Build Coastguard Worker        touch(root_path, filename)
62*61c4878aSAndroid Build Coastguard Worker    return root_path
63*61c4878aSAndroid Build Coastguard Worker
64*61c4878aSAndroid Build Coastguard Worker
65*61c4878aSAndroid Build Coastguard Workerdef touch(parent_dir: pathlib.Path, filename: str):
66*61c4878aSAndroid Build Coastguard Worker    """Creates an empty file at parent_dir/filename."""
67*61c4878aSAndroid Build Coastguard Worker    with open(parent_dir / filename, 'a') as touch_file:
68*61c4878aSAndroid Build Coastguard Worker        touch_file.write(filename)
69*61c4878aSAndroid Build Coastguard Worker
70*61c4878aSAndroid Build Coastguard Worker
71*61c4878aSAndroid Build Coastguard Workerdef get_directory_contents(path: pathlib.Path):
72*61c4878aSAndroid Build Coastguard Worker    """Iterates through a directory and returns a set of its contents."""
73*61c4878aSAndroid Build Coastguard Worker    contents = set()
74*61c4878aSAndroid Build Coastguard Worker    for filename in path.glob('**/*'):
75*61c4878aSAndroid Build Coastguard Worker        # Remove the original parent directories to get just the relative path.
76*61c4878aSAndroid Build Coastguard Worker        contents.add(filename.relative_to(path))
77*61c4878aSAndroid Build Coastguard Worker    return contents
78*61c4878aSAndroid Build Coastguard Worker
79*61c4878aSAndroid Build Coastguard Worker
80*61c4878aSAndroid Build Coastguard Workerclass TestZipping(unittest.TestCase):
81*61c4878aSAndroid Build Coastguard Worker    """Tests for the pw_build.zip module."""
82*61c4878aSAndroid Build Coastguard Worker
83*61c4878aSAndroid Build Coastguard Worker    def test_zip_up_file(self):
84*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
85*61c4878aSAndroid Build Coastguard Worker            # Arrange.
86*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
87*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
88*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/file1.txt {DELIMITER} /']
89*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
90*61c4878aSAndroid Build Coastguard Worker
91*61c4878aSAndroid Build Coastguard Worker            # Act.
92*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename)
93*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
94*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
95*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
96*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(tmp_path, 'expected', ['file1.txt'])
97*61c4878aSAndroid Build Coastguard Worker
98*61c4878aSAndroid Build Coastguard Worker            # Assert.
99*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
100*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
101*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
102*61c4878aSAndroid Build Coastguard Worker            )
103*61c4878aSAndroid Build Coastguard Worker
104*61c4878aSAndroid Build Coastguard Worker    def test_zip_up_dir(self):
105*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
106*61c4878aSAndroid Build Coastguard Worker            # Arrange.
107*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
108*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
109*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/dir1/ {DELIMITER} /']
110*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
111*61c4878aSAndroid Build Coastguard Worker
112*61c4878aSAndroid Build Coastguard Worker            # Act.
113*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename)
114*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
115*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
116*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
117*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(
118*61c4878aSAndroid Build Coastguard Worker                tmp_path,
119*61c4878aSAndroid Build Coastguard Worker                'expected',
120*61c4878aSAndroid Build Coastguard Worker                [
121*61c4878aSAndroid Build Coastguard Worker                    'file3.txt',
122*61c4878aSAndroid Build Coastguard Worker                    'file4.txt',
123*61c4878aSAndroid Build Coastguard Worker                    'dir2/file5.txt',
124*61c4878aSAndroid Build Coastguard Worker                    'dir2/file6.txt',
125*61c4878aSAndroid Build Coastguard Worker                ],
126*61c4878aSAndroid Build Coastguard Worker            )
127*61c4878aSAndroid Build Coastguard Worker
128*61c4878aSAndroid Build Coastguard Worker            # Assert.
129*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
130*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
131*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
132*61c4878aSAndroid Build Coastguard Worker            )
133*61c4878aSAndroid Build Coastguard Worker
134*61c4878aSAndroid Build Coastguard Worker    def test_file_rename(self):
135*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
136*61c4878aSAndroid Build Coastguard Worker            # Arrange.
137*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
138*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
139*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/file1.txt {DELIMITER} /renamed.txt']
140*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
141*61c4878aSAndroid Build Coastguard Worker
142*61c4878aSAndroid Build Coastguard Worker            # Act.
143*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename)
144*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
145*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
146*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
147*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(
148*61c4878aSAndroid Build Coastguard Worker                tmp_path, 'expected', ['renamed.txt']
149*61c4878aSAndroid Build Coastguard Worker            )
150*61c4878aSAndroid Build Coastguard Worker
151*61c4878aSAndroid Build Coastguard Worker            # Assert.
152*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
153*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
154*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
155*61c4878aSAndroid Build Coastguard Worker            )
156*61c4878aSAndroid Build Coastguard Worker
157*61c4878aSAndroid Build Coastguard Worker    def test_file_move(self):
158*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
159*61c4878aSAndroid Build Coastguard Worker            # Arrange.
160*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
161*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
162*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/file1.txt {DELIMITER} /foo/']
163*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
164*61c4878aSAndroid Build Coastguard Worker
165*61c4878aSAndroid Build Coastguard Worker            # Act.
166*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename)
167*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
168*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
169*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
170*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(
171*61c4878aSAndroid Build Coastguard Worker                tmp_path, 'expected', ['foo/file1.txt']
172*61c4878aSAndroid Build Coastguard Worker            )
173*61c4878aSAndroid Build Coastguard Worker
174*61c4878aSAndroid Build Coastguard Worker            # Assert.
175*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
176*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
177*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
178*61c4878aSAndroid Build Coastguard Worker            )
179*61c4878aSAndroid Build Coastguard Worker
180*61c4878aSAndroid Build Coastguard Worker    def test_dir_move(self):
181*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
182*61c4878aSAndroid Build Coastguard Worker            # Arrange.
183*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
184*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
185*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/dir1/ {DELIMITER} /foo/']
186*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
187*61c4878aSAndroid Build Coastguard Worker
188*61c4878aSAndroid Build Coastguard Worker            # Act.
189*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename)
190*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
191*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
192*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
193*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(
194*61c4878aSAndroid Build Coastguard Worker                tmp_path,
195*61c4878aSAndroid Build Coastguard Worker                'expected',
196*61c4878aSAndroid Build Coastguard Worker                [
197*61c4878aSAndroid Build Coastguard Worker                    'foo/file3.txt',
198*61c4878aSAndroid Build Coastguard Worker                    'foo/file4.txt',
199*61c4878aSAndroid Build Coastguard Worker                    'foo/dir2/file5.txt',
200*61c4878aSAndroid Build Coastguard Worker                    'foo/dir2/file6.txt',
201*61c4878aSAndroid Build Coastguard Worker                ],
202*61c4878aSAndroid Build Coastguard Worker            )
203*61c4878aSAndroid Build Coastguard Worker
204*61c4878aSAndroid Build Coastguard Worker            # Assert.
205*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
206*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
207*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
208*61c4878aSAndroid Build Coastguard Worker            )
209*61c4878aSAndroid Build Coastguard Worker
210*61c4878aSAndroid Build Coastguard Worker    def test_change_delimiter(self):
211*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
212*61c4878aSAndroid Build Coastguard Worker            # Arrange.
213*61c4878aSAndroid Build Coastguard Worker            tmp_path = pathlib.Path(tmp_dir)
214*61c4878aSAndroid Build Coastguard Worker            in_path = make_directory(tmp_path, 'in', IN_FILENAMES)
215*61c4878aSAndroid Build Coastguard Worker            delimiter = '==>'
216*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{in_path}/file1.txt {delimiter} /']
217*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_path}/out.zip'
218*61c4878aSAndroid Build Coastguard Worker
219*61c4878aSAndroid Build Coastguard Worker            # Act.
220*61c4878aSAndroid Build Coastguard Worker            zip_up(input_list, out_filename, delimiter=delimiter)
221*61c4878aSAndroid Build Coastguard Worker            out_path = pathlib.Path(f'{tmp_path}/out/')
222*61c4878aSAndroid Build Coastguard Worker            with zipfile.ZipFile(out_filename, 'r') as zip_file:
223*61c4878aSAndroid Build Coastguard Worker                zip_file.extractall(out_path)
224*61c4878aSAndroid Build Coastguard Worker            expected_path = make_directory(tmp_path, 'expected', ['file1.txt'])
225*61c4878aSAndroid Build Coastguard Worker
226*61c4878aSAndroid Build Coastguard Worker            # Assert.
227*61c4878aSAndroid Build Coastguard Worker            self.assertSetEqual(
228*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(out_path),
229*61c4878aSAndroid Build Coastguard Worker                get_directory_contents(expected_path),
230*61c4878aSAndroid Build Coastguard Worker            )
231*61c4878aSAndroid Build Coastguard Worker
232*61c4878aSAndroid Build Coastguard Worker    def test_wrong_input_syntax_raises_error(self):
233*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
234*61c4878aSAndroid Build Coastguard Worker            # Arrange.
235*61c4878aSAndroid Build Coastguard Worker            bad_inputs = [
236*61c4878aSAndroid Build Coastguard Worker                '',  # Empty input
237*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/ /',  # No delimiter
238*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/ {DELIMITER} ',  # No zip destination
239*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir} /',  # No source
240*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/',  # No delimiter or zip destination
241*61c4878aSAndroid Build Coastguard Worker                f'{DELIMITER}',  # No source or zip destination
242*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir} {DELIMITER} /',  # No trailing source '/'
243*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/ {DELIMITER} foo/',  # No leading zip root '/'
244*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/ {DELIMITER} /foo',  # No trailing zip dest '/'
245*61c4878aSAndroid Build Coastguard Worker                f'{tmp_dir}/ {DELIMITER} /{tmp_dir}/ '
246*61c4878aSAndroid Build Coastguard Worker                f'{DELIMITER} /{tmp_dir}/',  # Too many paths on split
247*61c4878aSAndroid Build Coastguard Worker            ]
248*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_dir}/out.zip'
249*61c4878aSAndroid Build Coastguard Worker
250*61c4878aSAndroid Build Coastguard Worker            # Act & Assert.
251*61c4878aSAndroid Build Coastguard Worker            for bad_input in bad_inputs:
252*61c4878aSAndroid Build Coastguard Worker                with self.assertRaises(ZipError):
253*61c4878aSAndroid Build Coastguard Worker                    zip_up([bad_input], out_filename)
254*61c4878aSAndroid Build Coastguard Worker
255*61c4878aSAndroid Build Coastguard Worker    def test_nonexistant_file_raises_error(self):
256*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
257*61c4878aSAndroid Build Coastguard Worker            # Arrange.
258*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{tmp_dir}/nonexistant-file.txt > /']
259*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_dir}/out.zip'
260*61c4878aSAndroid Build Coastguard Worker
261*61c4878aSAndroid Build Coastguard Worker            # Act & Assert.
262*61c4878aSAndroid Build Coastguard Worker            with self.assertRaises(ZipError):
263*61c4878aSAndroid Build Coastguard Worker                zip_up(input_list, out_filename)
264*61c4878aSAndroid Build Coastguard Worker
265*61c4878aSAndroid Build Coastguard Worker    def test_nonexistant_dir_raises_error(self):
266*61c4878aSAndroid Build Coastguard Worker        with tempfile.TemporaryDirectory() as tmp_dir:
267*61c4878aSAndroid Build Coastguard Worker            # Arrange.
268*61c4878aSAndroid Build Coastguard Worker            input_list = [f'{tmp_dir}/nonexistant-dir/ > /']
269*61c4878aSAndroid Build Coastguard Worker            out_filename = f'{tmp_dir}/out.zip'
270*61c4878aSAndroid Build Coastguard Worker
271*61c4878aSAndroid Build Coastguard Worker            # Act & Assert.
272*61c4878aSAndroid Build Coastguard Worker            with self.assertRaises(ZipError):
273*61c4878aSAndroid Build Coastguard Worker                zip_up(input_list, out_filename)
274*61c4878aSAndroid Build Coastguard Worker
275*61c4878aSAndroid Build Coastguard Worker
276*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__':
277*61c4878aSAndroid Build Coastguard Worker    unittest.main()
278