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

F
Frost Ming 已提交
13
import pytest
F
frostming 已提交
14
from click.testing import CliRunner
F
Frost Ming 已提交
15 16
from pip._internal.vcs import versioncontrol
from pip._vendor import requests
F
frostming 已提交
17
from pip._vendor.pkg_resources import safe_name
F
Frost Ming 已提交
18

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

F
Frost Ming 已提交
32
os.environ["CI"] = "1"
33
main = Core()
F
frostming 已提交
34

F
frostming 已提交
35

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

F
Frost Ming 已提交
42 43 44
    def send(
        self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
    ):
D
Daniel Eades 已提交
45
        file_path = self.base_path / urlparse(request.url).path.lstrip("/")
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

77 78 79 80
class _FakeLink:
    is_wheel = False


F
frostming 已提交
81
class TestRepository(BaseRepository):
F
frostming 已提交
82 83
    def __init__(self, sources, environment):
        super().__init__(sources, environment)
F
frostming 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97
        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 已提交
98
        self, candidate: Candidate
F
frostming 已提交
99 100 101 102 103 104 105 106
    ) -> 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, []))
F
frostming 已提交
107
        deps = filter_requirements_with_extras(deps, candidate.req.extras or ())
F
frostming 已提交
108 109 110 111 112 113 114 115 116 117 118
        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 已提交
119

120
    def _find_candidates(self, requirement: Requirement) -> Iterable[Candidate]:
F
frostming 已提交
121 122
        for version, candidate in self._pypi_data.get(requirement.key, {}).items():
            c = Candidate(
F
frostming 已提交
123 124 125 126
                requirement,
                self.environment,
                name=requirement.project_name,
                version=version,
F
frostming 已提交
127
            )
F
Frost Ming 已提交
128
            c.requires_python = candidate.get("requires_python", "")
129 130
            c.link = _FakeLink()
            yield c
F
frostming 已提交
131

F
frostming 已提交
132 133 134 135
    def load_fixtures(self):
        json_file = FIXTURES / "pypi.json"
        self._pypi_data = json.loads(json_file.read_text())

F
frostming 已提交
136

F
frostming 已提交
137
class TestProject(Project):
138 139 140 141
    def __init__(self, core, root_path, is_global):
        self.root_path = Path(root_path or ".")
        self.GLOBAL_PROJECT = self.root_path / ".pdm-home" / "global-project"
        super().__init__(core, root_path, is_global)
F
frostming 已提交
142 143 144

    @cached_property
    def global_config(self):
145 146 147 148
        return Config(self.root_path / ".pdm-home" / "config.toml", is_global=True)


main.project_class = TestProject
F
frostming 已提交
149 150


F
frostming 已提交
151
class Distribution:
152
    def __init__(self, key, version, editable=False):
F
frostming 已提交
153 154
        self.key = key
        self.version = version
155
        self.editable = editable
F
frostming 已提交
156 157 158 159
        self.dependencies = []

    def requires(self, extras=()):
        return self.dependencies
F
Frost Ming 已提交
160 161 162 163 164 165 166


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

F
frostming 已提交
167 168
    def add_distribution(self, dist):
        self._data[dist.key] = dist
F
Frost Ming 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

    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 已提交
187
def working_set(mocker, repository):
F
Frost Ming 已提交
188
    from pdm.models.pip_shims import pip_logging
F
Frost Ming 已提交
189

F
Frost Ming 已提交
190 191 192 193
    rv = MockWorkingSet()
    mocker.patch.object(Environment, "get_working_set", return_value=rv)

    def install(candidate):
F
Frost Ming 已提交
194
        pip_logging._log_state.indentation = 0
F
frostming 已提交
195 196
        dependencies = repository.get_dependencies(candidate)[0]
        key = safe_name(candidate.name).lower()
197
        dist = Distribution(key, candidate.version, candidate.req.editable)
F
frostming 已提交
198 199
        dist.dependencies = dependencies
        rv.add_distribution(dist)
F
Frost Ming 已提交
200

F
Frost Ming 已提交
201 202
    def uninstall(dist):
        del rv[dist.key]
