Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | |
michael@0 | 3 | import os |
michael@0 | 4 | import stat |
michael@0 | 5 | import shutil |
michael@0 | 6 | import tempfile |
michael@0 | 7 | import threading |
michael@0 | 8 | import time |
michael@0 | 9 | import unittest |
michael@0 | 10 | |
michael@0 | 11 | import mozfile |
michael@0 | 12 | import mozinfo |
michael@0 | 13 | |
michael@0 | 14 | import stubs |
michael@0 | 15 | |
michael@0 | 16 | |
michael@0 | 17 | def mark_readonly(path): |
michael@0 | 18 | """Removes all write permissions from given file/directory. |
michael@0 | 19 | |
michael@0 | 20 | :param path: path of directory/file of which modes must be changed |
michael@0 | 21 | """ |
michael@0 | 22 | mode = os.stat(path)[stat.ST_MODE] |
michael@0 | 23 | os.chmod(path, mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH) |
michael@0 | 24 | |
michael@0 | 25 | |
michael@0 | 26 | class FileOpenCloseThread(threading.Thread): |
michael@0 | 27 | """Helper thread for asynchronous file handling""" |
michael@0 | 28 | def __init__(self, path, delay, delete=False): |
michael@0 | 29 | threading.Thread.__init__(self) |
michael@0 | 30 | self.delay = delay |
michael@0 | 31 | self.path = path |
michael@0 | 32 | self.delete = delete |
michael@0 | 33 | |
michael@0 | 34 | def run(self): |
michael@0 | 35 | with open(self.path) as f: |
michael@0 | 36 | time.sleep(self.delay) |
michael@0 | 37 | if self.delete: |
michael@0 | 38 | try: |
michael@0 | 39 | os.remove(self.path) |
michael@0 | 40 | except: |
michael@0 | 41 | pass |
michael@0 | 42 | |
michael@0 | 43 | |
michael@0 | 44 | class MozfileRemoveTestCase(unittest.TestCase): |
michael@0 | 45 | """Test our ability to remove directories and files""" |
michael@0 | 46 | |
michael@0 | 47 | def setUp(self): |
michael@0 | 48 | # Generate a stub |
michael@0 | 49 | self.tempdir = stubs.create_stub() |
michael@0 | 50 | |
michael@0 | 51 | def tearDown(self): |
michael@0 | 52 | if os.path.isdir(self.tempdir): |
michael@0 | 53 | shutil.rmtree(self.tempdir) |
michael@0 | 54 | |
michael@0 | 55 | def test_remove_directory(self): |
michael@0 | 56 | """Test the removal of a directory""" |
michael@0 | 57 | self.assertTrue(os.path.isdir(self.tempdir)) |
michael@0 | 58 | mozfile.remove(self.tempdir) |
michael@0 | 59 | self.assertFalse(os.path.exists(self.tempdir)) |
michael@0 | 60 | |
michael@0 | 61 | def test_remove_directory_with_open_file(self): |
michael@0 | 62 | """Test removing a directory with an open file""" |
michael@0 | 63 | # Open a file in the generated stub |
michael@0 | 64 | filepath = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 65 | f = file(filepath, 'w') |
michael@0 | 66 | f.write('foo-bar') |
michael@0 | 67 | |
michael@0 | 68 | # keep file open and then try removing the dir-tree |
michael@0 | 69 | if mozinfo.isWin: |
michael@0 | 70 | # On the Windows family WindowsError should be raised. |
michael@0 | 71 | self.assertRaises(OSError, mozfile.remove, self.tempdir) |
michael@0 | 72 | self.assertTrue(os.path.exists(self.tempdir)) |
michael@0 | 73 | else: |
michael@0 | 74 | # Folder should be deleted on all other platforms |
michael@0 | 75 | mozfile.remove(self.tempdir) |
michael@0 | 76 | self.assertFalse(os.path.exists(self.tempdir)) |
michael@0 | 77 | |
michael@0 | 78 | def test_remove_closed_file(self): |
michael@0 | 79 | """Test removing a closed file""" |
michael@0 | 80 | # Open a file in the generated stub |
michael@0 | 81 | filepath = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 82 | with open(filepath, 'w') as f: |
michael@0 | 83 | f.write('foo-bar') |
michael@0 | 84 | |
michael@0 | 85 | # Folder should be deleted on all platforms |
michael@0 | 86 | mozfile.remove(self.tempdir) |
michael@0 | 87 | self.assertFalse(os.path.exists(self.tempdir)) |
michael@0 | 88 | |
michael@0 | 89 | def test_removing_open_file_with_retry(self): |
michael@0 | 90 | """Test removing a file in use with retry""" |
michael@0 | 91 | filepath = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 92 | |
michael@0 | 93 | thread = FileOpenCloseThread(filepath, 1) |
michael@0 | 94 | thread.start() |
michael@0 | 95 | |
michael@0 | 96 | # Wait a bit so we can be sure the file has been opened |
michael@0 | 97 | time.sleep(.5) |
michael@0 | 98 | mozfile.remove(filepath) |
michael@0 | 99 | thread.join() |
michael@0 | 100 | |
michael@0 | 101 | # Check deletion was successful |
michael@0 | 102 | self.assertFalse(os.path.exists(filepath)) |
michael@0 | 103 | |
michael@0 | 104 | def test_removing_already_deleted_file_with_retry(self): |
michael@0 | 105 | """Test removing a meanwhile removed file with retry""" |
michael@0 | 106 | filepath = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 107 | |
michael@0 | 108 | thread = FileOpenCloseThread(filepath, .8, True) |
michael@0 | 109 | thread.start() |
michael@0 | 110 | |
michael@0 | 111 | # Wait a bit so we can be sure the file has been opened and gets deleted |
michael@0 | 112 | # while remove() waits for the next retry |
michael@0 | 113 | time.sleep(.5) |
michael@0 | 114 | mozfile.remove(filepath) |
michael@0 | 115 | thread.join() |
michael@0 | 116 | |
michael@0 | 117 | # Check deletion was successful |
michael@0 | 118 | self.assertFalse(os.path.exists(filepath)) |
michael@0 | 119 | |
michael@0 | 120 | def test_remove_readonly_tree(self): |
michael@0 | 121 | """Test removing a read-only directory""" |
michael@0 | 122 | |
michael@0 | 123 | dirpath = os.path.join(self.tempdir, "nested_tree") |
michael@0 | 124 | mark_readonly(dirpath) |
michael@0 | 125 | |
michael@0 | 126 | # However, mozfile should change write permissions and remove dir. |
michael@0 | 127 | mozfile.remove(dirpath) |
michael@0 | 128 | |
michael@0 | 129 | self.assertFalse(os.path.exists(dirpath)) |
michael@0 | 130 | |
michael@0 | 131 | def test_remove_readonly_file(self): |
michael@0 | 132 | """Test removing read-only files""" |
michael@0 | 133 | filepath = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 134 | mark_readonly(filepath) |
michael@0 | 135 | |
michael@0 | 136 | # However, mozfile should change write permission and then remove file. |
michael@0 | 137 | mozfile.remove(filepath) |
michael@0 | 138 | |
michael@0 | 139 | self.assertFalse(os.path.exists(filepath)) |
michael@0 | 140 | |
michael@0 | 141 | @unittest.skipIf(mozinfo.isWin, "Symlinks are not supported on Windows") |
michael@0 | 142 | def test_remove_symlink(self): |
michael@0 | 143 | """Test removing a symlink""" |
michael@0 | 144 | file_path = os.path.join(self.tempdir, *stubs.files[1]) |
michael@0 | 145 | symlink_path = os.path.join(self.tempdir, 'symlink') |
michael@0 | 146 | |
michael@0 | 147 | os.symlink(file_path, symlink_path) |
michael@0 | 148 | self.assertTrue(os.path.islink(symlink_path)) |
michael@0 | 149 | |
michael@0 | 150 | # The linked folder and files should not be deleted |
michael@0 | 151 | mozfile.remove(symlink_path) |
michael@0 | 152 | self.assertFalse(os.path.exists(symlink_path)) |
michael@0 | 153 | self.assertTrue(os.path.exists(file_path)) |
michael@0 | 154 | |
michael@0 | 155 | @unittest.skipIf(mozinfo.isWin, "Symlinks are not supported on Windows") |
michael@0 | 156 | def test_remove_symlink_in_subfolder(self): |
michael@0 | 157 | """Test removing a folder with an contained symlink""" |
michael@0 | 158 | file_path = os.path.join(self.tempdir, *stubs.files[0]) |
michael@0 | 159 | dir_path = os.path.dirname(os.path.join(self.tempdir, *stubs.files[1])) |
michael@0 | 160 | symlink_path = os.path.join(dir_path, 'symlink') |
michael@0 | 161 | |
michael@0 | 162 | os.symlink(file_path, symlink_path) |
michael@0 | 163 | self.assertTrue(os.path.islink(symlink_path)) |
michael@0 | 164 | |
michael@0 | 165 | # The folder with the contained symlink will be deleted but not the |
michael@0 | 166 | # original linked file |
michael@0 | 167 | mozfile.remove(dir_path) |
michael@0 | 168 | self.assertFalse(os.path.exists(dir_path)) |
michael@0 | 169 | self.assertFalse(os.path.exists(symlink_path)) |
michael@0 | 170 | self.assertTrue(os.path.exists(file_path)) |
michael@0 | 171 | |
michael@0 | 172 | @unittest.skipIf(mozinfo.isWin or not os.geteuid(), |
michael@0 | 173 | "Symlinks are not supported on Windows and cannot run test as root") |
michael@0 | 174 | def test_remove_symlink_for_system_path(self): |
michael@0 | 175 | """Test removing a symlink which points to a system folder""" |
michael@0 | 176 | symlink_path = os.path.join(self.tempdir, 'symlink') |
michael@0 | 177 | |
michael@0 | 178 | os.symlink(os.path.dirname(self.tempdir), symlink_path) |
michael@0 | 179 | self.assertTrue(os.path.islink(symlink_path)) |
michael@0 | 180 | |
michael@0 | 181 | # The folder with the contained symlink will be deleted but not the |
michael@0 | 182 | # original linked file |
michael@0 | 183 | mozfile.remove(symlink_path) |
michael@0 | 184 | self.assertFalse(os.path.exists(symlink_path)) |