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

mozilla / relman-auto-nag / #5290

25 Oct 2024 02:03PM CUT coverage: 21.688% (+0.06%) from 21.625%
#5290

push

coveralls-python

web-flow
Bump coverage from 7.6.3 to 7.6.4 (#2511)

Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.3 to 7.6.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.3...7.6.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

426 of 2872 branches covered (14.83%)

1943 of 8959 relevant lines covered (21.69%)

0.22 hits per line

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

0.0
/bugbot/crash/socorro_util.py
1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
# You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
"""The code in this module was borrowed from Socorro (some parts were adjusted).
6
Each function, class, or dictionary is documented with a link to the original
7
source.
8
"""
9

10

11
import re
×
12
from functools import cached_property
×
13
from itertools import islice
×
14

15

16
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/templatetags/jinja_helpers.py#L182-L203
17
def generate_bug_description_data(report) -> dict:
×
18
    crashing_thread = get_crashing_thread(report)
×
19
    parsed_dump = get_parsed_dump(report) or {}
×
20

21
    frames = None
×
22
    threads = parsed_dump.get("threads")
×
23
    if threads:
×
24
        thread_index = crashing_thread or 0
×
25
        frames = bugzilla_thread_frames(parsed_dump["threads"][thread_index])
×
26

27
    return {
×
28
        "uuid": report["uuid"],
29
        # NOTE(willkg): this is the redacted stack trace--not the raw one that can
30
        # have PII in it
31
        "java_stack_trace": report.get("java_stack_trace", None),
32
        # NOTE(willkg): this is the redacted mozcrashreason--not the raw one that
33
        # can have PII in it
34
        "moz_crash_reason": report.get("moz_crash_reason", None),
35
        "reason": report.get("reason", None),
36
        "frames": frames,
37
        "crashing_thread": crashing_thread,
38
    }
39

40

41
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/templatetags/jinja_helpers.py#L227-L278
42
def bugzilla_thread_frames(thread):
×
43
    """Build frame information for bug creation link
44

45
    Extract frame info for the top frames of a crashing thread to be included in the
46
    Bugzilla summary when reporting the crash.
47

48
    :arg thread: dict of thread information including "frames" list
49

50
    :returns: list of frame information dicts
51

52
    """
53

54
    def frame_generator(thread):
×
55
        """Yield frames in a thread factoring in inlines"""
56
        for frame in thread["frames"]:
×
57
            for inline in frame.get("inlines") or []:
×
58
                yield {
×
59
                    "frame": frame.get("frame", "?"),
60
                    "module": frame.get("module", ""),
61
                    "signature": inline["function"],
62
                    "file": inline["file"],
63
                    "line": inline["line"],
64
                }
65

66
            yield frame
×
67

68
    # We only want to include 10 frames in the link
69
    MAX_FRAMES = 10
×
70

71
    frames = []
×
72
    for frame in islice(frame_generator(thread), MAX_FRAMES):
×
73
        # Source is an empty string if data isn't available
74
        source = frame.get("file") or ""
×
75
        if frame.get("line"):
×
76
            source += ":{}".format(frame["line"])
×
77

78
        signature = frame.get("signature") or ""
×
79

80
        # Remove function arguments
81
        if not signature.startswith("(unloaded"):
×
82
            signature = re.sub(r"\(.*\)", "", signature)
×
83

84
        frames.append(
×
85
            {
86
                "frame": frame.get("frame", "?"),
87
                "module": frame.get("module") or "?",
88
                "signature": signature,
89
                "source": source,
90
            }
91
        )
92

93
    return frames
×
94

95

96
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/utils.py#L343-L359
97
def enhance_json_dump(dump, vcs_mappings):
×
98
    """
99
    Add some information to the stackwalker's json_dump output
100
    for display. Mostly applying vcs_mappings to stack frames.
101
    """
102
    for thread_index, thread in enumerate(dump.get("threads", [])):
×
103
        if "thread" not in thread:
×
104
            thread["thread"] = thread_index
×
105

106
        frames = thread["frames"]
×
107
        for frame in frames:
×
108
            enhance_frame(frame, vcs_mappings)
×
109
            for inline in frame.get("inlines") or []:
×
110
                enhance_frame(inline, vcs_mappings)
×
111

112
        thread["frames"] = frames
×
113
    return dump
×
114

115

116
# https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/utils.py#L259-L340
117
def enhance_frame(frame, vcs_mappings):
×
118
    """Add additional info to a stack frame
119

120
    This adds signature and source links from vcs_mappings.
121

122
    """
123
    # If this is a truncation frame, then we don't need to enhance it in any way
124
    if frame.get("truncated") is not None:
×
125
        return
×
126

127
    if frame.get("function"):
×
128
        # Remove spaces before all stars, ampersands, and commas
129
        function = re.sub(r" (?=[\*&,])", "", frame["function"])
×
130
        # Ensure a space after commas
131
        function = re.sub(r",(?! )", ", ", function)
×
132
        frame["function"] = function
×
133
        signature = function
×
134
    elif frame.get("file") and frame.get("line"):
×
135
        signature = "%s#%d" % (frame["file"], frame["line"])
×
136
    elif frame.get("module") and frame.get("module_offset"):
×
137
        signature = "%s@%s" % (
×
138
            frame["module"],
139
            strip_leading_zeros(frame["module_offset"]),
140
        )
141
    elif frame.get("unloaded_modules"):
×
142
        first_module = frame["unloaded_modules"][0]
×
143
        if first_module.get("offsets"):
×
144
            signature = "(unloaded %s@%s)" % (
×
145
                first_module.get("module") or "",
146
                strip_leading_zeros(first_module.get("offsets")[0]),
147
            )
148
        else:
149
            signature = "(unloaded %s)" % first_module
×
150
    else:
151
        signature = "@%s" % frame["offset"]
×
152

153
    frame["signature"] = signature
×
154
    if signature.startswith("(unloaded"):
×
155
        # If the signature is based on an unloaded module, leave the string as is
156
        frame["short_signature"] = signature
×
157
    else:
158
        # Remove arguments which are enclosed in parens
159
        frame["short_signature"] = re.sub(r"\(.*\)", "", signature)
×
160

161
    if frame.get("file"):
×
162
        vcsinfo = frame["file"].split(":")
×
163
        if len(vcsinfo) == 4:
×
164
            vcstype, root, vcs_source_file, revision = vcsinfo
×
165
            if "/" in root:
×
166
                # The root is something like 'hg.mozilla.org/mozilla-central'
167
                server, repo = root.split("/", 1)
×
168
            else:
169
                # E.g. 'gecko-generated-sources' or something without a '/'
170
                repo = server = root
×
171

172
            if (
×
173
                vcs_source_file.count("/") > 1
174
                and len(vcs_source_file.split("/")[0]) == 128
175
            ):
176
                # In this case, the 'vcs_source_file' will be something like
177
                # '{SHA-512 hex}/ipc/ipdl/PCompositorBridgeChild.cpp'
178
                # So drop the sha part for the sake of the 'file' because
179
                # we don't want to display a 128 character hex code in the
180
                # hyperlink text.
181
                vcs_source_file_display = "/".join(vcs_source_file.split("/")[1:])
×
182
            else:
183
                # Leave it as is if it's not unwieldy long.
184
                vcs_source_file_display = vcs_source_file
×
185

186
            if vcstype in vcs_mappings:
×
187
                if server in vcs_mappings[vcstype]:
×
188
                    link = vcs_mappings[vcstype][server]
×
189
                    frame["file"] = vcs_source_file_display
×
190
                    frame["source_link"] = link % {
×
191
                        "repo": repo,
192
                        "file": vcs_source_file,
193
                        "revision": revision,
194
                        "line": frame["line"],
195
                    }
196
            else:
197
                path_parts = vcs_source_file.split("/")
×
198
                frame["file"] = path_parts.pop()
×
199

200

201
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/socorro/signature/utils.py#L405-L422
202
def strip_leading_zeros(text):
×
203
    """Strips leading zeros from a hex string.
204

205
    Example:
206

207
    >>> strip_leading_zeros("0x0000000000032ec0")
208
    "0x32ec0"
209

210
    :param text: the text to strip leading zeros from
211

212
    :returns: stripped text
213

214
    """
215
    try:
×
216
        return hex(int(text, base=16))
×
217
    except (ValueError, TypeError):
×
218
        return text
×
219

220

221
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/settings/base.py#L268-L293
222
# Link to source if possible
223
VCS_MAPPINGS = {
×
224
    "cvs": {
225
        "cvs.mozilla.org": (
226
            "http://bonsai.mozilla.org/cvsblame.cgi?file=%(file)s&rev=%(revision)s&mark=%(line)s#%(line)s"
227
        )
228
    },
229
    "hg": {
230
        "hg.mozilla.org": (
231
            "https://hg.mozilla.org/%(repo)s/file/%(revision)s/%(file)s#l%(line)s"
232
        )
233
    },
234
    "git": {
235
        "git.mozilla.org": (
236
            "http://git.mozilla.org/?p=%(repo)s;a=blob;f=%(file)s;h=%(revision)s#l%(line)s"
237
        ),
238
        "github.com": (
239
            "https://github.com/%(repo)s/blob/%(revision)s/%(file)s#L%(line)s"
240
        ),
241
    },
242
    "s3": {
243
        "gecko-generated-sources": (
244
            "/sources/highlight/?url=https://gecko-generated-sources.s3.amazonaws.com/%(file)s&line=%(line)s#L-%(line)s"
245
        )
246
    },
247
}
248

249

250
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/views.py#L141-L153
251
def get_parsed_dump(report):
×
252
    # For C++/Rust crashes
253
    if "json_dump" in report:
×
254
        json_dump = report["json_dump"]
×
255

256
        # This is for displaying on the "Details" tab
257
        enhance_json_dump(json_dump, VCS_MAPPINGS)
×
258
        parsed_dump = json_dump
×
259
    else:
260
        parsed_dump = {}
×
261

262
    return parsed_dump
×
263

264

265
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/views.py#L155-L160
266
def get_crashing_thread(report):
×
267
    if report["signature"].startswith("shutdownhang"):
×
268
        # For shutdownhang signatures, we want to use thread 0 as the crashing thread,
269
        # because that's the thread that actually contains the useful data about what
270
        # happened.
271
        return 0
×
272

273
    return report.get("crashing_thread")
×
274

275

276
# Original Socorro code: https://github.com/mozilla-services/socorro/blob/ff8f5d6b41689e34a6b800577d8ffe383e1e62eb/webapp/crashstats/crashstats/utils.py#L73-L195
277
class SignatureStats:
×
278
    def __init__(
×
279
        self,
280
        signature,
281
        num_total_crashes,
282
        rank=0,
283
        platforms=None,
284
        previous_signature=None,
285
    ):
286
        self.signature = signature
×
287
        self.num_total_crashes = num_total_crashes
×
288
        self.rank = rank
×
289
        self.platforms = platforms
×
290
        self.previous_signature = previous_signature
×
291

292
    @cached_property
×
293
    def platform_codes(self):
×
294
        return [x["short_name"] for x in self.platforms if x["short_name"] != "unknown"]
×
295

296
    @cached_property
×
297
    def signature_term(self):
×
298
        return self.signature["term"]
×
299

300
    @cached_property
×
301
    def percent_of_total_crashes(self):
×
302
        return 100.0 * self.signature["count"] / self.num_total_crashes
×
303

304
    @cached_property
×
305
    def num_crashes(self):
×
306
        return self.signature["count"]
×
307

308
    @cached_property
×
309
    def num_crashes_per_platform(self):
×
310
        num_crashes_per_platform = {
×
311
            platform + "_count": 0 for platform in self.platform_codes
312
        }
313
        for platform in self.signature["facets"]["platform"]:
×
314
            code = platform["term"][:3].lower()
×
315
            if code in self.platform_codes:
×
316
                num_crashes_per_platform[code + "_count"] = platform["count"]
×
317
        return num_crashes_per_platform
×
318

319
    @cached_property
×
320
    def num_crashes_in_garbage_collection(self):
×
321
        num_crashes_in_garbage_collection = 0
×
322
        for row in self.signature["facets"]["is_garbage_collecting"]:
×
323
            if row["term"].lower() == "t":
×
324
                num_crashes_in_garbage_collection = row["count"]
×
325
        return num_crashes_in_garbage_collection
×
326

327
    @cached_property
×
328
    def num_installs(self):
×
329
        return self.signature["facets"]["cardinality_install_time"]["value"]
×
330

331
    @cached_property
×
332
    def percent_of_total_crashes_diff(self):
×
333
        if self.previous_signature:
×
334
            # The number should go "up" when moving towards 100 and "down" when moving
335
            # towards 0
336
            return (
×
337
                self.percent_of_total_crashes
338
                - self.previous_signature.percent_of_total_crashes
339
            )
340
        return "new"
×
341

342
    @cached_property
×
343
    def rank_diff(self):
×
344
        if self.previous_signature:
×
345
            # The number should go "up" when moving towards 1 and "down" when moving
346
            # towards infinity
347
            return self.previous_signature.rank - self.rank
×
348
        return 0
×
349

350
    @cached_property
×
351
    def previous_percent_of_total_crashes(self):
×
352
        if self.previous_signature:
×
353
            return self.previous_signature.percent_of_total_crashes
×
354
        return 0
×
355

356
    @cached_property
×
357
    def num_startup_crashes(self):
×
358
        return sum(
×
359
            row["count"]
360
            for row in self.signature["facets"]["startup_crash"]
361
            if row["term"] in ("T", "1")
362
        )
363

364
    @cached_property
×
365
    def is_startup_crash(self):
×
366
        return self.num_startup_crashes == self.num_crashes
×
367

368
    @cached_property
×
369
    def is_potential_startup_crash(self):
×
370
        return (
×
371
            self.num_startup_crashes > 0 and self.num_startup_crashes < self.num_crashes
372
        )
373

374
    @cached_property
×
375
    def is_startup_window_crash(self):
×
376
        is_startup_window_crash = False
×
377
        for row in self.signature["facets"]["histogram_uptime"]:
×
378
            # Aggregation buckets use the lowest value of the bucket as
379
            # term. So for everything between 0 and 60 excluded, the
380
            # term will be `0`.
381
            if row["term"] < 60:
×
382
                ratio = 1.0 * row["count"] / self.num_crashes
×
383
                is_startup_window_crash = ratio > 0.5
×
384
        return is_startup_window_crash
×
385

386
    @cached_property
×
387
    def is_plugin_crash(self):
×
388
        for row in self.signature["facets"]["process_type"]:
×
389
            if row["term"].lower() == "plugin":
×
390
                return row["count"] > 0
×
391
        return False
×
392

393
    @cached_property
×
394
    def is_startup_related_crash(self):
×
395
        return (
×
396
            self.is_startup_crash
397
            or self.is_potential_startup_crash
398
            or self.is_startup_window_crash
399
        )
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