File: test_environment.py

package info (click to toggle)
pdm 2.26.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 5,664 kB
  • sloc: python: 26,438; sh: 314; javascript: 34; makefile: 26
file content (135 lines) | stat: -rw-r--r-- 4,866 bytes parent folder | download | duplicates (2)
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
import os
import sys
from pathlib import Path
from types import ModuleType, SimpleNamespace

import pytest

from pdm.environments.local import PythonLocalEnvironment
from pdm.utils import pdm_scheme


@pytest.fixture()
def local_env(project):
    # Ensure we construct a fresh local environment for each test
    env = PythonLocalEnvironment(project)
    return env


def test_packages_path_compat_suffix_32(local_env, tmp_path, monkeypatch):
    # Simulate interpreter identifier ending with -32, and only non-"-32" directory exists
    monkeypatch.setattr(local_env, "_interpreter", SimpleNamespace(identifier="3.11-32"))
    base = local_env.project.root / "__pypackages__"
    compat_dir = base / "3.11"
    compat_dir.mkdir(parents=True, exist_ok=True)
    # Also ensure parent exists for side effects inside packages_path
    p = local_env.packages_path
    assert p.name == "3.11"
    assert p.parent == base


def test_local_get_paths_headers_override(local_env):
    paths = local_env.get_paths(dist_name="mypkg")
    # Ensure headers path is under include/mypkg (cross-platform path check)
    from pathlib import Path as _P

    assert _P(paths["headers"]).parts[-2:] == ("include", "mypkg")
    # Sanity: scheme base is pep582
    scheme = pdm_scheme(local_env.packages_path.as_posix())
    for k in ("purelib", "platlib", "scripts", "data"):
        assert paths[k].startswith(scheme[k])


def test_pip_command_uses_existing_module(monkeypatch, project):
    # Simulate: `python -Esm pip --version` fails, but host pip is compatible
    env = PythonLocalEnvironment(project)

    class DummyCompleted:
        returncode = 1

    monkeypatch.setattr("subprocess.run", lambda *a, **k: DummyCompleted())

    # Provide a dummy pip module with a file path
    dummy_pip = ModuleType("pip")
    dummy_dir = Path(project.core.create_temp_dir(prefix="pip-test-"))
    dummy_file = dummy_dir / "__init__.py"
    dummy_file.write_text("")
    dummy_pip.__file__ = str(dummy_file)
    monkeypatch.setitem(sys.modules, "pip", dummy_pip)

    # Make it considered compatible; patch the symbol used in BaseEnvironment
    monkeypatch.setattr("pdm.environments.base.is_pip_compatible_with_python", lambda v: True)

    cmd = env.pip_command
    assert cmd[:3] == [str(env.interpreter.executable), "-Es", str(dummy_dir)]


def test_pip_command_download_fallback(monkeypatch, project):
    # Simulate: `python -Esm pip --version` fails and host pip is unavailable/incompatible
    env = PythonLocalEnvironment(project)

    class DummyCompleted:
        returncode = 1

    monkeypatch.setattr("subprocess.run", lambda *a, **k: DummyCompleted())

    # Force importing pip to fail inside BaseEnvironment so pip_location is None
    import builtins as _builtins

    _real_import = _builtins.__import__

    def _fake_import(name, globals=None, locals=None, fromlist=(), level=0):
        if name == "pip" or name.startswith("pip."):
            raise ImportError
        return _real_import(name, globals, locals, fromlist, level)

    monkeypatch.setattr("builtins.__import__", _fake_import)

    # Also ensure compatibility check returns False on the symbol used in BaseEnvironment
    monkeypatch.setattr("pdm.environments.base.is_pip_compatible_with_python", lambda v: False)

    def fake_download(path):
        # Create the expected wheel path
        Path(path).write_text("")

    # Avoid network: stub out the download function to just create the file
    monkeypatch.setattr(type(env), "_download_pip_wheel", lambda self, p: fake_download(p))

    cmd = env.pip_command
    # Expect the -m form not used, fallback to the wheel's pip script
    assert cmd[0] == str(env.interpreter.executable)
    assert Path(cmd[1]).name == "pip"


def test_pip_command_installed(monkeypatch, project):
    # Simulate: `python -Esm pip --version` succeeds -> use it directly
    env = PythonLocalEnvironment(project)

    class DummyCompleted:
        returncode = 0

    monkeypatch.setattr("subprocess.run", lambda *a, **k: DummyCompleted())

    cmd = env.pip_command
    assert cmd[:3] == [str(env.interpreter.executable), "-Esm", "pip"]


def test_script_kind_posix(local_env):
    # On non-Windows platforms, script_kind should be posix
    if os.name != "nt":
        assert local_env.script_kind == "posix"


def test_which_python_variants(local_env):
    # Should resolve to interpreter path when asking for pythonN or python
    exe = str(local_env.interpreter.executable)
    assert local_env.which("python") == exe
    # python3 matches the major version
    assert local_env.which(f"python{local_env.interpreter.version.major}") == exe


def test_process_env_includes_scripts_first(local_env):
    env = local_env.process_env
    scripts = local_env.get_paths()["scripts"]
    path_entries = env["PATH"].split(os.pathsep)
    assert path_entries[0] == scripts