conftest.py 7.9 KB
Newer Older
F
Frost Ming 已提交
1
import collections
F
frostming 已提交
2
import json
F
Frost Ming 已提交
3 4
import os
import shutil
F
frostming 已提交
5 6
from io import BytesIO
from pathlib import Path
F
frostming 已提交
7
from typing import Callable, Iterable, List, Optional, Tuple
F
frostming 已提交
8 9
from urllib.parse import urlparse

F
Frost Ming 已提交
10 11
from pip._internal.vcs import versioncontrol
from pip._vendor import requests
F
frostming 已提交
12
from pip._vendor.pkg_resources import safe_name
F
Frost Ming 已提交
13

F
Frost Ming 已提交
14
import pytest
F
linting  
Frost Ming 已提交
15
from pdm._types import CandidateInfo
F
frostming 已提交
16
from pdm.cli.actions import do_init
F
frostming 已提交
17
from pdm.context import context
F
frostming 已提交
18
from pdm.exceptions import CandidateInfoNotFound
F
frostming 已提交
19
from pdm.models.candidates import Candidate
F
Frost Ming 已提交
20
from pdm.models.environment import Environment
F
frostming 已提交
21 22 23
from pdm.models.repositories import BaseRepository
from pdm.models.requirements import Requirement
from pdm.models.specifiers import PySpecSet
F
frostming 已提交
24
from pdm.project import Project
F
frostming 已提交
25 26 27
from pdm.utils import get_finder
from tests import FIXTURES

F
Frost Ming 已提交
28
context.io.disable_colors()
F
frostming 已提交
29

F
frostming 已提交
30

F
frostming 已提交
31
class LocalFileAdapter(requests.adapters.BaseAdapter):
F
frostming 已提交
32 33 34 35 36
    def __init__(self, base_path):
        super().__init__()
        self.base_path = base_path
        self._opened_files = []

F
Frost Ming 已提交
37 38 39 40 41 42
    def send(
        self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
    ):
        file_path = self.base_path / urlparse(request.url).path.lstrip(
            "/"
        )  # type: Path
F
Frost Ming 已提交
43
        response = requests.models.Response()
F
frostming 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        response.request = request
        if not file_path.exists():
            response.status_code = 404
            response.reason = "Not Found"
            response.raw = BytesIO(b"Not Found")
        else:
            response.status_code = 200
            response.reason = "OK"
            response.raw = file_path.open("rb")
        self._opened_files.append(response.raw)
        return response

    def close(self):
        for fp in self._opened_files:
            fp.close()
        self._opened_files.clear()


F
Frost Ming 已提交
62 63 64 65 66 67 68
class MockVersionControl(versioncontrol.VersionControl):
    def obtain(self, dest, url):
        url, _ = self.get_url_rev_options(url)
        path = os.path.splitext(os.path.basename(urlparse(str(url)).path))[0]
        mocked_path = FIXTURES / "projects" / path
        shutil.copytree(mocked_path, dest)

F
frostming 已提交
69 70 71 72
    @classmethod
    def get_revision(cls, location):
        return "1234567890abcdef"

F
Frost Ming 已提交
73

F
frostming 已提交
74
class TestRepository(BaseRepository):
F
frostming 已提交
75 76
    def __init__(self, sources, environment):
        super().__init__(sources, environment)
F
frostming 已提交
77 78 79 80 81 82 83 84 85 86 87 88 89 90
        self._pypi_data = {}
        self.load_fixtures()

    def add_candidate(self, name, version, requires_python=""):
        pypi_data = self._pypi_data.setdefault(safe_name(name), {}).setdefault(
            version, {}
        )
        pypi_data["requires_python"] = requires_python

    def add_dependencies(self, name, version, requirements):
        pypi_data = self._pypi_data[safe_name(name)][version]
        pypi_data.setdefault("dependencies", []).extend(requirements)

    def _get_dependencies_from_fixture(
F
Frost Ming 已提交
91
        self, candidate: Candidate
F
frostming 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    ) -> Tuple[List[str], str, str]:
        try:
            pypi_data = self._pypi_data[candidate.req.key][candidate.version]
        except KeyError:
            raise CandidateInfoNotFound(candidate)
        deps = pypi_data.get("dependencies", [])
        for extra in candidate.req.extras or ():
            deps.extend(pypi_data.get("extras_require", {}).get(extra, []))
        return deps, pypi_data.get("requires_python", ""), ""

    def dependency_generators(self) -> Iterable[Callable[[Candidate], CandidateInfo]]:
        return (
            self._get_dependencies_from_cache,
            self._get_dependencies_from_fixture,
            self._get_dependencies_from_metadata,
        )

    def get_hashes(self, candidate: Candidate) -> None:
        candidate.hashes = {}
F
frostming 已提交
111

F
Frost Ming 已提交
112 113 114
    def _find_named_matches(
        self,
        requirement: Requirement,
F
frostming 已提交
115 116 117
        requires_python: PySpecSet = PySpecSet(),
        allow_prereleases: Optional[bool] = None,
        allow_all: bool = False,
F
Frost Ming 已提交
118
    ) -> List[Candidate]:
