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 def test_zip_up_file(self): 83 with tempfile.TemporaryDirectory() as tmp_dir: 84 # Arrange. 85 tmp_path = pathlib.Path(tmp_dir) 86 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 87 input_list = [f'{in_path}/file1.txt {DELIMITER} /'] 88 out_filename = f'{tmp_path}/out.zip' 89 90 # Act. 91 zip_up(input_list, out_filename) 92 out_path = pathlib.Path(f'{tmp_path}/out/') 93 with zipfile.ZipFile(out_filename, 'r') as zip_file: 94 zip_file.extractall(out_path) 95 expected_path = make_directory(tmp_path, 'expected', ['file1.txt']) 96 97 # Assert. 98 self.assertSetEqual(get_directory_contents(out_path), 99 get_directory_contents(expected_path)) 100 101 def test_zip_up_dir(self): 102 with tempfile.TemporaryDirectory() as tmp_dir: 103 # Arrange. 104 tmp_path = pathlib.Path(tmp_dir) 105 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 106 input_list = [f'{in_path}/dir1/ {DELIMITER} /'] 107 out_filename = f'{tmp_path}/out.zip' 108 109 # Act. 110 zip_up(input_list, out_filename) 111 out_path = pathlib.Path(f'{tmp_path}/out/') 112 with zipfile.ZipFile(out_filename, 'r') as zip_file: 113 zip_file.extractall(out_path) 114 expected_path = make_directory(tmp_path, 'expected', [ 115 'file3.txt', 116 'file4.txt', 117 'dir2/file5.txt', 118 'dir2/file6.txt', 119 ]) 120 121 # Assert. 122 self.assertSetEqual(get_directory_contents(out_path), 123 get_directory_contents(expected_path)) 124 125 def test_file_rename(self): 126 with tempfile.TemporaryDirectory() as tmp_dir: 127 # Arrange. 128 tmp_path = pathlib.Path(tmp_dir) 129 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 130 input_list = [f'{in_path}/file1.txt {DELIMITER} /renamed.txt'] 131 out_filename = f'{tmp_path}/out.zip' 132 133 # Act. 134 zip_up(input_list, out_filename) 135 out_path = pathlib.Path(f'{tmp_path}/out/') 136 with zipfile.ZipFile(out_filename, 'r') as zip_file: 137 zip_file.extractall(out_path) 138 expected_path = make_directory(tmp_path, 'expected', 139 ['renamed.txt']) 140 141 # Assert. 142 self.assertSetEqual(get_directory_contents(out_path), 143 get_directory_contents(expected_path)) 144 145 def test_file_move(self): 146 with tempfile.TemporaryDirectory() as tmp_dir: 147 # Arrange. 148 tmp_path = pathlib.Path(tmp_dir) 149 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 150 input_list = [f'{in_path}/file1.txt {DELIMITER} /foo/'] 151 out_filename = f'{tmp_path}/out.zip' 152 153 # Act. 154 zip_up(input_list, out_filename) 155 out_path = pathlib.Path(f'{tmp_path}/out/') 156 with zipfile.ZipFile(out_filename, 'r') as zip_file: 157 zip_file.extractall(out_path) 158 expected_path = make_directory(tmp_path, 'expected', 159 ['foo/file1.txt']) 160 161 # Assert. 162 self.assertSetEqual(get_directory_contents(out_path), 163 get_directory_contents(expected_path)) 164 165 def test_dir_move(self): 166 with tempfile.TemporaryDirectory() as tmp_dir: 167 # Arrange. 168 tmp_path = pathlib.Path(tmp_dir) 169 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 170 input_list = [f'{in_path}/dir1/ {DELIMITER} /foo/'] 171 out_filename = f'{tmp_path}/out.zip' 172 173 # Act. 174 zip_up(input_list, out_filename) 175 out_path = pathlib.Path(f'{tmp_path}/out/') 176 with zipfile.ZipFile(out_filename, 'r') as zip_file: 177 zip_file.extractall(out_path) 178 expected_path = make_directory(tmp_path, 'expected', [ 179 'foo/file3.txt', 180 'foo/file4.txt', 181 'foo/dir2/file5.txt', 182 'foo/dir2/file6.txt', 183 ]) 184 185 # Assert. 186 self.assertSetEqual(get_directory_contents(out_path), 187 get_directory_contents(expected_path)) 188 189 def test_change_delimiter(self): 190 with tempfile.TemporaryDirectory() as tmp_dir: 191 # Arrange. 192 tmp_path = pathlib.Path(tmp_dir) 193 in_path = make_directory(tmp_path, 'in', IN_FILENAMES) 194 delimiter = '==>' 195 input_list = [f'{in_path}/file1.txt {delimiter} /'] 196 out_filename = f'{tmp_path}/out.zip' 197 198 # Act. 199 zip_up(input_list, out_filename, delimiter=delimiter) 200 out_path = pathlib.Path(f'{tmp_path}/out/') 201 with zipfile.ZipFile(out_filename, 'r') as zip_file: 202 zip_file.extractall(out_path) 203 expected_path = make_directory(tmp_path, 'expected', ['file1.txt']) 204 205 # Assert. 206 self.assertSetEqual(get_directory_contents(out_path), 207 get_directory_contents(expected_path)) 208 209 def test_wrong_input_syntax_raises_error(self): 210 with tempfile.TemporaryDirectory() as tmp_dir: 211 # Arrange. 212 bad_inputs = [ 213 '', # Empty input 214 f'{tmp_dir}/ /', # No delimiter 215 f'{tmp_dir}/ {DELIMITER} ', # No zip destination 216 f'{tmp_dir} /', # No source 217 f'{tmp_dir}/', # No delimiter or zip destination 218 f'{DELIMITER}', # No source or zip destination 219 f'{tmp_dir} {DELIMITER} /', # No trailing source '/' 220 f'{tmp_dir}/ {DELIMITER} foo/', # No leading zip root '/' 221 f'{tmp_dir}/ {DELIMITER} /foo', # No trailing zip dest '/' 222 f'{tmp_dir}/ {DELIMITER} /{tmp_dir}/ ' 223 f'{DELIMITER} /{tmp_dir}/', # Too many paths on split 224 ] 225 out_filename = f'{tmp_dir}/out.zip' 226 227 # Act & Assert. 228 for bad_input in bad_inputs: 229 with self.assertRaises(ZipError): 230 zip_up([bad_input], out_filename) 231 232 def test_nonexistant_file_raises_error(self): 233 with tempfile.TemporaryDirectory() as tmp_dir: 234 # Arrange. 235 input_list = [f'{tmp_dir}/nonexistant-file.txt > /'] 236 out_filename = f'{tmp_dir}/out.zip' 237 238 # Act & Assert. 239 with self.assertRaises(ZipError): 240 zip_up(input_list, out_filename) 241 242 def test_nonexistant_dir_raises_error(self): 243 with tempfile.TemporaryDirectory() as tmp_dir: 244 # Arrange. 245 input_list = [f'{tmp_dir}/nonexistant-dir/ > /'] 246 out_filename = f'{tmp_dir}/out.zip' 247 248 # Act & Assert. 249 with self.assertRaises(ZipError): 250 zip_up(input_list, out_filename) 251 252 253if __name__ == '__main__': 254 unittest.main() 255