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

mozilla / relman-auto-nag / #4678

pending completion
#4678

push

coveralls-python

suhaibmujahid
[file_crash_bug] Show the date for the first crash

716 of 3564 branches covered (20.09%)

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

1924 of 8720 relevant lines covered (22.06%)

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

7
import humanize
×
8
import jinja2
×
9
import requests
×
10

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

18

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

22
    # NOTE: If you make changes that affect the output of the rule, you should
23
    # increment this number. This is needed in the experimental phase only.
24
    VERSION = 2
×
25
    MAX_BUG_TITLE_LENGTH = 255
×
26
    FILE_ON_BUGZILLA_DEV = True
×
27

28
    def __init__(self):
×
29
        super().__init__()
×
30

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

35
    def description(self):
×
36
        return "New actionable crashes"
×
37

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

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

48
    def _active_regression_authors(
×
49
        self, signatures: list[SignatureAnalyzer]
50
    ) -> set[str]:
51
        """Get Bugzilla usernames for users who are active and can be needinfo'd.
52

53
        Args:
54
            signatures: a list of signatures for which to check the status of
55
                their regression author.
56

57
        Returns:
58
            A set of user emails.
59
        """
60
        ni_skiplist = self.get_auto_ni_skiplist()
×
61
        users = UserActivity(include_fields=["requests"]).check_users(
×
62
            (
63
                signature.regressed_by_author["name"]
64
                for signature in signatures
65
                if signature.regressed_by_author
66
            ),
67
            keep_active=True,
68
            fetch_employee_info=True,
69
        )
70

71
        return {
×
72
            name
73
            for name, user in users.items()
74
            if name not in ni_skiplist
75
            and user["status"] == UserStatus.ACTIVE
76
            and not user["requests"]["needinfo"]["blocked"]
77
        }
78

79
    def get_bugs(self, date):
×
80
        self.query_url = None
×
81
        bugs = {}
×
82

83
        data_fetcher = SignaturesDataFetcher.find_new_actionable_crashes(
×
84
            "Firefox", "nightly"
85
        )
86
        signatures = data_fetcher.analyze()
×
87

88
        signature_details_delta = humanize.naturaldelta(data_fetcher.SUMMARY_DURATION)
×
89

90
        active_regression_authors = self._active_regression_authors(signatures)
×
91

92
        for signature in signatures:
×
93
            logger.debug("Generating bug for signature: %s", signature.signature_term)
×
94

95
            title = (
×
96
                f"Startup crash in [@ {signature.signature_term}]"
97
                if signature.is_startup_related_crash
98
                else f"Crash in [@ {signature.signature_term}]"
99
            )
100
            if len(title) > self.MAX_BUG_TITLE_LENGTH:
×
101
                title = title[: self.MAX_BUG_TITLE_LENGTH - 3] + "..."
×
102

103
            # Whether we should needinfo the regression author.
104
            needinfo_regression_author = (
×
105
                signature.regressed_by
106
                and signature.regressed_by_author["email"] in active_regression_authors
107
            )
108

109
            report = signature.fetch_representative_processed_crash()
×
110
            description = self.bug_description_template.render(
×
111
                {
112
                    **socorro_util.generate_bug_description_data(report),
113
                    "signature": signature,
114
                    "needinfo_regression_author": needinfo_regression_author,
115
                    "signature_details_delta": signature_details_delta,
116
                    "signature_details_channel": "Nightly",
117
                }
118
            )
119

120
            # TODO: Provide the following information:
121
            # [X] Crash signature
122
            # [X] Top 10 frames of crashing thread
123
            # [X] Component
124
            # [X] The kind of crash
125
            # [ ] Regression window
126
            # [X] Inducing patch
127
            # [X] Reason
128
            # [X] Regressed by
129
            # [X] Platform
130
            # [ ] Firefox status flags
131
            # [ ] Severity
132
            # [ ] Time correlation
133
            # [X] User comments
134
            # [X] Crash address commonalities
135
            # [ ] Estimated future crash volume
136