F
frostming 已提交
119 120 121 122 123 124
        if allow_prereleases is None:
            allow_prereleases = requirement.allow_prereleases

        cans = []
        for version, candidate in self._pypi_data.get(requirement.key, {}).items():
            c = Candidate(
F
frostming 已提交
125 126 127 128
                requirement,
                self.environment,
                name=requirement.project_name,
                version=version,
F
frostming 已提交
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
            )
            c._requires_python = PySpecSet(candidate.get("requires_python", ""))
            cans.append(c)

        sorted_cans = sorted(
            (
                c
                for c in cans
                if requirement.specifier.contains(c.version, allow_prereleases)
            ),
            key=lambda c: c.version,
        )
        if not allow_all:
            sorted_cans = [
                can
                for can in sorted_cans
                if requires_python.is_subset(can.requires_python)
            ]
        if not sorted_cans and allow_prereleases is None:
            # No non-pre-releases is found, force pre-releases now
            sorted_cans = sorted(
                (c for c in cans if requirement.specifier.contains(c.version, True)),
                key=lambda c: c.version,
            )
        return sorted_cans
F
frostming 已提交
154

F
frostming 已提交
155 156 157 158
    def load_fixtures(self):
        json_file = FIXTURES / "pypi.json"
        self._pypi_data = json.loads(json_file.read_text())

F
frostming 已提交
159

F
frostming 已提交
160
class TestProject(Project):
F
frostming 已提交
161 162 163
    pass


F
Frost Ming 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
Distribution = collections.namedtuple("Distribution", "key,version")


class MockWorkingSet(collections.abc.MutableMapping):
    def __init__(self, *args, **kwargs):
        self.pkg_ws = None
        self._data = {}

    def add_candidate(self, candidate):
        key = safe_name(candidate.name).lower()
        self._data[key] = Distribution(key, candidate.version)

    def __getitem__(self, key):
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    def __setitem__(self, key, value):
        self._data[key] = value

    def __delitem__(self, key):
        del self._data[key]


@pytest.fixture()
def working_set(mocker):
    rv = MockWorkingSet()
    mocker.patch.object(Environment, "get_working_set", return_value=rv)

    def install(candidate):
        rv.add_candidate(candidate)

    def uninstall(name):
        del rv[safe_name(name).lower()]

    installer = mocker.MagicMock()
    installer.install.side_effect = install
    installer.uninstall.side_effect = uninstall
    mocker.patch("pdm.installers.Installer", return_value=installer)

    yield rv
F
frostming 已提交
209 210


F
frostming 已提交
211 212 213 214
def get_local_finder(*args, **kwargs):
    finder = get_finder(*args, **kwargs)
    finder.session.mount("http://fixtures.test/", LocalFileAdapter(FIXTURES))
    return finder
F
frostming 已提交
215 216 217


@pytest.fixture()
F
frostming 已提交
218
def project(tmp_path, mocker):
F
frostming 已提交
219 220
    p = TestProject(tmp_path.as_posix())
    p.config["cache_dir"] = tmp_path.joinpath("caches").as_posix()
F
frostming 已提交
221 222
    mocker.patch("pdm.utils.get_finder", get_local_finder)
    mocker.patch("pdm.models.environment.get_finder", get_local_finder)
F
frostming 已提交
223
    mocker.patch("pdm.cli.commands.Project", return_value=p)
F
frostming 已提交
224
    do_init(p, "test_project", "0.0.0")
F
frostming 已提交
225
    return p
F
Frost Ming 已提交
226 227


F
Frost Ming 已提交
228 229 230 231 232 233 234 235 236 237
@pytest.fixture()
def project_no_init(tmp_path, mocker):
    p = TestProject(tmp_path.as_posix())
    p.config["cache_dir"] = tmp_path.joinpath("caches").as_posix()
    mocker.patch("pdm.utils.get_finder", get_local_finder)
    mocker.patch("pdm.models.environment.get_finder", get_local_finder)
    mocker.patch("pdm.cli.commands.Project", return_value=p)
    return p


F
frostming 已提交
238 239
@pytest.fixture()
def repository(project):
F
frostming 已提交
240 241 242 243 244
    rv = TestRepository([], project.environment)
    project.get_repository = lambda: rv
    return rv


F
Frost Ming 已提交
245 246 247 248 249 250
@pytest.fixture()
def vcs(mocker):
    ret = MockVersionControl()
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend", return_value=ret
    )
F
frostming 已提交
251 252 253 254
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend_for_scheme",
        return_value=ret,
    )
F
Frost Ming 已提交
255 256 257 258 259 260
    yield ret


@pytest.fixture(params=[False, True])
def is_editable(request):
    return request.param
F
frostming 已提交
261 262 263 264 265


@pytest.fixture(params=[False, True])
def is_dev(request):
    return request.param