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

pantsbuild / pants / 18771072790

24 Oct 2025 06:00AM UTC coverage: 80.282% (+0.003%) from 80.279%
18771072790

push

github

web-flow
Ensure that materialization is idempotent as advertised. (#22757)

Materializing from the store is supposed to be idempotent. 

This is important in cases such as:
- Materializing over an existing directory in `dist/`.
- The sandboxer retrying an operation after a restart.

However in practice it was not idempotent, in several ways.

This change fixes how files, hardlinks and symlinks are
written, to ensure idempotence. It also updates the tests
to verify this.

77874 of 97000 relevant lines covered (80.28%)

3.88 hits per line

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

85.94
/src/python/pants/testutil/python_interpreter_selection.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
13✔
5

6
import os
13✔
7
import subprocess
13✔
8
from collections.abc import Iterable
13✔
9
from functools import lru_cache
13✔
10
from unittest import skipIf
13✔
11

12
import _pytest.mark.structures
13✔
13
import pytest
13✔
14

15
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
13✔
16

17
PY_2 = "2"
13✔
18
PY_3 = "3"
13✔
19

20
PY_27 = "2.7"
13✔
21
PY_36 = "3.6"
13✔
22
PY_37 = "3.7"
13✔
23
PY_38 = "3.8"
13✔
24
PY_39 = "3.9"
13✔
25
PY_310 = "3.10"
13✔
26
PY_311 = "3.11"
13✔
27

28

29
def has_python_version(version):
13✔
30
    """Returns `True` if the current system has the specified version of python.
31

32
    :param string version: A python version string, such as 2.7, 3.
33
    """
34
    # TODO: Tests that skip unless a python interpreter is present often need the path to that
35
    # interpreter, and so end up calling python_interpreter_path again. Find a way to streamline this.
36
    return python_interpreter_path(version) is not None
13✔
37

38

39
@lru_cache
13✔
40
def python_interpreter_path(version):
13✔
41
    """Returns the interpreter path if the current system has the specified version of python.
42

43
    :param string version: A python version string, such as 2.7, 3.
44
    :returns: the normalized path to the interpreter binary if found; otherwise `None`
45
    :rtype: string
46
    """
47
    try:
13✔
48
        command = [f"python{version}", "-c", "import sys; print(sys.executable)"]
13✔
49
        py_path = subprocess.check_output(command).decode().strip()
13✔
50
        return os.path.realpath(py_path)
13✔
51
    except (subprocess.CalledProcessError, FileNotFoundError):
1✔
52
        return None
1✔
53

54

55
def skip_unless_all_pythons_present(*versions):
13✔
56
    """A decorator that only runs the decorated test method if all of the specified pythons are
57
    present.
58

59
    :param string *versions: Python version strings, such as 2.7, 3.
60
    """
61
    missing_versions = [v for v in versions if not has_python_version(v)]
12✔
62
    if len(missing_versions) == 1:
12✔
63
        return skipIf(True, f"Could not find python {missing_versions[0]} on system. Skipping.")
1✔
64
    elif len(missing_versions) > 1:
12✔
65
        return skipIf(
×
66
            True,
67
            "Skipping due to the following missing required pythons: {}".format(
68
                ", ".join(missing_versions)
69
            ),
70
        )
71
    else:
72
        return skipIf(False, "All required pythons present, continuing with test!")
12✔
73

74

75
def skip_unless_python27_present(func):
13✔
76
    """A test skip decorator that only runs a test method if python2.7 is present."""
77
    return skip_unless_all_pythons_present(PY_27)(func)
×
78

79

80
def skip_unless_python3_present(func):
13✔
81
    """A test skip decorator that only runs a test method if python3 is present."""
82
    return skip_unless_all_pythons_present(PY_3)(func)
×
83

84

85
def skip_unless_python36_present(func):
13✔
86
    """A test skip decorator that only runs a test method if python3.6 is present."""
87
    return skip_unless_all_pythons_present(PY_36)(func)
×
88

89

90
def skip_unless_python37_present(func):
13✔
91
    """A test skip decorator that only runs a test method if python3.7 is present."""
92
    return skip_unless_all_pythons_present(PY_37)(func)
1✔
93

94

95
def skip_unless_python38_present(func):
13✔
96
    """A test skip decorator that only runs a test method if python3.8 is present."""
97
    return skip_unless_all_pythons_present(PY_38)(func)
10✔
98

99

100
def skip_unless_python39_present(func):
13✔
101
    """A test skip decorator that only runs a test method if python3.9 is present."""
102
    return skip_unless_all_pythons_present(PY_39)(func)
10✔
103

104

105
def skip_unless_python310_present(func):
13✔
106
    """A test skip decorator that only runs a test method if python3.10 is present."""
107
    return skip_unless_all_pythons_present(PY_310)(func)
×
108

109

110
def skip_unless_python311_present(func):
13✔
111
    """A test skip decorator that only runs a test method if python3.11 is present."""
112
    return skip_unless_all_pythons_present(PY_311)(func)
×
113

114

115
def skip_unless_python310_and_python311_present(func):
13✔
116
    """A test skip decorator that only runs a test method if python3.8 and python3.9 are present."""
117
    return skip_unless_all_pythons_present(PY_310, PY_311)(func)
6✔
118

119

120
def skip_unless_python27_and_python3_present(func):
13✔
121
    """A test skip decorator that only runs a test method if python2.7 and python3 are present."""
122
    return skip_unless_all_pythons_present(PY_27, PY_3)(func)
×
123

124

125
def skip_unless_python27_and_python36_present(func):
13✔
126
    """A test skip decorator that only runs a test method if python2.7 and python3.6 are present."""
127
    return skip_unless_all_pythons_present(PY_27, PY_36)(func)
×
128

129

130
def skip_unless_python38_and_python39_present(func):
13✔
131
    """A test skip decorator that only runs a test method if python3.8 and python3.9 are present."""
132
    return skip_unless_all_pythons_present(PY_38, PY_39)(func)
1✔
133

134

135
def skip_unless_python37_and_python39_present(func):
13✔
136
    """A test skip decorator that only runs a test method if python3.7 and python3.9 are present."""
137
    return skip_unless_all_pythons_present(PY_37, PY_39)(func)
×
138

139

140
def all_major_minor_python_versions(
13✔
141
    constraints: Iterable[str],
142
) -> tuple[_pytest.mark.structures.ParameterSet, ...]:
143
    """All major.minor Python versions used by the interpreter constraints.
144

145
    This is intended to be used with `@pytest.mark.parametrize()` to run a test with every relevant
146
    Python interpreter.
147
    """
148
    versions = InterpreterConstraints(constraints).partition_into_major_minor_versions(
13✔
149
        # Please update this when new stable Python versions are released to CI.
150
        interpreter_universe=["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
151
    )
152
    return tuple(
13✔
153
        pytest.param(
154
            version,
155
            marks=pytest.mark.skipif(
156
                not has_python_version(version),
157
                reason=f"Could not find python {version} on system. Skipping.",
158
            ),
159
        )
160
        for version in versions
161
    )
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