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

mozilla / relman-auto-nag / #4686

pending completion
#4686

push

coveralls-python

web-flow
[file_crash_bug] Fix the template and re-generate bugs (#2180)

716 of 3564 branches covered (20.09%)

7 of 7 new or added lines in 2 files covered. (100.0%)

1924 of 8726 relevant lines covered (22.05%)

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 (
×
16
    EXPERIMENT_VERSION,
17
    DevBugzilla,
18
    SignatureAnalyzer,
19
    SignaturesDataFetcher,
20
)
21
from bugbot.user_activity import UserActivity, UserStatus
×
22

23

24
class FileCrashBug(BzCleaner):
×
25
    """File bugs for new actionable crashes."""
26

27
    MAX_BUG_TITLE_LENGTH = 255
×
28
    FILE_ON_BUGZILLA_DEV = True
×
29

30
    def __init__(self):
×
31
        super().__init__()
×
32

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

37
    def description(self):
×
38
        return "New actionable crashes"
×
39

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

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

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

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

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

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

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

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

90
        signature_details_delta = humanize.naturaldelta(data_fetcher.SUMMARY_DURATION)
×
91

92
        active_regression_authors = self._active_regression_authors(signatures)
×
93

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

245
        return bugs
×
246

247

248
if __name__ == "__main__":
×
249
    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