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

mozilla / relman-auto-nag / #4807

01 Nov 2023 12:33PM CUT coverage: 22.006%. Remained the same
#4807

push

coveralls-python

suhaibmujahid
[file_crash_bug] Clean up the code

716 of 3584 branches covered (0.0%)

3 of 3 new or added lines in 1 file covered. (100.0%)

1924 of 8743 relevant lines covered (22.01%)

0.22 hits per line

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

0.0
/bugbot/rules/file_crash_bug.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
import pprint
×
6
from functools import cached_property
×
7

8
import humanize
×
9
import jinja2
×
10
import requests
×
11
from libmozdata.bugzilla import Bugzilla
×
12

13
from bugbot import logger, utils
×
14
from bugbot.bug.analyzer import BugAnalyzer
×
15
from bugbot.bzcleaner import BzCleaner
×
16
from bugbot.crash import socorro_util
×
17
from bugbot.crash.analyzer import SignatureAnalyzer, SignaturesDataFetcher
×
18
from bugbot.user_activity import UserActivity, UserStatus
×
19

20

21
class FileCrashBug(BzCleaner):
×
22
    """File bugs for new actionable crashes."""
23

24
    MAX_BUG_TITLE_LENGTH = 255
×
25

26
    def __init__(self):
×
27
        super().__init__()
×
28

29
        self.bug_description_template = jinja2.Environment(
×
30
            loader=jinja2.FileSystemLoader("templates")
31
        ).get_template("file_crash_bug_description.md.jinja")
32

33
    def description(self):
×
34
        return "New actionable crashes"
×
35

36
    def columns(self):
×
37
        return ["component", "id", "summary"]
×
38

39
    def get_bz_params(self, date):
×
40
        return {
×
41
            "resolution": ["---", "FIXED"],
42
            "keywords": ["feature", "regression"],
43
            "keywords_type": "allwords",
44
        }
45

46
    @cached_property
×
47
    def current_status_flags(self) -> list[str]:
×
48
        """The status flags for Firefox, which includes Nightly, Beta, Release, and ESR."""
49
        status = "cf_status_firefox"
×
50
        status_esr = "cf_status_firefox_esr"
×
51
        data: dict[str, list[str]] = {
×
52
            status: [],
53
            status_esr: [],
54
        }
55

56
        def handler(bug):
×
57
            for field in bug.keys():
×
58
                if field.startswith(status_esr):
×
59
                    data[status_esr].append(field)
×
60
                elif field.startswith(status):
×
61
                    data[status].append(field)
×
62

63
        Bugzilla(bugids=1234567, include_fields="_custom", bughandler=handler).wait()
×
64

65
        return (
×
66
            # ESR
67
            sorted(
68
                data[status_esr],
69
                key=lambda flag: int(flag[len(status_esr) :]),
70
            )[-1:]
71
            # Release, Beta, Nightly
72
            + sorted(
73
                data[status],
74
                key=lambda flag: int(flag[len(status) :]),
75
            )[-3:]
76
        )
77

78
    @cached_property
×
79
    def nightly_status_flag(self) -> str:
×
80
        """The nightly release status flag for Firefox."""
81
        return self.current_status_flags[-1]
×
82

83
    def _active_regression_authors(
×
84
        self, signatures: list[SignatureAnalyzer]
85
    ) -> set[str]:
86
        """Get Bugzilla usernames for users who are active and can be needinfo'd.
87

88
        Args:
89
            signatures: a list of signatures for which to check the status of
90
                their regression author.
91

92
        Returns:
93
            A set of user emails.
94
        """
95
        ni_skiplist = self.get_auto_ni_skiplist()
×
96
        users = UserActivity(include_fields=["requests"]).check_users(
×
97
            (
98
                signature.regressed_by_author["name"]
99
                for signature in signatures
100
                if signature.regressed_by_author
101
            ),
102
            keep_active=True,
103
            fetch_employee_info=True,
104
        )
105

106
        return {
×
107
            name
108
            for name, user in users.items()
109
            if name not in ni_skiplist
110
            and user["status"] == UserStatus.ACTIVE
111
            and not user["requests"]["needinfo"]["blocked"]
112
        }
113

114
    def get_bugs(self, date):
×
115
        self.query_url = None
×
116
        bugs = {}
×
117

118
        data_fetcher = SignaturesDataFetcher.find_new_actionable_crashes(
×
119
            "Firefox", "nightly"
120
        )
121
        signatures = data_fetcher.analyze()
×
122
        # This is the last filtering stage which aims to avoid filing bugs for
123
        # junky crashes, where the volume is low and the crashes do not show
124
        # signals of being actionable or critical.
125
        signatures = [
×
126
            signature
127
            for signature in signatures
128
            if signature.num_installs > 5
129
            or signature.num_crashes > 25
130
            or signature.is_potential_near_null_crash
131
            or signature.is_potential_security_crash
132
            or signature.has_moz_crash_reason("MOZ_RELEASE_ASSERT")
133
            or signature.has_moz_crash_reason("DocumentChannel::SetLoadFlags")
134
        ]
