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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

0.0
/src/python/pants/core/goals/check.py
1
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import logging
×
UNCOV
7
from collections import defaultdict
×
UNCOV
8
from collections.abc import Iterable
×
UNCOV
9
from dataclasses import dataclass
×
UNCOV
10
from typing import Any, ClassVar, Generic, TypeVar, cast
×
11

UNCOV
12
from pants.core.environments.rules import EnvironmentNameRequest, resolve_environment_name
×
UNCOV
13
from pants.core.goals.lint import REPORT_DIR as REPORT_DIR  # noqa: F401
×
UNCOV
14
from pants.core.goals.multi_tool_goal_helper import (
×
15
    OnlyOption,
16
    determine_specified_tool_ids,
17
    write_reports,
18
)
UNCOV
19
from pants.core.util_rules.distdir import DistDir
×
UNCOV
20
from pants.engine.collection import Collection
×
UNCOV
21
from pants.engine.console import Console
×
UNCOV
22
from pants.engine.engine_aware import EngineAwareParameter, EngineAwareReturnType
×
UNCOV
23
from pants.engine.environment import EnvironmentName
×
UNCOV
24
from pants.engine.fs import EMPTY_DIGEST, Digest, Workspace
×
UNCOV
25
from pants.engine.goal import Goal, GoalSubsystem
×
UNCOV
26
from pants.engine.internals.selectors import concurrently
×
UNCOV
27
from pants.engine.process import FallibleProcessResult
×
UNCOV
28
from pants.engine.rules import QueryRule, collect_rules, goal_rule, implicitly, rule
×
UNCOV
29
from pants.engine.target import FieldSet, FilteredTargets
×
UNCOV
30
from pants.engine.unions import UnionMembership, union
×
UNCOV
31
from pants.util.logging import LogLevel
×
UNCOV
32
from pants.util.memo import memoized_property
×
UNCOV
33
from pants.util.meta import classproperty
×
UNCOV
34
from pants.util.strutil import Simplifier
×
35

UNCOV
36
logger = logging.getLogger(__name__)
×
37

UNCOV
38
_FS = TypeVar("_FS", bound=FieldSet)
×
39

40

UNCOV
41
@dataclass(frozen=True)
×
UNCOV
42
class CheckResult:
×
UNCOV
43
    exit_code: int
×
UNCOV
44
    stdout: str
×
UNCOV
45
    stderr: str
×
UNCOV
46
    partition_description: str | None = None
×
UNCOV
47
    report: Digest = EMPTY_DIGEST
×
48

UNCOV
49
    @staticmethod
×
UNCOV
50
    def from_fallible_process_result(
×
51
        process_result: FallibleProcessResult,
52
        *,
53
        partition_description: str | None = None,
54
        output_simplifier: Simplifier = Simplifier(),
55
        report: Digest = EMPTY_DIGEST,
56
    ) -> CheckResult:
UNCOV
57
        return CheckResult(
×
58
            exit_code=process_result.exit_code,
59
            stdout=output_simplifier.simplify(process_result.stdout),
60
            stderr=output_simplifier.simplify(process_result.stderr),
61
            partition_description=partition_description,
62
            report=report,
63
        )
64

UNCOV
65
    def metadata(self) -> dict[str, Any]:
×
66
        return {"partition": self.partition_description}
×
67

68

UNCOV
69
@dataclass(frozen=True)
×
UNCOV
70
class CheckResults(EngineAwareReturnType):
×
71
    """Zero or more CheckResult objects for a single type checker.
72

73
    Typically, type checkers will return one result. If they no-oped, they will return zero results.
74
    However, some type checkers may need to partition their input and thus may need to return
75
    multiple results.
76
    """
77

UNCOV
78
    results: tuple[CheckResult, ...]
×
UNCOV
79
    checker_name: str
×
80

UNCOV
81
    def __init__(self, results: Iterable[CheckResult], *, checker_name: str) -> None:
×
UNCOV
82
        object.__setattr__(self, "results", tuple(results))