F
Frost Ming 已提交
203 204 205 206

    installer = mocker.MagicMock()
    installer.install.side_effect = install
    installer.uninstall.side_effect = uninstall
F
Frost Ming 已提交
207
    mocker.patch("pdm.installers.synchronizers.Installer", return_value=installer)
F
frostming 已提交
208
    mocker.patch("pdm.installers.Installer", return_value=installer)
F
Frost Ming 已提交
209 210

    yield rv
F
frostming 已提交
211 212


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


F
frostming 已提交
219 220
@pytest.fixture(autouse=True)
def pip_global_tempdir_manager():
F
Frost Ming 已提交
221
    from pdm.models.pip_shims import global_tempdir_manager
F
frostming 已提交
222 223 224 225 226

    with global_tempdir_manager():
        yield


F
frostming 已提交
227
@pytest.fixture()
F
frostming 已提交
228
def project_no_init(tmp_path, mocker):
229
    p = main.create_project(tmp_path)
F
frostming 已提交
230 231
    mocker.patch("pdm.utils.get_finder", get_local_finder)
    mocker.patch("pdm.models.environment.get_finder", get_local_finder)
F
frostming 已提交
232
    mocker.patch("pdm.project.core.Config.HOME_CONFIG", tmp_path)
F
Frost Ming 已提交
233
    old_config_map = Config._config_map.copy()
F
frostming 已提交
234
    p.global_config["cache_dir"] = tmp_path.joinpath("caches").as_posix()
235
    do_use(p, getattr(sys, "_base_executable", sys.executable))
F
Frost Ming 已提交
236 237
    with temp_environ():
        os.environ.pop("VIRTUAL_ENV", None)
F
Frost Ming 已提交
238
        os.environ.pop("PYTHONPATH", None)
F
Frost Ming 已提交
239
        os.environ.pop("PEP582_PACKAGES", None)
F
Frost Ming 已提交
240
        yield p
F
Frost Ming 已提交
241 242
    # Restore the config items
    Config._config_map = old_config_map
F
Frost Ming 已提交
243 244


F
Frost Ming 已提交
245
@pytest.fixture()
F
frostming 已提交
246 247
def project(project_no_init):
    do_init(project_no_init, "test_project", "0.0.0")
F
Frost Ming 已提交
248
    # Clean the cached property
249
    project_no_init._environment = None
F
frostming 已提交
250
    return project_no_init
F
Frost Ming 已提交
251 252


F
frostming 已提交
253 254 255 256 257 258
@pytest.fixture()
def fixture_project(project_no_init):
    """Initailize a project from a fixture project"""

    def func(project_name):
        source = FIXTURES / "projects" / project_name
F
frostming 已提交
259
        copy_tree(source.as_posix(), project_no_init.root.as_posix())
F
Frost Ming 已提交
260
        project_no_init._pyproject = None
F
frostming 已提交
261 262 263 264 265
        return project_no_init

    return func


F
frostming 已提交
266
@pytest.fixture()
267
def repository(project, mocker):
F
frostming 已提交
268
    rv = TestRepository([], project.environment)
269
    mocker.patch.object(project, "get_repository", return_value=rv)
F
frostming 已提交
270 271 272
    return rv


F
Frost Ming 已提交
273 274 275 276 277 278
@pytest.fixture()
def vcs(mocker):
    ret = MockVersionControl()
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend", return_value=ret
    )
F
frostming 已提交
279 280 281 282
    mocker.patch(
        "pip._internal.vcs.versioncontrol.VcsSupport.get_backend_for_scheme",
        return_value=ret,
    )
F
Frost Ming 已提交
283 284 285 286 287 288
    yield ret


@pytest.fixture(params=[False, True])
def is_editable(request):
    return request.param
F
frostming 已提交
289 290 291 292 293


@pytest.fixture(params=[False, True])
def is_dev(request):
    return request.param
F
frostming 已提交
294 295 296 297


@pytest.fixture()
def invoke():
F
Frost Ming 已提交
298
    runner = CliRunner(mix_stderr=False)
F
frostming 已提交
299
    return functools.partial(runner.invoke, main, prog_name="pdm")
300 301 302 303 304


@pytest.fixture()
def core():
    return main