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