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