• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 20859302922

09 Jan 2026 05:00PM UTC coverage: 80.261% (-0.008%) from 80.269%
20859302922

Pull #22994

github

web-flow
Merge 4a045ad2f into 3782956e6
Pull Request #22994: Fix incorrect use of `PEX_PYTHON`.

3 of 14 new or added lines in 5 files covered. (21.43%)

20 existing lines in 2 files now uncovered.

78787 of 98164 relevant lines covered (80.26%)

3.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.46
/src/python/pants/backend/python/goals/export_integration_test.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import platform
1✔
7
import shutil
1✔
8
from collections.abc import Mapping
1✔
9
from pathlib import Path
1✔
10
from textwrap import dedent
1✔
11
from typing import Any
1✔
12

13
import pytest
1✔
14

15
from pants.backend.python.goals.export import PythonResolveExportFormat
1✔
16
from pants.testutil.pants_integration_test import run_pants, setup_tmpdir
1✔
17
from pants.util.contextutil import temporary_dir
1✔
18

19
SOURCES = {
1✔
20
    "3rdparty/BUILD": dedent(
21
        """\
22
        python_requirement(name='req1', requirements=['ansicolors==1.1.8'], resolve='a', modules=['colors'])
23
        python_requirement(name='req2', requirements=['ansicolors==1.0.2'], resolve='b', modules=['colors'])
24
        python_requirement(name='req3', requirements=['wheel'], resolve=parametrize('a', 'b'))
25
        """
26
    ),
27
    "src/python/foo.py": "from colors import *",
28
    "src/python/BUILD": dedent(
29
        """\
30
        python_source(name='foo', source='foo.py', resolve=parametrize('a', 'b'))
31
        python_distribution(
32
            name='dist',
33
            provides=python_artifact(name='foo-dist', version='1.2.3'),
34
            dependencies=[':foo@resolve=a'],
35
        )
36
        """
37
    ),
38
}
39

40

41
def build_config(
1✔
42
    py_resolve_format: PythonResolveExportFormat, py_hermetic_scripts: bool = True
43
) -> Mapping[str, Any]:
44
    return {
1✔
45
        "GLOBAL": {
46
            "backend_packages": ["pants.backend.python"],
47
        },
48
        "python": {
49
            "enable_resolves": True,
50
            "interpreter_constraints": [f"=={platform.python_version()}"],
51
            "resolves": {
52
                "a": "3rdparty/a.lock",
53
                "b": "3rdparty/b.lock",
54
            },
55
        },
56
        "export": {
57
            "py_resolve_format": py_resolve_format.value,
58
            "py_non_hermetic_scripts_in_resolve": [] if py_hermetic_scripts else ["a", "b"],
59
        },
60
    }
61

62

63
@pytest.mark.parametrize(
1✔
64
    "py_resolve_format,py_hermetic_scripts",
65
    [
66
        (PythonResolveExportFormat.mutable_virtualenv, True),
67
        (PythonResolveExportFormat.mutable_virtualenv, False),
68
        (PythonResolveExportFormat.symlinked_immutable_virtualenv, True),
69
    ],
70
)
71
def test_export(py_resolve_format: PythonResolveExportFormat, py_hermetic_scripts: bool) -> None:
1✔
72
    with setup_tmpdir(SOURCES):
1✔
73
        resolve_names = ["a", "b"]
1✔
74
        run_pants(
1✔
75
            [
76
                "--print-stacktrace",
77
                "generate-lockfiles",
78
                "export",
79
                *(f"--resolve={name}" for name in resolve_names),
80
                "--export-py-editable-in-resolve=['a']",
81
            ],
82
            config=build_config(py_resolve_format, py_hermetic_scripts),
83
        ).assert_success()
84

85
    export_prefix = Path("dist") / "export" / "python" / "virtualenvs"
1✔
86
    assert export_prefix.is_dir(), f"export prefix dir '{export_prefix}' does not exist"
1✔
87

88
    py_minor_version = f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}"
1✔
89
    for resolve, ansicolors_version in [("a", "1.1.8"), ("b", "1.0.2")]:
1✔
90
        export_resolve_dir = export_prefix / resolve
1✔
91
        assert export_resolve_dir.is_dir(), (
1✔
92
            f"expected export resolve dir '{export_resolve_dir}' does not exist"
93
        )
94

95
        export_dir = export_resolve_dir / platform.python_version()
1✔
96
        assert export_dir.is_dir(), f"expected export dir '{export_dir}' does not exist"
