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

mozilla / relman-auto-nag / #5426

20 Feb 2025 01:14PM CUT coverage: 21.147% (-0.05%) from 21.2%
#5426

push

coveralls-python

gmierz
Remove acceptance methods, and set comment outside method.

426 of 2966 branches covered (14.36%)

0 of 14 new or added lines in 1 file covered. (0.0%)

68 existing lines in 1 file now uncovered.

1943 of 9188 relevant lines covered (21.15%)

0.21 hits per line

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

0.0
/bugbot/rules/perfalert_resolved_regression.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
from datetime import timedelta
×
6

7
from libmozdata import utils as lmdutils
×
8
from libmozdata.bugzilla import BugzillaUser
×
9

10
from bugbot.bzcleaner import BzCleaner
×
11
from bugbot.constants import BOT_MAIN_ACCOUNT
×
12

UNCOV
13
RESOLUTION_KEYWORDS = (
×
14
    "backedout",
15
    "backed out",
16
    "back out",
17
    "backout",
18
    "wontfix",
19
    "invalid",
20
    "incomplete",
21
    "duplicate",
22
    "fixed",
23
    "resolved",
24
    "resolve",
25
    "resolution",
26
)
27

28

UNCOV
29
class PerfAlertResolvedRegression(BzCleaner):
×
NEW
30
    def __init__(self, acceptance_time=86400):
×
NEW
31
        super().__init__()
×
NEW
32
        self.acceptance_time = acceptance_time
×
33
        self.extra_ni = {}
×
34

UNCOV
35
    def description(self):
×
36
        return "PerfAlert regressions whose resolution has changed recently"
×
37

38
    def columns(self):
×
UNCOV
39
        return [
×
40
            "id",
41
            "summary",
42
            "status",
43
            "status_author",
44
            "resolution",
45
            "resolution_comment",
46
            "resolution_previous",
47
            "needinfo",
48
        ]
49

UNCOV
50
    def get_extra_for_needinfo_template(self):
×
51
        return self.extra_ni
×
52

UNCOV
53
    def get_bz_params(self, date):
×
UNCOV
54
        end_date = lmdutils.get_date_ymd("today")
×
UNCOV
55
        start_date = end_date - timedelta(1)
×
56

UNCOV
57
        fields = [
×
58
            "id",
59
            "history",
60
            "comments.text",
61
            "comments.creation_time",
62
            "comments.author",
63
        ]
64

65
        # Find all bugs that have perf-alert, and regression in their keywords. Search
66
        # for bugs that have been changed in the last day. Only look for bugs after
67
        # October 1st, 2024 to prevent triggering comments on older performance regressions
UNCOV
68
        params = {
×
69
            "include_fields": fields,
70
            "f3": "creation_ts",
71
            "o3": "greaterthan",
72
            "v3": "2024-10-01T00:00:00Z",
73
            "f1": "regressed_by",
74
            "o1": "isnotempty",
75
            "f2": "keywords",
76
            "o2": "allwords",
77
            "v2": ["regression", "perf-alert"],
78
            "f4": "resolution",
79
            "o4": "changedafter",
80
            "v4": start_date,
81
            "f5": "resolution",
82
            "o5": "changedbefore",
83
            "v5": end_date,
84
        }
85

86
        return params
×
87

88
    def should_needinfo(self, bug_comments, status_time):
×
89
        # Check if the bugbot has already needinfo'ed on the bug since
90
        # the last status change before making one
91
        for comment in bug_comments[::-1]:
×
UNCOV
92
            if comment["creation_time"] <= status_time:
×
UNCOV
93
                break
×
94

UNCOV
95
            if comment["author"] == BOT_MAIN_ACCOUNT:
×
UNCOV
96
                if (
×
97
                    "could you provide a comment explaining the resolution?"
98
                    in comment["text"]
99
                ):
100
                    # Bugbot has already commented on this bug since the last
101
                    # status change. No need to comment again since this was
102
                    # just a resolution change
UNCOV
103
                    return False
×
104

105
        return True
×
106

107
    def get_resolution_comments(self, comments, status_time):
×
UNCOV
108
        resolution_comment = None
×
109
        preceding_comment = None
×
110

111
        for comment in comments:
×
UNCOV
112
            if comment["creation_time"] > status_time:
×
UNCOV
113
                break
×
UNCOV
114
            if comment["creation_time"] == status_time:
×
115
                resolution_comment = comment
×
UNCOV
116
            if (
×
117
                comment["author"] != BOT_MAIN_ACCOUNT
118
                and comment["author"] != "intermittent-bug-filer@mozilla.bugs"
119
            ):
UNCOV
120
                preceding_comment = comment
×
121

UNCOV
122
        return resolution_comment, preceding_comment
×
123

NEW
124
    def get_resolution_comment(self, comments, bug_history):
×
125
        status_time = bug_history["status_time"]
×
UNCOV
126
        resolution_comment, preceding_comment = self.get_resolution_comments(
×
127
            comments, status_time
128
        )
129

NEW
130
        if (
×
131
            resolution_comment
132
            and resolution_comment["author"] == bug_history["status_author"]
133
        ):
134
            # Accept if status author provided a comment at the same time
NEW
135
            return resolution_comment["text"]
×
136
        elif preceding_comment:
×
NEW
137
            if preceding_comment["author"] == bug_history["status_author"]:
×
138
                # Accept if status author provided a comment before setting
139
                # resolution
NEW
140
                return preceding_comment["text"]
