test_move_remove.py (8984B)
1 #!/usr/bin/env python 2 3 import errno 4 import os 5 import shutil 6 import stat 7 import threading 8 import time 9 import unittest 10 from contextlib import contextmanager 11 12 import mozfile 13 import mozinfo 14 import mozunit 15 import stubs 16 17 18 def mark_readonly(path): 19 """Removes all write permissions from given file/directory. 20 21 :param path: path of directory/file of which modes must be changed 22 """ 23 mode = os.stat(path)[stat.ST_MODE] 24 os.chmod(path, mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH) 25 26 27 class FileOpenCloseThread(threading.Thread): 28 """Helper thread for asynchronous file handling""" 29 30 def __init__(self, path, delay, delete=False): 31 threading.Thread.__init__(self) 32 self.file_opened = threading.Event() 33 self.delay = delay 34 self.path = path 35 self.delete = delete 36 37 def run(self): 38 with open(self.path): 39 self.file_opened.set() 40 time.sleep(self.delay) 41 if self.delete: 42 try: 43 os.remove(self.path) 44 except Exception: 45 pass 46 47 48 @contextmanager 49 def wait_file_opened_in_thread(*args, **kwargs): 50 thread = FileOpenCloseThread(*args, **kwargs) 51 thread.start() 52 thread.file_opened.wait() 53 try: 54 yield thread 55 finally: 56 thread.join() 57 58 59 class MozfileRemoveTestCase(unittest.TestCase): 60 """Test our ability to remove directories and files""" 61 62 def setUp(self): 63 # Generate a stub 64 self.tempdir = stubs.create_stub() 65 66 def tearDown(self): 67 if os.path.isdir(self.tempdir): 68 shutil.rmtree(self.tempdir) 69 70 def test_remove_directory(self): 71 """Test the removal of a directory""" 72 self.assertTrue(os.path.isdir(self.tempdir)) 73 mozfile.remove(self.tempdir) 74 self.assertFalse(os.path.exists(self.tempdir)) 75 76 def test_remove_directory_with_open_file(self): 77 """Test removing a directory with an open file""" 78 # Open a file in the generated stub 79 filepath = os.path.join(self.tempdir, *stubs.files[1]) 80 f = open(filepath, "w") 81 f.write("foo-bar") 82 83 # keep file open and then try removing the dir-tree 84 if mozinfo.isWin: 85 # On the Windows family WindowsError should be raised. 86 self.assertRaises(OSError, mozfile.remove, self.tempdir) 87 self.assertTrue(os.path.exists(self.tempdir)) 88 else: 89 # Folder should be deleted on all other platforms 90 mozfile.remove(self.tempdir) 91 self.assertFalse(os.path.exists(self.tempdir)) 92 93 def test_remove_closed_file(self): 94 """Test removing a closed file""" 95 # Open a file in the generated stub 96 filepath = os.path.join(self.tempdir, *stubs.files[1]) 97 with open(filepath, "w") as f: 98 f.write("foo-bar") 99 100 # Folder should be deleted on all platforms 101 mozfile.remove(self.tempdir) 102 self.assertFalse(os.path.exists(self.tempdir)) 103 104 def test_removing_open_file_with_retry(self): 105 """Test removing a file in use with retry""" 106 filepath = os.path.join(self.tempdir, *stubs.files[1]) 107 108 with wait_file_opened_in_thread(filepath, 0.2): 109 # on windows first attempt will fail, 110 # and it will be retried until the thread leave the handle 111 mozfile.remove(filepath) 112 113 # Check deletion was successful 114 self.assertFalse(os.path.exists(filepath)) 115 116 def test_removing_already_deleted_file_with_retry(self): 117 """Test removing a meanwhile removed file with retry""" 118 filepath = os.path.join(self.tempdir, *stubs.files[1]) 119 120 with wait_file_opened_in_thread(filepath, 0.2, True): 121 # on windows first attempt will fail, and before 122 # the retry the opened file will be deleted in the thread 123 mozfile.remove(filepath) 124 125 # Check deletion was successful 126 self.assertFalse(os.path.exists(filepath)) 127 128 def test_remove_readonly_tree(self): 129 """Test removing a read-only directory""" 130 131 dirpath = os.path.join(self.tempdir, "nested_tree") 132 mark_readonly(dirpath) 133 134 # However, mozfile should change write permissions and remove dir. 135 mozfile.remove(dirpath) 136 137 self.assertFalse(os.path.exists(dirpath)) 138 139 def test_remove_readonly_file(self): 140 """Test removing read-only files""" 141 filepath = os.path.join(self.tempdir, *stubs.files[1]) 142 mark_readonly(filepath) 143 144 # However, mozfile should change write permission and then remove file. 145 mozfile.remove(filepath) 146 147 self.assertFalse(os.path.exists(filepath)) 148 149 @unittest.skipIf(mozinfo.isWin, "Symlinks are not supported on Windows") 150 def test_remove_symlink(self): 151 """Test removing a symlink""" 152 file_path = os.path.join(self.tempdir, *stubs.files[1]) 153 symlink_path = os.path.join(self.tempdir, "symlink") 154 155 os.symlink(file_path, symlink_path) 156 self.assertTrue(os.path.islink(symlink_path)) 157 158 # The linked folder and files should not be deleted 159 mozfile.remove(symlink_path) 160 self.assertFalse(os.path.exists(symlink_path)) 161 self.assertTrue(os.path.exists(file_path)) 162 163 @unittest.skipIf(mozinfo.isWin, "Symlinks are not supported on Windows") 164 def test_remove_symlink_in_subfolder(self): 165 """Test removing a folder with an contained symlink""" 166 file_path = os.path.join(self.tempdir, *stubs.files[0]) 167 dir_path = os.path.dirname(os.path.join(self.tempdir, *stubs.files[1])) 168 symlink_path = os.path.join(dir_path, "symlink") 169 170 os.symlink(file_path, symlink_path) 171 self.assertTrue(os.path.islink(symlink_path)) 172 173 # The folder with the contained symlink will be deleted but not the 174 # original linked file 175 mozfile.remove(dir_path) 176 self.assertFalse(os.path.exists(dir_path)) 177 self.assertFalse(os.path.exists(symlink_path)) 178 self.assertTrue(os.path.exists(file_path)) 179 180 @unittest.skipIf(mozinfo.isWin, "Symlinks are not supported on Windows") 181 def test_remove_broken_symlink(self): 182 """Test removing a folder with an contained symlink""" 183 file_path = os.path.join(self.tempdir, "readonly.txt") 184 working_link = os.path.join(self.tempdir, "link_to_readonly.txt") 185 broken_link = os.path.join(self.tempdir, "broken_link") 186 os.symlink(file_path, working_link) 187 os.symlink(os.path.join(self.tempdir, "broken.txt"), broken_link) 188 189 self.assertTrue(os.path.exists(file_path)) 190 self.assertTrue(os.path.islink(working_link)) 191 self.assertTrue(os.path.islink(broken_link)) 192 193 mozfile.remove(working_link) 194 self.assertFalse(os.path.lexists(working_link)) 195 self.assertTrue(os.path.exists(file_path)) 196 197 mozfile.remove(broken_link) 198 self.assertFalse(os.path.lexists(broken_link)) 199 200 @unittest.skipIf( 201 mozinfo.isWin or not os.geteuid(), 202 "Symlinks are not supported on Windows and cannot run test as root", 203 ) 204 def test_remove_symlink_for_system_path(self): 205 """Test removing a symlink which points to a system folder""" 206 symlink_path = os.path.join(self.tempdir, "symlink") 207 208 os.symlink(os.path.dirname(self.tempdir), symlink_path) 209 self.assertTrue(os.path.islink(symlink_path)) 210 211 # The folder with the contained symlink will be deleted but not the 212 # original linked file 213 mozfile.remove(symlink_path) 214 self.assertFalse(os.path.exists(symlink_path)) 215 216 def test_remove_path_that_does_not_exists(self): 217 not_existing_path = os.path.join(self.tempdir, "I_do_not_not_exists") 218 try: 219 mozfile.remove(not_existing_path) 220 except OSError as exc: 221 if exc.errno == errno.ENOENT: 222 self.fail("removing non existing path must not raise error") 223 raise 224 225 226 class MozFileMoveTestCase(unittest.TestCase): 227 def setUp(self): 228 # Generate a stub 229 self.tempdir = stubs.create_stub() 230 self.addCleanup(mozfile.rmtree, self.tempdir) 231 232 def test_move_file(self): 233 file_path = os.path.join(self.tempdir, *stubs.files[1]) 234 moved_path = file_path + ".moved" 235 self.assertTrue(os.path.isfile(file_path)) 236 self.assertFalse(os.path.exists(moved_path)) 237 mozfile.move(file_path, moved_path) 238 self.assertFalse(os.path.exists(file_path)) 239 self.assertTrue(os.path.isfile(moved_path)) 240 241 def test_move_file_with_retry(self): 242 file_path = os.path.join(self.tempdir, *stubs.files[1]) 243 moved_path = file_path + ".moved" 244 245 with wait_file_opened_in_thread(file_path, 0.2): 246 # first move attempt should fail on windows and be retried 247 mozfile.move(file_path, moved_path) 248 self.assertFalse(os.path.exists(file_path)) 249 self.assertTrue(os.path.isfile(moved_path)) 250 251 252 if __name__ == "__main__": 253 mozunit.main()