1✔
97
        if py_resolve_format == PythonResolveExportFormat.symlinked_immutable_virtualenv:
1✔
98
            assert export_dir.is_symlink(), f"expected export dir '{export_dir}' is not a symlink"
1✔
99

100
        lib_dir = export_dir / "lib" / f"python{py_minor_version}" / "site-packages"
1✔
101
        assert lib_dir.is_dir(), f"expected export lib dir '{lib_dir}' does not exist"
1✔
102
        expected_ansicolors_dir = lib_dir / f"ansicolors-{ansicolors_version}.dist-info"
1✔
103
        assert expected_ansicolors_dir.is_dir(), (
1✔
104
            f"expected dist-info for ansicolors '{expected_ansicolors_dir}' does not exist"
105
        )
106

107
        if py_resolve_format == PythonResolveExportFormat.mutable_virtualenv:
1✔
UNCOV
108
            activate_path = export_dir / "bin" / "activate"
×
UNCOV
109
            activate_contents = activate_path.read_text()
×
UNCOV
110
            expected_version = f"{resolve}/{platform.python_version()}"
×
UNCOV
111
            assert any(
×
112
                line.strip().startswith("PS1=") and expected_version in line
113
                for line in activate_contents.splitlines()
114
            ), "Expected PS1 prompt not defined in bin/activate."
115

UNCOV
116
            script_path = export_dir / "bin" / "wheel"
×
UNCOV
117
            with script_path.open() as script_file:
×
UNCOV
118
                shebang = script_file.readline().strip()
×
UNCOV
119
                if py_hermetic_scripts:
×
UNCOV
120
                    assert shebang.endswith(" -sE")
×
121
                else:
UNCOV
122
                    assert not shebang.endswith(" -sE")
×
123

UNCOV
124
            expected_foo_dir = lib_dir / "foo_dist-1.2.3.dist-info"
×
UNCOV
125
            if resolve == "b":
×
UNCOV
126
                assert not expected_foo_dir.is_dir(), (
×
127
                    f"unexpected dist-info for foo-dist '{expected_foo_dir}' exists"
128
                )
UNCOV
129
            elif resolve == "a":
×
130
                # make sure the editable wheel for the python_distribution is installed
UNCOV
131
                assert expected_foo_dir.is_dir(), (
×
132
                    f"expected dist-info for foo-dist '{expected_foo_dir}' does not exist"
133
                )
134

135
                # direct_url__pants__.json should be moved to direct_url.json
UNCOV
136
                expected_foo_direct_url_pants = expected_foo_dir / "direct_url__pants__.json"
×
UNCOV
137
                assert not expected_foo_direct_url_pants.is_file(), (
×
138
                    f"expected direct_url__pants__.json for foo-dist '{expected_foo_direct_url_pants}' was not removed"
139
                )
140

UNCOV
141
                expected_foo_direct_url = expected_foo_dir / "direct_url.json"
×
UNCOV
142
                assert expected_foo_direct_url.is_file(), (
×
143
                    f"expected direct_url.json for foo-dist '{expected_foo_direct_url}' does not exist"
144
                )
145

146

147
def test_symlinked_venv_resilience() -> None:
1✔
148
    with temporary_dir() as named_caches:
1✔
149
        pex_root = Path(named_caches).resolve() / "pex_root"
1✔
150
        with setup_tmpdir(SOURCES):
1✔
151
            run_pants(
1✔
152
                [
153
                    f"--named-caches-dir={named_caches}",
154
                    "generate-lockfiles",
155
                    "export",
156
                    "--resolve=a",
157
                ],
158
                config=build_config(PythonResolveExportFormat.symlinked_immutable_virtualenv),
159
            ).assert_success()
160

161
            def check():
1✔
162
                py = platform.python_version()
1✔
163
                export_dir = Path("dist") / "export" / "python" / "virtualenvs" / "a" / py
1✔
164
                assert export_dir.is_symlink()
1✔
165
                export_dir_tgt = export_dir.readlink()
1✔
166
                assert export_dir_tgt.is_dir()
1✔
167
                assert export_dir_tgt.is_relative_to(pex_root)
1✔
168

169
            check()
1✔
170

171
            shutil.rmtree(pex_root)
1✔
172

173
            run_pants(
1✔
174
                [f"--named-caches-dir={named_caches}", "export", "--resolve=a"],
175
                config=build_config(PythonResolveExportFormat.symlinked_immutable_virtualenv),
176
            ).assert_success()
177

178
            check()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc