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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

85.98
/src/python/pants/backend/go/util_rules/coverage_profile.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from __future__ import annotations
1✔
4

5
import dataclasses
1✔
6
import math
1✔
7
import re
1✔
8
from collections import defaultdict
1✔
9
from dataclasses import dataclass
1✔
10

11
from pants.backend.go.util_rules.coverage import GoCoverMode
1✔
12
from pants.util.strutil import strip_prefix
1✔
13

14
#
15
# This is a transcription of the Go coverage support library at
16
# https://cs.opensource.google/go/x/tools/+/master:cover/profile.go.
17
#
18
# Original copyright:
19
#   // Copyright 2013 The Go Authors. All rights reserved.
20
#   // Use of this source code is governed by a BSD-style
21
#   // license that can be found in the LICENSE file.
22
#
23

24

25
@dataclass(frozen=True)
1✔
26
class GoCoverageProfileBlock:
1✔
27
    start_line: int
1✔
28
    start_col: int
1✔
29
    end_line: int
1✔
30
    end_col: int
1✔
31
    num_stmt: int
1✔
32
    count: int
1✔
33

34

35
@dataclass(frozen=True)
1✔
36
class GoCoverageBoundary:
1✔
37
    offset: int
1✔
38
    start: bool
1✔
39
    count: int
1✔
40
    norm: float
1✔
41
    index: int
1✔
42

43

44
@dataclass(frozen=True)
1✔
45
class GoCoverageProfile:
1✔
46
    """Parsed representation of a raw Go coverage profile for a single file.
47

48
    A coverage output file may report on multiple files which will be split into different instances
49
    of this dataclass.
50
    """
51

52
    filename: str
1✔
53
    cover_mode: GoCoverMode
1✔
54
    blocks: tuple[GoCoverageProfileBlock, ...]
1✔
55

56
    def boundaries(self, src: bytes) -> tuple[GoCoverageBoundary, ...]:
1✔
57
        max_count = 0
1✔
58
        for block in self.blocks:
1✔
59
            if block.count > max_count:
1✔
60
                max_count = block.count
1✔
61
        # Note: Python throws ValueError if max_count == 0
62
        divisor = math.log(max_count) if max_count > 0 else 0
1✔
63

64
        index = 0
1✔
65

66
        def boundary(offset: int, start: bool, count: int) -> GoCoverageBoundary:
1✔
67
            nonlocal index, max_count
68
            b = GoCoverageBoundary(offset=offset, start=start, count=count, norm=0.0, index=index)
1✔
69
            index = index + 1
1✔
70
            if not start or count == 0:
1✔
71
                return b
1✔
72
            new_norm = None
1✔
73
            if max_count <= 1:
1✔
74
                new_norm = 0.8  # Profile is in "set" mode; we want a heat map. Use cov8 in the CSS.
1✔
75
            elif count > 0:
×
76
                # Divide by zero avoided since divisor > 0 if max_count > 1.
77
                # TODO: What if max_count == 1 so divisor == 0?
78
                new_norm = math.log(count) / divisor
×
79
            if new_norm is not None:
1✔
80
                b = dataclasses.replace(b, norm=new_norm)
1✔
81
            return b
1✔
82

83
        line, col = 1, 2  # TODO: Why is this 2?
1✔
84
        si, bi = 0, 0
1✔
85
        boundaries = []
1✔
86
        while si < len(src) and bi < len(self.blocks):
1✔
87
            b = self.blocks[bi]
1✔
88
            if b.start_line == line and b.start_col == col:
1✔
89
                boundaries.append(boundary(si, True, b.count))
1✔
90
            if b.end_line == line and b.end_col == col or line > b.end_line:
1✔
91
                boundaries.append(boundary(si, False, 0))
1✔
92
                bi += 1
1✔
93
                continue  # Don't advance through src; maybe the next block starts here.
1✔
94
            if src[si] == ord("\n"):
1✔
95
                line += 1
1✔
96
                col = 0
1✔
97
            col += 1
1✔
98
            si += 1
1✔
99

100
        boundaries.sort(key=lambda b: (b.offset, b.index))