×
UNCOV
83
        object.__setattr__(self, "checker_name", checker_name)
×
84

UNCOV
85
    @property
×
UNCOV
86
    def skipped(self) -> bool:
×
UNCOV
87
        return bool(self.results) is False
×
88

UNCOV
89
    @memoized_property
×
UNCOV
90
    def exit_code(self) -> int:
×
UNCOV
91
        return next((result.exit_code for result in self.results if result.exit_code != 0), 0)
×
92

UNCOV
93
    def level(self) -> LogLevel | None:
×
UNCOV
94
        if self.skipped:
×
UNCOV
95
            return LogLevel.DEBUG
×
UNCOV
96
        return LogLevel.ERROR if self.exit_code != 0 else LogLevel.INFO
×
97

UNCOV
98
    def message(self) -> str | None:
×
UNCOV
99
        if self.skipped:
×
UNCOV
100
            return f"{self.checker_name} skipped."
×
UNCOV
101
        message = self.checker_name
×
UNCOV
102
        message += (
×
103
            " succeeded." if self.exit_code == 0 else f" failed (exit code {self.exit_code})."
104
        )
105

UNCOV
106
        def msg_for_result(result: CheckResult) -> str:
×
UNCOV
107
            msg = ""
×
UNCOV
108
            if result.stdout:
×
UNCOV
109
                msg += f"\n{result.stdout}"
×
UNCOV
110
            if result.stderr:
×
UNCOV
111
                msg += f"\n{result.stderr}"
×
UNCOV
112
            if msg:
×
UNCOV
113
                msg = f"{msg.rstrip()}\n\n"
×
UNCOV
114
            return msg
×
115

UNCOV
116
        if len(self.results) == 1:
×
UNCOV
117
            results_msg = msg_for_result(self.results[0])
×
118
        else:
UNCOV
119
            results_msg = "\n"
×
UNCOV
120
            for i, result in enumerate(self.results):
×
UNCOV
121
                msg = f"Partition #{i + 1}"
×
UNCOV
122
                msg += (
×
123
                    f" - {result.partition_description}:" if result.partition_description else ":"
124
                )
UNCOV
125
                msg += msg_for_result(result) or "\n\n"
×
UNCOV
126
                results_msg += msg
×
UNCOV
127
        message += results_msg
×
UNCOV
128
        return message
×
129

UNCOV
130
    def cacheable(self) -> bool:
×
131
        """Is marked uncacheable to ensure that it always renders."""
132
        return False
×
133

134

UNCOV
135
@dataclass(frozen=True)
×
UNCOV
136
@union(in_scope_types=[EnvironmentName])
×
UNCOV
137
class CheckRequest(Generic[_FS], EngineAwareParameter):
×
138
    """A union for targets that should be checked.
139

140
    Subclass and install a member of this type to provide a checker.
141
    """
142

UNCOV
143
    field_set_type: ClassVar[type[_FS]]
×
UNCOV
144
    tool_name: ClassVar[str]
×
145

UNCOV
146
    @classproperty
×
UNCOV
147
    def tool_id(cls) -> str:
×
148
        """The "id" of the tool, used in tool selection (Eg --only=<id>)."""
149
        return cls.tool_name
×
150

UNCOV
151
    field_sets: Collection[_FS]
×
152

UNCOV
153
    def __init__(self, field_sets: Iterable[_FS]) -> None:
×
UNCOV
154
        object.__setattr__(self, "field_sets", Collection[_FS](field_sets))
×
155

UNCOV
156
    def debug_hint(self) -> str:
×
157
        return self.tool_name
×
158

UNCOV
159
    def metadata(self) -> dict[str, Any]:
×
160
        return {"addresses": [fs.address.spec for fs in self.field_sets]}
×
161

162

UNCOV
163
@rule(polymorphic=True)
×
UNCOV
164
async def check(
×
165
    req: CheckRequest,
166
    environment_name: EnvironmentName,
167
) -> CheckResults:
168
    raise NotImplementedError()
