• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

pantsbuild / pants / 18517631058

15 Oct 2025 04:18AM UTC coverage: 69.207% (-11.1%) from 80.267%
18517631058

Pull #22745

github

web-flow
Merge 642a76ca1 into 99919310e
Pull Request #22745: [windows] Add windows support in the stdio crate.

53815 of 77759 relevant lines covered (69.21%)

2.42 hits per line

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

28.97
/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
×
58
        for block in self.blocks:
×
59
            if block.count > max_count:
×
60
                max_count = block.count
×
61
        # Note: Python throws ValueError if max_count == 0
62
        divisor = math.log(max_count) if max_count > 0 else 0
×
63

64
        index = 0
×
65

66
        def boundary(offset: int, start: bool, count: int) -> GoCoverageBoundary:
×
67
            nonlocal index, max_count
68
            b = GoCoverageBoundary(offset=offset, start=start, count=count, norm=0.0, index=index)
×
69
            index = index + 1
×
70
            if not start or count == 0:
×
71
                return b
×
72
            new_norm = None
×
73
            if max_count <= 1:
×
74
                new_norm = 0.8  # Profile is in "set" mode; we want a heat map. Use cov8 in the CSS.
×
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:
×
80
                b = dataclasses.replace(b, norm=new_norm)
×
81
            return b
×
82

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

100
        boundaries.sort(key=lambda b: (b.offset, b.index))
×
101
        return tuple(boundaries)
×
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())
×
111

112
    # Extract the mode line from the first line.
113
    mode_line = next(lines)
×
114
    if not mode_line.startswith("mode: "):
×
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: ")
×
120
    cover_mode = GoCoverMode(raw_mode)
×
121

122
    # Parse the coverage blocks from the remainder of the file.
123
    blocks_by_file: dict[str, list[GoCoverageProfileBlock]] = defaultdict(list)
×
124
    for line in lines:
×
125
        parsed_profile_line = _BLOCK_REGEX.fullmatch(line)
×
126
        if not parsed_profile_line:
×
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)
×
132
        start_line = int(parsed_profile_line.group(2))
×
133
        start_col = int(parsed_profile_line.group(3))
×
134
        end_line = int(parsed_profile_line.group(4))
×
135
        end_col = int(parsed_profile_line.group(5))
×
136
        num_stmt = int(parsed_profile_line.group(6))
×
137
        count = int(parsed_profile_line.group(7))
×
138

139
        blocks_by_file[filename].append(
×
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 = [
×
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):
×
161
        blocks = list(profile.blocks)
×
162
        blocks.sort(key=lambda b: (b.start_line, b.start_col))
×
163
        j = 1
×
164
        for i in range(1, len(blocks)):
×
165
            b = blocks[i]
×
166
            last = blocks[j - 1]
×
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
            ):
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
                    )
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:
182
                    blocks[j - 1] = dataclasses.replace(
×
183
                        blocks[j - 1], count=blocks[j - 1].count + b.count
184
                    )
185
                continue
×
186
            blocks[j] = b
×
187
            j = j + 1
×
188

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

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