×
NEW
141
            elif any(
×
142
                keyword in preceding_comment["text"].lower()
143
                for keyword in RESOLUTION_KEYWORDS
144
            ):
145
                # Accept if a non-status author provided a comment before a
146
                # resolution was set, and hit some keywords
NEW
147
                return (
×
148
                    preceding_comment["text"]
149
                    + f" (provided by {preceding_comment['author']})"
150
                )
NEW
151
            elif (
×
152
                lmdutils.get_timestamp(status_time)
153
                - lmdutils.get_timestamp(preceding_comment["creation_time"])
154
            ) < self.acceptance_time:
155
                # Accept if the previous comment from another author is
156
                # within the last 24 hours
NEW
157
                return (
×
158
                    preceding_comment["text"]
159
                    + f" (provided by {preceding_comment['author']})"
160
                )
161

NEW
162
        return "N/A"
×
163

164
    def get_resolution_history(self, bug):
×
165
        bug_info = {}
×
166

167
        # Get the last resolution change that was made in this bug
UNCOV
168
        for change in bug["history"][::-1]:
×
169
            # Get the most recent resolution change first, this is because
170
            # it could have changed since the status was changed and by who
171
            if not bug_info.get("resolution"):
×
UNCOV
172
                for specific_change in change["changes"]:
×
173
                    if specific_change["field_name"] == "resolution":
×
UNCOV
174
                        bug_info["resolution"] = specific_change["added"]
×
UNCOV
175
                        bug_info["resolution_previous"] = (
×
176
                            specific_change["removed"].strip() or "---"
177
                        )
UNCOV
178
                        bug_info["resolution_time"] = change["when"]
×
179
                        break
×
180

181
            if bug_info.get("resolution"):
×
182
                # Find the status that the bug was resolved to, and by who
183
                for specific_change in change["changes"]:
×
184
                    if specific_change["field_name"] == "status" and specific_change[
×
185
                        "added"
186
                    ] in ("RESOLVED", "REOPENED"):
UNCOV
187
                        bug_info["status"] = specific_change["added"]
×
188
                        bug_info["status_author"] = change["who"]
×
189
                        bug_info["status_time"] = change["when"]
×
UNCOV
190
                        break
×
191

UNCOV
192
            if bug_info.get("status"):
×
UNCOV
193
                break
×
194

UNCOV
195
        return bug_info
×
196

UNCOV
197
    def set_autofix(self, bugs):
×
UNCOV
198
        for bug_id, bug_info in bugs.items():
×
UNCOV
199
            if bug_info["needinfo"]:
×
UNCOV
200
                self.extra_ni[bug_id] = {
×
201
                    "resolution": bug_info["resolution"],
202
                    "status": bug_info["status"],
203
                }
UNCOV
204
                self.add_auto_ni(
×
205
                    bug_id,
206
                    {
207
                        "mail": bug_info["status_author"],
208
                        "nickname": bug_info["nickname"],
209
                    },
210
                )
211

UNCOV
212
    def get_needinfo_nicks(self, bugs):
×
UNCOV
213
        def _user_handler(user, data):
×
UNCOV
214
            data[user["name"]] = user["nick"]
×
215

UNCOV
216
        authors_to_ni = set()
×
UNCOV
217
        for bug_id, bug_info in bugs.items():
×
UNCOV
218
            if bug_info["needinfo"]:
×
UNCOV
219
                authors_to_ni.add(bug_info["status_author"])
×
220

UNCOV
221
        if not authors_to_ni:
×
UNCOV
222
            return
×
223

UNCOV
224
        user_emails_to_names = {}
×
UNCOV
225
        BugzillaUser(
×
226
            user_names=list(authors_to_ni),
227
            include_fields=["nick", "name"],
228
            user_handler=_user_handler,
229
            user_data=user_emails_to_names,
230
        ).wait()
231

UNCOV
232
        for bug_id, bug_info in bugs.items():
×
UNCOV
233
            if bug_info["needinfo"]:
×
UNCOV
234
                bug_info["nickname"] = user_emails_to_names[bug_info["status_author"]]
×
235

UNCOV
236
    def handle_bug(self, bug, data):
×
237
        # Match all the resolutions with resolution comments if they exist
UNCOV
238
        bug_id = str(bug["id"])
×
UNCOV
239
        bug_comments = bug["comments"]
×
UNCOV
240
        bug_history = self.get_resolution_history(bug)
×
241

242
        # Sometimes a resolution comment is not provided so use a default
UNCOV
243
        bug_history["needinfo"] = False
×
NEW
244
        bug_history["resolution_comment"] = self.get_resolution_comment(
×
245
            bug_comments, bug_history
246
        )
UNCOV
247
        if bug_history["resolution_comment"] == "N/A":
×
UNCOV
248
            bug_history["needinfo"] = self.should_needinfo(
×
249
                bug_comments, bug_history["status_time"]
250
            )
251

UNCOV
252
        data[bug_id] = bug_history
×
253

UNCOV
254
        return bug
×
255

UNCOV
256
    def get_bugs(self, *args, **kwargs):
×
UNCOV
257
        bugs = super().get_bugs(*args, **kwargs)
×
UNCOV
258
        self.get_needinfo_nicks(bugs)
×
UNCOV
259
        self.set_autofix(bugs)
×
UNCOV
260
        return bugs
×
261

262

UNCOV
263
if __name__ == "__main__":
×
UNCOV
264
    PerfAlertResolvedRegression().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