135

136
        signature_details_delta = humanize.naturaldelta(data_fetcher.SUMMARY_DURATION)
×
137

138
        active_regression_authors = self._active_regression_authors(signatures)
×
139

140
        for signature in signatures:
×
141
            logger.debug("Generating bug for signature: %s", signature.signature_term)
×
142

143
            title = (
×
144
                f"Startup crash in [@ {signature.signature_term}]"
145
                if signature.is_startup_related_crash
146
                else f"Crash in [@ {signature.signature_term}]"
147
            )
148
            if len(title) > self.MAX_BUG_TITLE_LENGTH:
×
149
                title = title[: self.MAX_BUG_TITLE_LENGTH - 3] + "..."
×
150

151
            # Whether we should needinfo the regression author.
152
            needinfo_regression_author = (
×
153
                signature.regressed_by
154
                and signature.regressed_by_author["email"] in active_regression_authors
155
            )
156

157
            report = signature.fetch_representative_processed_crash()
×
158
            description = self.bug_description_template.render(
×
159
                {
160
                    **socorro_util.generate_bug_description_data(report),
161
                    "signature": signature,
162
                    "needinfo_regression_author": needinfo_regression_author,
163
                    "signature_details_delta": signature_details_delta,
164
                    "signature_details_channel": "Nightly",
165
                }
166
            )
167

168
            # TODO: Provide the following information:
169
            # [X] Crash signature
170
            # [X] Top 10 frames of crashing thread
171
            # [X] Component
172
            # [X] The kind of crash
173
            # [ ] Regression window
174
            # [X] Inducing patch
175
            # [X] Reason
176
            # [X] Regressed by
177
            # [X] Platform
178
            # [x] Firefox status flags
179
            # [ ] Severity
180
            # [ ] Time correlation
181
            # [X] User comments
182
            # [X] Crash address commonalities
183
            # [ ] Estimated future crash volume
184

185
            bug_data = {
×
186
                "blocks": "bugbot-auto-crash",
187
                "type": "defect",
188
                "keywords": ["crash"],
189
                "summary": title,
190
                "product": signature.crash_component.product,
191
                "component": signature.crash_component.name,
192
                "op_sys": signature.bugzilla_op_sys,
193
                "rep_platform": signature.bugzilla_cpu_arch,
194
                "cf_crash_signature": f"[@ {signature.signature_term}]",
195
                "description": description,
196
                self.nightly_status_flag: "affected",
197
                # NOTE(suhaib): the following CC is for testing purposes only
198
                # to allow us access and evaluate security bugs. It should be
199
                # removed at some point after we move to production.
200
                "cc": [
201
                    "smujahid@mozilla.com",
202
                    "mcastelluccio@mozilla.com",
203
                    "aryx.bugmail@gmx-topmail.de",
204
                ],
205
            }
206

207
            if needinfo_regression_author:
×
208
                bug_data["flags"] = [
×
209
                    {
210
                        "name": "needinfo",
211
                        "requestee": signature.regressed_by_author["name"],
212
                        "status": "?",
213
                        "new": "true",
214
                    }
215
                ]
216

217
            if signature.is_potential_security_crash:
×
218
                bug_data["groups"] = ["core-security"]
×
219

220
            if signature.regressed_by:
×
221
                bug_data["keywords"].append("regression")
×
222
                bug_data["regressed_by"] = [signature.regressed_by]
×
223

224
                # Empty statuses are needed to detect the affected releases.
225
                for flag in self.current_status_flags:
×
226
                    if flag not in bug_data:
×
227
                        bug_data[flag] = "---"
×
228

229
                bug_analyzer = BugAnalyzer(bug_data, signature.bugs_store)
×
230
                updates = bug_analyzer.detect_version_status_updates()
×
231
                for update in updates:
×
232
                    bug_data[update.flag] = update.status
×
233

234
            if self.dryrun:
×
235
                logger.info("Dry-run bug:")
×
236
                pprint.pprint(bug_data)
×
237
                bug_id = str(len(bugs) + 1)
×
238
            else:
239
                try:
×
240
                    bug = utils.create_bug(bug_data)
×
241
                except requests.HTTPError as err:
×
242
                    logger.exception(
×
243
                        "Failed to create a bug for signature %s: %s",
244
                        signature.signature_term,
245
                        err.response.text,
246
                    )
247
                    continue
×
248

249
                bug_id = str(bug["id"])
×
250
                # TODO: log the created bugs info somewhere (e.g., DB,
251
                # spreadsheet, or LabelStudio)
252

253
            bugs[bug_id] = {
×
254
                "id": bug_id,
255
                "summary": "..." if signature.is_potential_security_crash else title,
256
                "component": signature.crash_component,
257
            }
258

259
        logger.debug("Total of %d bugs have been filed", len(bugs))
×
260

261
        return bugs
×
262

263

264
if __name__ == "__main__":
×
265
    FileCrashBug().run()
×
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