summaryrefslogtreecommitdiff
path: root/tests/unit/test_locations.py
blob: 77567665376327eadc66f0f9809e3dea104a91f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
"""
locations.py tests

"""
import getpass
import os
import shutil
import sys
import sysconfig
import tempfile
from pathlib import Path
from typing import Any, Dict
from unittest.mock import Mock

import pytest

from pip._internal.locations import SCHEME_KEYS, _should_use_sysconfig, get_scheme

if sys.platform == "win32":
    pwd = Mock()
else:
    import pwd


def _get_scheme_dict(*args: Any, **kwargs: Any) -> Dict[str, str]:
    scheme = get_scheme(*args, **kwargs)
    return {k: getattr(scheme, k) for k in SCHEME_KEYS}


class TestLocations:
    def setup(self) -> None:
        self.tempdir = tempfile.mkdtemp()
        self.st_uid = 9999
        self.username = "example"
        self.patch()

    def teardown(self) -> None:
        self.revert_patch()
        shutil.rmtree(self.tempdir, ignore_errors=True)

    def patch(self) -> None:
        """first store and then patch python methods pythons"""
        self.tempfile_gettempdir = tempfile.gettempdir
        self.old_os_fstat = os.fstat
        if sys.platform != "win32":
            # os.geteuid and pwd.getpwuid are not implemented on windows
            self.old_os_geteuid = os.geteuid
            self.old_pwd_getpwuid = pwd.getpwuid
        self.old_getpass_getuser = getpass.getuser

        # now patch
        tempfile.gettempdir = lambda: self.tempdir
        getpass.getuser = lambda: self.username
        os.fstat = lambda fd: self.get_mock_fstat(fd)
        if sys.platform != "win32":
            os.geteuid = lambda: self.st_uid
            pwd.getpwuid = self.get_mock_getpwuid

    def revert_patch(self) -> None:
        """revert the patches to python methods"""
        tempfile.gettempdir = self.tempfile_gettempdir
        getpass.getuser = self.old_getpass_getuser
        if sys.platform != "win32":
            # os.geteuid and pwd.getpwuid are not implemented on windows
            os.geteuid = self.old_os_geteuid
            pwd.getpwuid = self.old_pwd_getpwuid
        os.fstat = self.old_os_fstat

    def get_mock_fstat(self, fd: int) -> os.stat_result:
        """returns a basic mock fstat call result.
        Currently only the st_uid attribute has been set.
        """
        result = Mock()
        result.st_uid = self.st_uid
        return result

    def get_mock_getpwuid(self, uid: int) -> Any:
        """returns a basic mock pwd.getpwuid call result.
        Currently only the pw_name attribute has been set.
        """
        result = Mock()
        result.pw_name = self.username
        return result

    def test_default_should_use_sysconfig(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.delattr(sysconfig, "_PIP_USE_SYSCONFIG", raising=False)
        if sys.version_info[:2] >= (3, 10):
            assert _should_use_sysconfig() is True
        else:
            assert _should_use_sysconfig() is False

    @pytest.mark.parametrize("vendor_value", [True, False, None, "", 0, 1])
    def test_vendor_overriden_should_use_sysconfig(
        self, monkeypatch: pytest.MonkeyPatch, vendor_value: Any
    ) -> None:
        monkeypatch.setattr(
            sysconfig, "_PIP_USE_SYSCONFIG", vendor_value, raising=False
        )
        assert _should_use_sysconfig() is bool(vendor_value)


class TestDistutilsScheme:
    def test_root_modifies_appropriately(self) -> None:
        # This deals with nt/posix path differences
        # root is c:\somewhere\else or /somewhere/else
        root = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        norm_scheme = _get_scheme_dict("example")
        root_scheme = _get_scheme_dict("example", root=root)

        for key, value in norm_scheme.items():
            drive, path = os.path.splitdrive(os.path.abspath(value))
            expected = os.path.join(root, path[1:])
            assert os.path.abspath(root_scheme[key]) == expected

    @pytest.mark.incompatible_with_sysconfig
    @pytest.mark.incompatible_with_venv
    def test_distutils_config_file_read(
        self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        # This deals with nt/posix path differences
        install_scripts = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        f = tmpdir / "config" / "setup.cfg"
        f.parent.mkdir()
        f.write_text("[install]\ninstall-scripts=" + install_scripts)
        from distutils.dist import Distribution

        # patch the function that returns what config files are present
        monkeypatch.setattr(
            Distribution,
            "find_config_files",
            lambda self: [f],
        )
        scheme = _get_scheme_dict("example")
        assert scheme["scripts"] == install_scripts

    @pytest.mark.incompatible_with_sysconfig
    @pytest.mark.incompatible_with_venv
    # when we request install-lib, we should install everything (.py &
    # .so) into that path; i.e. ensure platlib & purelib are set to
    # this path. sysconfig does not support this.
    def test_install_lib_takes_precedence(
        self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        # This deals with nt/posix path differences
        install_lib = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        f = tmpdir / "config" / "setup.cfg"
        f.parent.mkdir()
        f.write_text("[install]\ninstall-lib=" + install_lib)
        from distutils.dist import Distribution

        # patch the function that returns what config files are present
        monkeypatch.setattr(
            Distribution,
            "find_config_files",
            lambda self: [f],
        )
        scheme = _get_scheme_dict("example")
        assert scheme["platlib"] == install_lib + os.path.sep
        assert scheme["purelib"] == install_lib + os.path.sep

    def test_prefix_modifies_appropriately(self) -> None:
        prefix = os.path.abspath(os.path.join("somewhere", "else"))

        normal_scheme = _get_scheme_dict("example")
        prefix_scheme = _get_scheme_dict("example", prefix=prefix)

        def _calculate_expected(value: str) -> str:
            path = os.path.join(prefix, os.path.relpath(value, sys.prefix))
            return os.path.normpath(path)

        expected = {k: _calculate_expected(v) for k, v in normal_scheme.items()}
        assert prefix_scheme == expected