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