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

pantsbuild / pants / 18847018991

27 Oct 2025 03:45PM UTC coverage: 92.254% (+12.0%) from 80.282%
18847018991

Pull #22816

github

web-flow
Merge f1312fa87 into 06e216752
Pull Request #22816: Update Pants internal Python to 3.14

39 of 40 new or added lines in 11 files covered. (97.5%)

382 existing lines in 22 files now uncovered.

89230 of 96722 relevant lines covered (92.25%)

3.72 hits per line

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

77.78
/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 os
1✔
7
import platform
1✔
8
import re
1✔
9
import shutil
1✔
10
from collections.abc import Mapping, MutableMapping
1✔
11
from textwrap import dedent
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
    tmpdir: str, py_resolve_format: PythonResolveExportFormat, py_hermetic_scripts: bool = True
43
) -> Mapping:
44
    cfg: MutableMapping = {
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": f"{tmpdir}/3rdparty/a.lock",
53
                "b": f"{tmpdir}/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
    return cfg
1✔
63

64

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

86
    export_prefix = os.path.join("dist", "export", "python", "virtualenvs")
1✔
87
    assert os.path.isdir(export_prefix), (
1✔
88
        f"expected export prefix dir '{export_prefix}' does not exist"
89
    )
90
    py_minor_version = f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}"
1✔
91
    for resolve, ansicolors_version in [("a", "1.1.8"), ("b", "1.0.2")]:
1✔
92
        export_resolve_dir = os.path.join(export_prefix, resolve)
1✔
93
        assert os.path.isdir(export_resolve_dir), (
1✔
94
            f"expected export resolve dir '{export_resolve_dir}' does not exist"
95
        )
96

97
        export_dir = os.path.join(export_resolve_dir, platform.python_version())
1✔
98
        assert os.path.isdir(export_dir), f"expected export dir '{export_dir}' does not exist"
1✔
99
        if py_resolve_format == PythonResolveExportFormat.symlinked_immutable_virtualenv:
1✔
100
            assert os.path.islink(export_dir), (
1✔
101
                f"expected export dir '{export_dir}' is not a symlink"
102
            )
103

104
        lib_dir = os.path.join(export_dir, "lib", f"python{py_minor_version}", "site-packages")
1✔
105
        assert os.path.isdir(lib_dir), f"expected export lib dir '{lib_dir}' does not exist"
1✔
106
        expected_ansicolors_dir = os.path.join(
1✔
107
            lib_dir, f"ansicolors-{ansicolors_version}.dist-info"
108
        )
109
        assert os.path.isdir(expected_ansicolors_dir), (
1✔
110
            f"expected dist-info for ansicolors '{expected_ansicolors_dir}' does not exist"
111
        )
112

113
        if py_resolve_format == PythonResolveExportFormat.mutable_virtualenv:
1✔
114
            activate_path = os.path.join(export_dir, "bin", "activate")
1✔
115
            assert os.path.isfile(activate_path), "virtualenv's bin/activate is missing"
1✔
116
            with open(activate_path) as activate_file:
1✔
117
                activate_content = activate_file.read()
1✔
118

119
            prompt_re = re.compile(rf"""PS1=('|")\({resolve}/{platform.python_version()}\) """)
1✔
120
            assert prompt_re.search(activate_content) is not None, (
1✔
121
                "Expected PS1 prompt not defined in bin/activate."
122
            )
123

UNCOV
124
            script_path = os.path.join(export_dir, "bin", "wheel")
×
UNCOV
125
            assert os.path.isfile(script_path), (
×
126
                "expected wheel to be installed, but bin/wheel is missing"
127
            )
UNCOV
128
            with open(script_path) as script_file:
×
UNCOV
129
                shebang = script_file.readline().strip()
×
UNCOV
130
            if py_hermetic_scripts:
×
UNCOV
131
                assert shebang.endswith(" -sE")
×
132
            else:
UNCOV
133
                assert not shebang.endswith(" -sE")
×
134

UNCOV
135
            expected_foo_dir = os.path.join(lib_dir, "foo_dist-1.2.3.dist-info")
×
UNCOV
136
            if resolve == "b":
×
UNCOV
137
                assert not os.path.isdir(expected_foo_dir), (
×
138
                    f"unexpected dist-info for foo-dist '{expected_foo_dir}' exists"
139
                )
UNCOV
140
            elif resolve == "a":
×
141
                # make sure the editable wheel for the python_distribution is installed
UNCOV
142
                assert os.path.isdir(expected_foo_dir), (
×
143
                    f"expected dist-info for foo-dist '{expected_foo_dir}' does not exist"
144
                )
145
                # direct_url__pants__.json should be moved to direct_url.json
UNCOV
146
                expected_foo_direct_url_pants = os.path.join(
×
147
                    expected_foo_dir, "direct_url__pants__.json"
148
                )
UNCOV
149
                assert not os.path.isfile(expected_foo_direct_url_pants), (
×
150
                    f"expected direct_url__pants__.json for foo-dist '{expected_foo_direct_url_pants}' was not removed"
151
                )
UNCOV
152
                expected_foo_direct_url = os.path.join(expected_foo_dir, "direct_url.json")
×
UNCOV
153
                assert os.path.isfile(expected_foo_direct_url), (
×
154
                    f"expected direct_url.json for foo-dist '{expected_foo_direct_url}' does not exist"
155
                )
156

157

158
def test_symlinked_venv_resilience() -> None:
1✔
159
    with temporary_dir() as named_caches:
1✔
160
        pex_root = os.path.join(os.path.realpath(named_caches), "pex_root")
1✔
161
        with setup_tmpdir(SOURCES) as tmpdir:
1✔
162
            run_pants(
1✔
163
                [
164
                    f"--named-caches-dir={named_caches}",
165
                    "generate-lockfiles",
166
                    "export",
167
                    "--resolve=a",
168
                ],
169
                config=build_config(
170
                    tmpdir, PythonResolveExportFormat.symlinked_immutable_virtualenv
171
                ),
172
            ).assert_success()
173

174
            def check():
1✔
175
                export_dir = os.path.join(
1✔
176
                    "dist", "export", "python", "virtualenvs", "a", platform.python_version()
177
                )
178
                assert os.path.islink(export_dir)
1✔
179
                export_dir_tgt = os.readlink(export_dir)
1✔
180
                assert os.path.isdir(export_dir_tgt)
1✔
181
                assert os.path.commonpath([pex_root, export_dir_tgt]) == pex_root
1✔
182

183
            check()
1✔
184

185
            shutil.rmtree(pex_root)
1✔
186

187
            run_pants(
1✔
188
                [f"--named-caches-dir={named_caches}", "export", "--resolve=a"],
189
                config=build_config(
190
                    tmpdir, PythonResolveExportFormat.symlinked_immutable_virtualenv
191
                ),
192
            ).assert_success()
193

194
            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

© 2025 Coveralls, Inc