1import itertools 2import os 3import stat 4import tempfile 5 6import pytest 7 8from pip._internal.utils import temp_dir 9from pip._internal.utils.misc import ensure_dir 10from pip._internal.utils.temp_dir import ( 11 AdjacentTempDirectory, 12 TempDirectory, 13 _default, 14 global_tempdir_manager, 15 tempdir_registry, 16) 17 18 19# No need to test symlinked directories on Windows 20@pytest.mark.skipif("sys.platform == 'win32'") 21def test_symlinked_path(): 22 with TempDirectory() as tmp_dir: 23 assert os.path.exists(tmp_dir.path) 24 25 alt_tmp_dir = tempfile.mkdtemp(prefix="pip-test-") 26 assert ( 27 os.path.dirname(tmp_dir.path) == 28 os.path.dirname(os.path.realpath(alt_tmp_dir)) 29 ) 30 # are we on a system where /tmp is a symlink 31 if os.path.realpath(alt_tmp_dir) != os.path.abspath(alt_tmp_dir): 32 assert ( 33 os.path.dirname(tmp_dir.path) != 34 os.path.dirname(alt_tmp_dir) 35 ) 36 else: 37 assert ( 38 os.path.dirname(tmp_dir.path) == 39 os.path.dirname(alt_tmp_dir) 40 ) 41 os.rmdir(tmp_dir.path) 42 assert not os.path.exists(tmp_dir.path) 43 44 45def test_deletes_readonly_files(): 46 def create_file(*args): 47 fpath = os.path.join(*args) 48 ensure_dir(os.path.dirname(fpath)) 49 with open(fpath, "w") as f: 50 f.write("Holla!") 51 52 def readonly_file(*args): 53 fpath = os.path.join(*args) 54 os.chmod(fpath, stat.S_IREAD) 55 56 with TempDirectory() as tmp_dir: 57 create_file(tmp_dir.path, "normal-file") 58 create_file(tmp_dir.path, "readonly-file") 59 readonly_file(tmp_dir.path, "readonly-file") 60 61 create_file(tmp_dir.path, "subfolder", "normal-file") 62 create_file(tmp_dir.path, "subfolder", "readonly-file") 63 readonly_file(tmp_dir.path, "subfolder", "readonly-file") 64 65 66def test_path_access_after_context_raises(): 67 with TempDirectory() as tmp_dir: 68 path = tmp_dir.path 69 70 with pytest.raises(AssertionError) as e: 71 _ = tmp_dir.path 72 73 assert path in str(e.value) 74 75 76def test_path_access_after_clean_raises(): 77 tmp_dir = TempDirectory() 78 path = tmp_dir.path 79 tmp_dir.cleanup() 80 81 with pytest.raises(AssertionError) as e: 82 _ = tmp_dir.path 83 84 assert path in str(e.value) 85 86 87def test_create_and_cleanup_work(): 88 tmp_dir = TempDirectory() 89 created_path = tmp_dir.path 90 91 assert tmp_dir.path is not None 92 assert os.path.exists(created_path) 93 94 tmp_dir.cleanup() 95 assert not os.path.exists(created_path) 96 97 98@pytest.mark.parametrize("name", [ 99 "ABC", 100 "ABC.dist-info", 101 "_+-", 102 "_package", 103 "A......B", 104 "AB", 105 "A", 106 "2", 107]) 108def test_adjacent_directory_names(name): 109 def names(): 110 return AdjacentTempDirectory._generate_names(name) 111 112 chars = AdjacentTempDirectory.LEADING_CHARS 113 114 # Ensure many names are unique 115 # (For long *name*, this sequence can be extremely long. 116 # However, since we're only ever going to take the first 117 # result that works, provided there are many of those 118 # and that shorter names result in totally unique sets, 119 # it's okay to skip part of the test.) 120 some_names = list(itertools.islice(names(), 1000)) 121 # We should always get at least 1000 names 122 assert len(some_names) == 1000 123 124 # Ensure original name does not appear early in the set 125 assert name not in some_names 126 127 if len(name) > 2: 128 # Names should be at least 90% unique (given the infinite 129 # range of inputs, and the possibility that generated names 130 # may already exist on disk anyway, this is a much cheaper 131 # criteria to enforce than complete uniqueness). 132 assert len(some_names) > 0.9 * len(set(some_names)) 133 134 # Ensure the first few names are the same length as the original 135 same_len = list(itertools.takewhile( 136 lambda x: len(x) == len(name), 137 some_names 138 )) 139 assert len(same_len) > 10 140 141 # Check the first group are correct 142 expected_names = ['~' + name[1:]] 143 expected_names.extend('~' + c + name[2:] for c in chars) 144 for x, y in zip(some_names, expected_names): 145 assert x == y 146 147 else: 148 # All names are going to be longer than our original 149 assert min(len(x) for x in some_names) > 1 150 151 # All names are going to be unique 152 assert len(some_names) == len(set(some_names)) 153 154 if len(name) == 2: 155 # All but the first name are going to end with our original 156 assert all(x.endswith(name) for x in some_names[1:]) 157 else: 158 # All names are going to end with our original 159 assert all(x.endswith(name) for x in some_names) 160 161 162@pytest.mark.parametrize("name", [ 163 "A", 164 "ABC", 165 "ABC.dist-info", 166 "_+-", 167 "_package", 168]) 169def test_adjacent_directory_exists(name, tmpdir): 170 block_name, expect_name = itertools.islice( 171 AdjacentTempDirectory._generate_names(name), 2) 172 173 original = os.path.join(tmpdir, name) 174 blocker = os.path.join(tmpdir, block_name) 175 176 ensure_dir(original) 177 ensure_dir(blocker) 178 179 with AdjacentTempDirectory(original) as atmp_dir: 180 assert expect_name == os.path.split(atmp_dir.path)[1] 181 182 183def test_adjacent_directory_permission_error(monkeypatch): 184 name = "ABC" 185 186 def raising_mkdir(*args, **kwargs): 187 raise OSError("Unknown OSError") 188 189 with TempDirectory() as tmp_dir: 190 original = os.path.join(tmp_dir.path, name) 191 192 ensure_dir(original) 193 monkeypatch.setattr("os.mkdir", raising_mkdir) 194 195 with pytest.raises(OSError): 196 with AdjacentTempDirectory(original): 197 pass 198 199 200def test_global_tempdir_manager(): 201 with global_tempdir_manager(): 202 d = TempDirectory(globally_managed=True) 203 path = d.path 204 assert os.path.exists(path) 205 assert not os.path.exists(path) 206 207 208def test_tempdirectory_asserts_global_tempdir(monkeypatch): 209 monkeypatch.setattr(temp_dir, "_tempdir_manager", None) 210 with pytest.raises(AssertionError): 211 TempDirectory(globally_managed=True) 212 213 214deleted_kind = "deleted" 215not_deleted_kind = "not-deleted" 216 217 218@pytest.mark.parametrize("delete,kind,exists", [ 219 (None, deleted_kind, False), 220 (_default, deleted_kind, False), 221 (True, deleted_kind, False), 222 (False, deleted_kind, True), 223 (None, not_deleted_kind, True), 224 (_default, not_deleted_kind, True), 225 (True, not_deleted_kind, False), 226 (False, not_deleted_kind, True), 227 (None, "unspecified", False), 228 (_default, "unspecified", False), 229 (True, "unspecified", False), 230 (False, "unspecified", True), 231]) 232def test_tempdir_registry(kind, delete, exists): 233 with tempdir_registry() as registry: 234 registry.set_delete(deleted_kind, True) 235 registry.set_delete(not_deleted_kind, False) 236 237 with TempDirectory(delete=delete, kind=kind) as d: 238 path = d.path 239 assert os.path.exists(path) 240 assert os.path.exists(path) == exists 241 242 243@pytest.mark.parametrize("delete,exists", [ 244 (_default, True), (None, False) 245]) 246def test_temp_dir_does_not_delete_explicit_paths_by_default( 247 tmpdir, delete, exists 248): 249 path = tmpdir / "example" 250 path.mkdir() 251 252 with tempdir_registry() as registry: 253 registry.set_delete(deleted_kind, True) 254 255 with TempDirectory(path=path, delete=delete, kind=deleted_kind) as d: 256 assert str(d.path) == path 257 assert os.path.exists(path) 258 assert os.path.exists(path) == exists 259 260 261@pytest.mark.parametrize("should_delete", [True, False]) 262def test_tempdir_registry_lazy(should_delete): 263 """ 264 Test the registry entry can be updated after a temp dir is created, 265 to change whether a kind should be deleted or not. 266 """ 267 with tempdir_registry() as registry: 268 with TempDirectory(delete=None, kind="test-for-lazy") as d: 269 path = d.path 270 registry.set_delete("test-for-lazy", should_delete) 271 assert os.path.exists(path) 272 assert os.path.exists(path) == (not should_delete) 273