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

pantsbuild / pants / 21572405674

01 Feb 2026 11:36PM UTC coverage: 80.273% (-0.05%) from 80.324%
21572405674

push

github

web-flow
Remove support for Get (#23062)

Delete the bulk of the code needed to
support `Get` (and `Effect`).

Follow up changes will further trim code
that is no longer needed, but this
is already a big enough step for now.

23 of 27 new or added lines in 5 files covered. (85.19%)

49 existing lines in 3 files now uncovered.

78274 of 97510 relevant lines covered (80.27%)

3.1 hits per line

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

74.38
/src/python/pants/engine/internals/rule_visitor_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 importlib
1✔
7
import sys
1✔
8
import textwrap
1✔
9
from collections.abc import Iterable
1✔
10
from contextlib import contextmanager
1✔
11
from pathlib import Path
1✔
12

13
from pants.engine.internals.rule_visitor import collect_awaitables
1✔
14
from pants.engine.rules import implicitly, rule
1✔
15

16
# The visitor inspects the module for definitions.
17
STR = str
1✔
18
INT = int
1✔
19
BOOL = bool
1✔
20

21

22
@rule
1✔
23
async def str_from_int(i: int) -> str:
1✔
24
    return str(i)
×
25

26

27
@rule
1✔
28
async def int_from_str(s: str) -> int:
1✔
29
    return int(s)
×
30

31

32
async def _top_helper(arg1):
1✔
33
    a = await str_from_int(arg1)
×
34
    return await _helper_helper(a)
×
35

36

37
async def _helper_helper(arg1):
1✔
38
    return await int_from_str(arg1)
×
39

40

41
class HelperContainer:
1✔
42
    async def _method_helper(self, arg1: int):
1✔
43
        return await str_from_int(**implicitly({arg1: int}))
×
44

45
    @staticmethod
1✔
46
    async def _static_helper():
1✔
47
        a = await str_from_int(42)
×
48
        return await _helper_helper(a)
×
49

50

51
container_instance = HelperContainer()
1✔
52

53

54
class InnerScope:
1✔
55
    STR = str
1✔
56
    INT = int
1✔
57
    BOOL = bool
1✔
58

59
    HelperContainer = HelperContainer
1✔
60
    container_instance = container_instance
1✔
61

62

63
OutT = type
1✔
64
InT = type
1✔
65

66

67
def assert_awaitables(func, awaitable_types: Iterable[tuple[OutT, InT | list[InT]]]):
1✔
UNCOV
68
    gets = collect_awaitables(func)
×
UNCOV
69
    actual_types = tuple((get.output_type, list(get.input_types)) for get in gets)
×
UNCOV
70
    expected_types = tuple(
×
71
        (output, ([input_] if isinstance(input_, type) else input_))
72
        for output, input_ in awaitable_types
73
    )
UNCOV
74
    assert actual_types == expected_types
×
75

76

77
def assert_byname_awaitables(func, awaitable_types: Iterable[tuple[OutT, InT | list[InT], int]]):
1✔
78
    gets = collect_awaitables(func)
1✔
79
    actual_types = tuple(
1✔
80
        (get.output_type, list(get.input_types), get.explicit_args_arity) for get in gets
81
    )
82
    expected_types = tuple(
1✔
83
        (output, ([input_] if isinstance(input_, type) else input_), explicit_args_arity)
84
        for output, input_, explicit_args_arity in awaitable_types
85
    )
86
    assert actual_types == expected_types
1✔
87

88

89
def test_byname() -> None:
1✔
90
    @rule
1✔
91
    async def rule0() -> int:
1✔
92
        return 11
×
93

94
    @rule
1✔
95
    async def rule1(arg: int) -> int:
1✔
96
        return arg
×
97

98
    @rule
1✔
99
    async def rule2(arg1: float, arg2: str) -> int:
1✔
100
        return int(arg1) + int(arg2)
×
101

102
    async def rule3() -> int:
1✔
103
        r0 = await rule0()
×
104
        r1_explicit = await rule1(22)
×
105
        r1_implicit = await rule1(**implicitly(int(23)))
×
106
        r2_explicit = await rule2(33.3, "44")
×
107
        r2_implicit = await rule2(**implicitly({33.4: float, "45": str}))
×
108
        r2_mixed = await rule2(33.5, **implicitly({"45": str}))
×
109
        return r0 + r1_explicit + r1_implicit + r2_explicit + r2_implicit + r2_mixed
×
110

111
    assert_byname_awaitables(
1✔
112
        rule3,
113
        [
114
            (int, [], 0),
115
            (int, [], 1),
116
            (int, int, 0),
117
            (int, [], 2),
118
            (int, [float, str], 0),
119
            (int, [str], 1),
120
        ],
121
    )
122

123

124
@contextmanager
1✔
125
def temporary_module(tmp_path: Path, rule_code: str):
1✔
126
    module_name = "_temp_module"
1✔
127
    src_file = tmp_path / f"{module_name}.py"
1✔
128
    with open(src_file, "w") as fp:
1✔
129
        fp.write(rule_code)
1✔
130
    spec = importlib.util.spec_from_file_location(module_name, src_file)
1✔
131
    assert spec
1✔
132
    assert spec.loader
1✔
133
    module = importlib.util.module_from_spec(spec)
1✔
134
    assert module
1✔
135
    sys.modules[module_name] = module
1✔
136
    spec.loader.exec_module(module)
1✔
137
    yield module
1✔
138
    del sys.modules[module_name]
1✔
139

140

141
def test_byname_recursion(tmp_path: Path) -> None:
1✔
142
    # Note that it's important that the rule is defined inside this function, so that
143
    # the @rule decorator is evaluated at test runtime, and not test file parse time.
144
    # However recursion is only supported for rules at module scope, so we have to
145
    # jump through some hoops to create a module at runtime.
146

147
    rule_code = textwrap.dedent("""
1✔
148
        from pants.engine.rules import rule
149

150
        @rule
151
        async def recursive_rule(arg: int) -> int:
152
            if arg == 0:
153
                return 0
154
            recursive = await recursive_rule(arg - 1)
155
            return recursive
156
    """)
157
    with temporary_module(tmp_path, rule_code) as module:
1✔
158
        assert_byname_awaitables(module.recursive_rule, [(int, [], 1)])
1✔
159

160

161
def test_byname_mutual_recursion(tmp_path: Path) -> None:
1✔
162
    # Note that it's important that the rules are defined inside this function, so that
163
    # the @rule decorators are evaluated at test runtime, and not test file parse time.
164
    # However recursion is only supported for rules at module scope, so we have to
165
    # jump through some hoops to create a module at runtime.
166

167
    rule_code = textwrap.dedent("""
1✔
168
        from pants.engine.rules import rule
169

170
        @rule
171
        async def mutually_recursive_rule_1(arg: str) -> int:
172
            if arg == "0":
173
                return 0
174
            recursive = await mutually_recursive_rule_2(int(arg) - 1)
175
            return int(recursive)
176

177
        @rule
178
        async def mutually_recursive_rule_2(arg: int) -> str:
179
            recursive = await mutually_recursive_rule_1(str(arg - 1))
180
            return str(recursive)
181
    """)
182

183
    with temporary_module(tmp_path, rule_code) as module:
1✔
184
        assert_byname_awaitables(module.mutually_recursive_rule_1, [(str, [], 1)])
1✔
185
        assert_byname_awaitables(module.mutually_recursive_rule_2, [(int, [], 1)])
1✔
186

187

188
def test_rule_helpers_free_functions() -> None:
1✔
189
    async def rule():
1✔
190
        _top_helper(1)
×
191

192
    assert_byname_awaitables(rule, [(str, [], 1), (int, [], 1)])
1✔
193

194

195
def test_rule_helpers_class_methods() -> None:
1✔
196
    async def rule1():
1✔
197
        HelperContainer()._static_helper(1)
×
198

199
    async def rule1_inner():
1✔
200
        InnerScope.HelperContainer()._static_helper(1)
×
201

202
    async def rule2():
1✔
203
        HelperContainer._static_helper(1)
×
204

205
    async def rule2_inner():
1✔
206
        InnerScope.HelperContainer._static_helper(1)
×
207

208
    async def rule3():
1✔
209
        container_instance._static_helper(1)
×
210

211
    async def rule3_inner():
1✔
212
        InnerScope.container_instance._static_helper(1)
×
213

214
    async def rule4():
1✔
215
        container_instance._method_helper(1)
×
216

217
    async def rule4_inner():
1✔
218
        InnerScope.container_instance._method_helper(1)
×
219

220
    # Rule helpers must be called via module-scoped attribute lookup
221
    assert_byname_awaitables(rule1, [])
1✔
222
    assert_byname_awaitables(rule1_inner, [])
1✔
223
    assert_byname_awaitables(rule2, [(str, [], 1), (int, [], 1)])
1✔
224
    assert_byname_awaitables(rule2_inner, [(str, [], 1), (int, [], 1)])
1✔
225
    assert_byname_awaitables(rule3, [(str, [], 1), (int, [], 1)])
1✔
226
    assert_byname_awaitables(rule3_inner, [(str, [], 1), (int, [], 1)])
1✔
227
    assert_byname_awaitables(rule4, [(str, [int], 0)])
1✔
228
    assert_byname_awaitables(rule4_inner, [(str, int, 0)])
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