×
169

170

UNCOV
171
class CheckSubsystem(GoalSubsystem):
×
UNCOV
172
    name = "check"
×
UNCOV
173
    help = "Run type checking or the lightest variant of compilation available for a language."
×
174

UNCOV
175
    @classmethod
×
UNCOV
176
    def activated(cls, union_membership: UnionMembership) -> bool:
×
177
        return CheckRequest in union_membership
×
178

UNCOV
179
    only = OnlyOption("checker", "mypy", "javac")
×
180

181

UNCOV
182
class Check(Goal):
×
UNCOV
183
    subsystem_cls = CheckSubsystem
×
UNCOV
184
    environment_behavior = Goal.EnvironmentBehavior.USES_ENVIRONMENTS
×
185

186

UNCOV
187
@goal_rule
×
UNCOV
188
async def check_goal(
×
189
    console: Console,
190
    workspace: Workspace,
191
    targets: FilteredTargets,
192
    dist_dir: DistDir,
193
    union_membership: UnionMembership,
194
    check_subsystem: CheckSubsystem,
195
) -> Check:
UNCOV
196
    request_types = cast("Iterable[type[CheckRequest]]", union_membership[CheckRequest])
×
UNCOV
197
    specified_ids = determine_specified_tool_ids("check", check_subsystem.only, request_types)
×
198

UNCOV
199
    requests = tuple(
×
200
        request_type(
201
            request_type.field_set_type.create(target)
202
            for target in targets
203
            if (
204
                request_type.tool_id in specified_ids
205
                and request_type.field_set_type.is_applicable(target)
206
            )
207
        )
208
        for request_type in request_types
209
    )
210

UNCOV
211
    request_to_field_set = [
×
212
        (request, field_set) for request in requests for field_set in request.field_sets
213
    ]
214

UNCOV
215
    environment_names = await concurrently(
×
216
        resolve_environment_name(EnvironmentNameRequest.from_field_set(field_set), **implicitly())
217
        for (_, field_set) in request_to_field_set
218
    )
219

UNCOV
220
    request_to_env_name = {
×
221
        (request, env_name)
222
        for (request, _), env_name in zip(request_to_field_set, environment_names)
223
    }
224

225
    # Run each check request in each valid environment (potentially multiple runs per tool)
UNCOV
226
    all_results = await concurrently(
×
227
        check(**implicitly({request: CheckRequest, env_name: EnvironmentName}))
228
        for (request, env_name) in request_to_env_name
229
    )
230

UNCOV
231
    results_by_tool: dict[str, list[CheckResult]] = defaultdict(list)
×
UNCOV
232
    for results in all_results:
×
UNCOV
233
        results_by_tool[results.checker_name].extend(results.results)
×
234

UNCOV
235
    write_reports(
×
236
        results_by_tool,
237
        workspace,
238
        dist_dir,
239
        goal_name=CheckSubsystem.name,
240
    )
241

UNCOV
242
    exit_code = 0
×
UNCOV
243
    if all_results:
×
UNCOV
244
        console.print_stderr("")
×
UNCOV
245
    for results in sorted(all_results, key=lambda results: results.checker_name):
×
UNCOV
246
        if results.skipped:
×
UNCOV
247
            continue
×
UNCOV
248
        elif results.exit_code == 0:
×
UNCOV
249
            sigil = console.sigil_succeeded()
×
UNCOV
250
            status = "succeeded"
×
251
        else:
UNCOV
252
            sigil = console.sigil_failed()
×
UNCOV
253
            status = "failed"
×
UNCOV
254
            exit_code = results.exit_code
×
UNCOV
255
        console.print_stderr(f"{sigil} {results.checker_name} {status}.")
×
256

UNCOV
257
    return Check(exit_code)
×
258

259

UNCOV
260
def rules():
×
UNCOV
261
    return [
×
262
        *collect_rules(),
263
        # NB: Would be unused otherwise.
264
        QueryRule(CheckSubsystem, []),
265
    ]
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