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

mozilla / relman-auto-nag / #4661

pending completion
#4661

push

coveralls-python

web-flow
[file_crash_bug] Set "nightly" as affected on regressions with no status flags (#2164)

716 of 3556 branches covered (20.13%)

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

1924 of 8703 relevant lines covered (22.11%)

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 jinja2
×
8
import requests
×
9

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

17

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

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

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

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

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

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

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

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

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

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

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

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

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

86
        active_regression_authors = self._active_regression_authors(signatures)
×
87

88
        for signature in signatures:
×
89
            logger.debug("Generating bug for signature: %s", signature.signature_term)
×
90

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

99
            # Whether we should needinfo the regression author.
100
            needinfo_regression_author = (
×
101
                signature.regressed_by
102
                and signature.regressed_by_author["email"] in active_regression_authors
103
            )
104

105
            report = signature.fetch_representative_processed_crash()
×
106
            description = self.bug_description_template.render(
×
107
                {
108
                    **socorro_util.generate_bug_description_data(report),
109
                    "signature": signature,
110
                    "needinfo_regression_author": needinfo_regression_author,
111
                }
112
            )
113

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

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

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

165
            if signature.regressed_by:
×
166
                bug_data["keywords"].append("regression")
×
167

168
            # NOTE: Filling the `regressed_by` field on bugzilla-dev will cause
169
            # "bug does not exist" errors.
170
            if signature.regressed_by and not self.FILE_ON_BUGZILLA_DEV:
×
171
                bug_data["regressed_by"] = signature.regressed_by
×
172

173
            if signature.is_potential_security_crash:
×
174
                bug_data["groups"] = ["core-security"]
×
175

176
            # NOTE: The following will have no effect on bugzilla-dev since we
177
            # don't fill the `regressed_by` field. Anyway, setting the version
178
            # status flag will cause errors on bugzilla-dev since the fields are
179
            # out of sync.
180
            bug_analyzer = BugAnalyzer(bug_data, signature.bugs_store)
×
181
            updates = bug_analyzer.detect_version_status_updates()
×
182
            for update in updates:
×
183
                bug_data[update.flag] = update.status
×
184

185
            if bug_data["regressed_by"] and not updates:
×
186
                # If we don't set the nightly flag here, the bot will set it
187
                # later as part of `regression_new_set_nightly_affected` rule.
188
                nightly_flag = utils.get_flag(
×
189
                    self.versions["nightly"], "status", "nightly"
190
                )
191
                bug_data[nightly_flag] = "affected"
×
192

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

223
                bug = resp.json()
×
224
                bug_id = str(bug["id"])
×
225
                # TODO: log the created bugs info somewhere (e.g., DB,
226
                # spreadsheet, or LabelStudio)
227

228
            bugs[bug_id] = {
×
229
                "id": bug_id,
230
                "summary": "..." if signature.is_potential_security_crash else title,
231
                "component": signature.crash_component,
232
            }
233

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

236
        return bugs
×
237

238

239
if __name__ == "__main__":
×
240
    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