137
            bug_data = {
×
138
                "blocks": "bugbot-auto-crash",
139
                "type": "defect",
140
                "keywords": ["crash"],
141
                "status_whiteboard": f"[bugbot-crash-v{self.VERSION}]",
142
                "summary": title,
143
                "product": signature.crash_component.product,
144
                "component": signature.crash_component.name,
145
                "op_sys": signature.bugzilla_op_sys,
146
                "rep_platform": signature.bugzilla_cpu_arch,
147
                "cf_crash_signature": f"[@ {signature.signature_term}]",
148
                "description": description,
149
                # NOTE(suhaib): the following CC is for testing purposes only
150
                # to allow us access and evaluate security bugs. It should be
151
                # removed at some point after we move to production.
152
                "cc": [
153
                    "smujahid@mozilla.com",
154
                    "mcastelluccio@mozilla.com",
155
                    "aryx.bugmail@gmx-topmail.de",
156
                ],
157
            }
158

159
            # NOTE: Filling the `flags` field on bugzilla-dev will cause an
160
            # error when the user does not exist.
161
            if needinfo_regression_author and not self.FILE_ON_BUGZILLA_DEV:
×
162
                bug_data["flags"] = [
×
163
                    {
164
                        "name": "needinfo",
165
                        "requestee": signature.regressed_by_author["name"],
166
                        "status": "?",
167
                        "new": "true",
168
                    }
169
                ]
170

171
            if signature.regressed_by:
×
172
                bug_data["keywords"].append("regression")
×
173

174
            # NOTE: Filling the `regressed_by` field on bugzilla-dev will cause
175
            # "bug does not exist" errors.
176
            if signature.regressed_by and not self.FILE_ON_BUGZILLA_DEV:
×
177
                bug_data["regressed_by"] = signature.regressed_by
×
178

179
            if signature.is_potential_security_crash:
×
180
                bug_data["groups"] = ["core-security"]
×
181

182
            # NOTE: The following will have no effect on bugzilla-dev since we
183
            # don't fill the `regressed_by` field. Anyway, setting the version
184
            # status flag will cause errors on bugzilla-dev since the fields are
185
            # out of sync.
186
            if "regressed_by" in bug_data:
×
187
                bug_analyzer = BugAnalyzer(bug_data, signature.bugs_store)
×
188
                updates = bug_analyzer.detect_version_status_updates()
×
189
                for update in updates:
×
190
                    bug_data[update.flag] = update.status
×
191

192
                if bug_data["regressed_by"] and not updates:
×
193
                    # If we don't set the nightly flag here, the bot will set it
194
                    # later as part of `regression_new_set_nightly_affected` rule.
195
                    nightly_flag = utils.get_flag(
×
196
                        self.versions["nightly"], "status", "nightly"
197
                    )
198
                    bug_data[nightly_flag] = "affected"
×
199

200
            if self.dryrun:
×
201
                logger.info("Dry-run bug:")
×
202
                pprint.pprint(bug_data)
×
203
                bug_id = str(len(bugs) + 1)
×
204
            else:
205
                # NOTE: When moving to production:
206
                #   - Use Bugzilla instead of DevBugzilla
207
                #   - Drop the DevBugzilla class
208
                #   - Update the bug URL `file_crash_bug.html`
209
                #   - Drop the bug link `file_crash_bug_description.md.jinja`
210
                #   - Fill the `regressed_by` and `flags` fields
211
                #   - Create the bug using `utils.create_bug`
212
                #   - Drop the FILE_ON_BUGZILLA_DEV attribute
213
                resp = requests.post(
×
214
                    url=DevBugzilla.API_URL,
215
                    json=bug_data,
216
                    headers=DevBugzilla([]).get_header(),
217
                    verify=True,
218
                    timeout=DevBugzilla.TIMEOUT,
219
                )
220
                try:
×
221
                    resp.raise_for_status()
×
222
                except requests.HTTPError:
×
223
                    logger.exception(
×
224
                        "Failed to create a bug for signature %s: %s",
225
                        signature.signature_term,
226
                        resp.text,
227
                    )
228
                    continue
×
229

230
                bug = resp.json()
×
231
                bug_id = str(bug["id"])
×
232
                # TODO: log the created bugs info somewhere (e.g., DB,
233
                # spreadsheet, or LabelStudio)
234

235
            bugs[bug_id] = {
×
236
                "id": bug_id,
237
                "summary": "..." if signature.is_potential_security_crash else title,
238
                "component": signature.crash_component,
239
            }
240

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

243
        return bugs
×
244

245

246
if __name__ == "__main__":
×
247
    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