1✔
101
        return tuple(boundaries)
1✔
102

103

104
_BLOCK_REGEX = re.compile(r"^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$")
1✔
105

106

107
def parse_go_coverage_profiles(
1✔
108
    contents: bytes, *, description_of_origin: str
109
) -> tuple[GoCoverageProfile, ...]:
110
    lines = iter(contents.decode().splitlines())
1✔
111

112
    # Extract the mode line from the first line.
113
    mode_line = next(lines)
1✔
114
    if not mode_line.startswith("mode: "):
1✔
UNCOV
115
        raise ValueError(
×
116
            f"Malformed Go coverage file from {description_of_origin}: invalid cover mode specifier"
117
        )
118

119
    raw_mode = strip_prefix(mode_line, "mode: ")
1✔
120
    cover_mode = GoCoverMode(raw_mode)
1✔
121

122
    # Parse the coverage blocks from the remainder of the file.
123
    blocks_by_file: dict[str, list[GoCoverageProfileBlock]] = defaultdict(list)
1✔
124
    for line in lines:
1✔
125
        parsed_profile_line = _BLOCK_REGEX.fullmatch(line)
1✔
126
        if not parsed_profile_line:
1✔
UNCOV
127
            raise ValueError(
×
128
                f"Malformed Go coverage file from {description_of_origin}: invalid profile block line"
129
            )
130

131
        filename = parsed_profile_line.group(1)
1✔
132
        start_line = int(parsed_profile_line.group(2))
1✔
133
        start_col = int(parsed_profile_line.group(3))
1✔
134
        end_line = int(parsed_profile_line.group(4))
1✔
135
        end_col = int(parsed_profile_line.group(5))
1✔
136
        num_stmt = int(parsed_profile_line.group(6))
1✔
137
        count = int(parsed_profile_line.group(7))
1✔
138

139
        blocks_by_file[filename].append(
1✔
140
            GoCoverageProfileBlock(
141
                start_line=start_line,
142
                start_col=start_col,
143
                end_line=end_line,
144
                end_col=end_col,
145
                num_stmt=num_stmt,
146
                count=count,
147
            )
148
        )
149

150
    profiles = [
1✔
151
        GoCoverageProfile(
152
            filename=filename,
153
            cover_mode=cover_mode,
154
            blocks=tuple(blocks),
155
        )
156
        for filename, blocks in blocks_by_file.items()
157
    ]
158

159
    # Merge blocks from the same location.
160
    for profile_index, profile in enumerate(profiles):
1✔
161
        blocks = list(profile.blocks)
1✔
162
        blocks.sort(key=lambda b: (b.start_line, b.start_col))
1✔
163
        j = 1
1✔
164
        for i in range(1, len(blocks)):
1✔
UNCOV
165
            b = blocks[i]
×
UNCOV
166
            last = blocks[j - 1]
×
UNCOV
167
            if (
×
168
                b.start_line == last.start_line
169
                and b.start_col == last.start_col
170
                and b.end_line == last.end_line
171
                and b.end_col == last.end_col
172
            ):
UNCOV
173
                if b.num_stmt != last.num_stmt:
×
174
                    raise ValueError(
×
175
                        f"inconsistent NumStmt: changed from {last.num_stmt} to {b.num_stmt}"
176
                    )
UNCOV
177
                if cover_mode == GoCoverMode.SET:
×
178
                    blocks[j - 1] = dataclasses.replace(
×
179
                        blocks[j - 1], count=blocks[j - 1].count | b.count
180
                    )
181
                else:
UNCOV
182
                    blocks[j - 1] = dataclasses.replace(
×
183
                        blocks[j - 1], count=blocks[j - 1].count + b.count
184
                    )
UNCOV
185
                continue
×
UNCOV
186
            blocks[j] = b
×
UNCOV
187
            j = j + 1
×
188

189
        profiles[profile_index] = dataclasses.replace(profile, blocks=tuple(blocks[:j]))
1✔
190

191
    profiles.sort(key=lambda p: p.filename)
1✔
192
    return tuple(profiles)
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