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

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

F
linting  
Frost Ming 已提交
16
from pdm._types import CandidateInfo
F
Frost Ming 已提交
17
from pdm.cli.actions import do_init, do_use
18
from pdm.core import main
F
frostming 已提交
19
from pdm.exceptions import CandidateInfoNotFound
20
from pdm.iostream import stream
F
frostming 已提交
21
from pdm.models.candidates import Candidate
F
Frost Ming 已提交
22
from pdm.models.environment import Environment
F
frostming 已提交
23 24 25
from pdm.models.repositories import BaseRepository
from pdm.models.requirements import Requirement
from pdm.models.specifiers import PySpecSet
F
frostming 已提交
26
from pdm.project import Project
F
frostming 已提交
27 28
from pdm.project.config import Config
from pdm.utils import cached_property, get_finder
F
frostming 已提交
29 30
from tests import FIXTURES

31
stream.disable_colors()
F
frostming 已提交
32

F
frostming 已提交
33

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

F
Frost Ming 已提交
40 41 42 43 44 45
    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 已提交
46
        response = requests.models.Response()
F
frostming 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
        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 已提交
65 66 67 68 69 70 71
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 已提交
72 73 74 75
    @classmethod
    def get_revision(cls, location):
        return "1234567890abcdef"

F
Frost Ming 已提交
76

F
frostming 已提交
77
class TestRepository(BaseRepository):
F
frostming 已提交
78 79
    def __init__(self, sources, environment):
        super().__init__(sources, environment)
F
frostming 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93
        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 已提交
94
        self, candidate: Candidate
F
frostming 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    ) -> 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 已提交
114

F
Frost Ming 已提交
115 116 117
    def _find_named_matches(
        self,
        requirement: Requirement,
F
frostming 已提交
118 119 120
        requires_python: PySpecSet = PySpecSet(),
        allow_prereleases: Optional[bool] = None,
        allow_all: bool = False,
F
Frost Ming 已提交
121
    ) -> List[Candidate]:
F
frostming 已提交
122 123 124 125 126 127
        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 已提交
128 129 130 131
                requirement,
                self.environment,
                name=requirement.project_name,
                version=version,
F
frostming 已提交
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
            )
            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 已提交
157

F
frostming 已提交
158 159 160 161
    def load_fixtures(self):
        json_file = FIXTURES / "pypi.json"
        self._pypi_data = json.loads(json_file.read_text())

F
frostming 已提交
162

F
frostming 已提交
163
class TestProject(Project):
F
frostming 已提交
164 165 166 167 168 169 170
    def __init__(self, root_path):
        self.GLOBAL_PROJECT = Path(root_path) / ".pdm-home" / "global-project"
        super().__init__(root_path)

    @cached_property
    def global_config(self):
        return Config(self.root / ".pdm-home" / "config.toml", is_global=True)
F
frostming 已提交
171 172


F
frostming 已提交
173 174 175 176 177 178 179 180
class Distribution:
    def __init__(self, key, version):
        self.key = key
        self.version = version
        self.dependencies = []

    def requires(self, extras=()):
        return self.dependencies
F
Frost Ming 已提交
181 182 183 184 185 186 187


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

F
frostming 已提交
188 189
    def add_distribution(self, dist):
        self._data[dist.key] = dist
F
Frost Ming 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

    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()
F
frostming 已提交
208
def working_set(mocker, repository):
F
Frost Ming 已提交
209 210 211 212
    rv = MockWorkingSet()
    mocker.patch.object(Environment, "get_working_set", return_value=rv)

    def install(candidate):
F
frostming 已提交
213 214 215 216 217
        dependencies = repository.get_dependencies(candidate)[0]
        key = safe_name(candidate.name).lower()
        dist = Distribution(key, candidate.version)
        dist.dependencies = dependencies
        rv.add_distribution(dist)
F
Frost Ming 已提交
218

F
Frost Ming 已提交
219 220
    def uninstall(dist):
        del rv[dist.key]
F
Frost Ming 已提交
221 222 223 224 225 226 227

    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 已提交
228 229


F
frostming 已提交
230 231 232 233
def get_local_finder(*args, **kwargs):
    finder = get_finder(*args, **kwargs)
    finder.session.mount("http://fixtures.test/", LocalFileAdapter(FIXTURES))
    return finder
F
frostming 已提交
234 235 236


@pytest.fixture()
F
frostming 已提交
237
def project_no_init(tmp_path, mocker):
F
frostming 已提交
238
    p = TestProject(tmp_path.as_posix())
239
    p.core = main
F
frostming 已提交
240 241
    mocker.patch("pdm.utils.get_finder", get_local_finder)
    mocker.patch("pdm.models.environment.get_finder", get_local_finder)
F
frostming 已提交
242
    mocker.patch("pdm.project.core.Config.HOME_CONFIG", tmp_path)
F
frostming 已提交
243
    p.global_config["cache_dir"] = tmp_path.joinpath("caches").as_posix()
F
Frost Ming 已提交
244
    do_use(p, sys.executable)
F
frostming 已提交
245
    return p
F
Frost Ming 已提交
246 247


F
Frost Ming 已提交
248
@pytest.fixture()
F
frostming 已提交
249 250 251
def project(project_no_init):
    do_init(project_no_init, "test_project", "0.0.0")
    return project_no_init
F
Frost Ming 已提交
252 253


F
frostming 已提交
254
@pytest.fixture()
255
def repository(project, mocker):
F
frostming 已提交
256
    rv = TestRepository([], project.environment)
257
    mocker.patch.object(project, "get_repository", return_value=rv)
F
frostming 已提交
258 259 260
    return rv


F
Frost Ming 已提交
261 262 263 264 265 266
@pytest.fixture()
def vcs(mocker):
    ret = MockVersionControl()
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend", return_value=ret
    )
F
frostming 已提交
267 268 269 270
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend_for_scheme",
        return_value=ret,
    )
F
Frost Ming 已提交
271 272 273 274 275 276
    yield ret


@pytest.fixture(params=[False, True])
def is_editable(request):
    return request.param
F
frostming 已提交
277 278 279 280 281


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