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

mozilla / relman-auto-nag / #4160

pending completion
#4160

push

coveralls-python

web-flow
Fix a typo

Co-authored-by: Marco Castelluccio <mcastelluccio@mozilla.com>

553 of 3089 branches covered (17.9%)

1797 of 7986 relevant lines covered (22.5%)

0.23 hits per line

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

0.0
/auto_nag/scripts/topcrash_add_keyword.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 itertools
×
6
import re
×
7
from collections import defaultdict
×
8

9
from libmozdata.bugzilla import Bugzilla
×
10
from libmozdata.connection import Connection
×
11

12
from auto_nag import utils
×
13
from auto_nag.bzcleaner import BzCleaner
×
14
from auto_nag.constants import HIGH_SEVERITY
×
15
from auto_nag.topcrash import Topcrash
×
16

17
MAX_SIGNATURES_PER_QUERY = 30
×
18

19

20
class TopcrashAddKeyword(BzCleaner):
×
21
    def description(self):
×
22
        return "Bugs with missing topcrash keywords"
×
23

24
    def columns(self):
×
25
        return ["id", "summary", "severity", "added_keywords"]
×
26

27
    def handle_bug(self, bug, data):
×
28
        bugid = str(bug["id"])
×
29
        if bugid in data:
×
30
            return
×
31

32
        existing_keywords = {
×
33
            keyword
34
            for keyword in ("topcrash", "topcrash-startup")
35
            if keyword in bug["keywords"]
36
        }
37

38
        if len(existing_keywords) == 2:
×
39
            # No topcrash keywords to add, the bug has them all
40
            return
×
41

42
        top_crash_signatures = [
×
43
            signature
44
            for signature in utils.get_signatures(bug["cf_crash_signature"])
45
            if signature in self.topcrashes
46
        ]
47

48
        if not top_crash_signatures:
×
49
            raise Exception("The bug should have a topcrash signature.")
×
50

51
        elif any(
×
52
            # Is it a startup topcrash bug?
53
            any(criterion["is_startup"] for criterion in self.topcrashes[signature])
54
            for signature in top_crash_signatures
55
        ):
56
            keywords_to_add = {"topcrash", "topcrash-startup"}
×
57

58
        else:
59
            keywords_to_add = {"topcrash"}
×
60

61
        keywords_to_add = keywords_to_add - existing_keywords
×
62
        if not keywords_to_add:
×
63
            return
×
64

65
        autofix = {
×
66
            "keywords": {
67
                "add": list(keywords_to_add),
68
            },
69
            "comment": {
70
                "body": self.get_matching_criteria_comment(top_crash_signatures),
71
            },
72
        }
73

74
        ni_person = utils.get_mail_to_ni(bug)
×
75
        if (
×
76
            ni_person
77
            and bug["severity"] not in HIGH_SEVERITY
78
            and "meta" not in bug["keywords"]
79
        ):
80
            autofix["flags"] = [
×
81
                {
82
                    "name": "needinfo",
83
                    "requestee": ni_person["mail"],
84
                    "status": "?",
85
                    "new": "true",
86
                }
87
            ]
88
            autofix["comment"]["body"] += (
×
89
                f'\n:{ ni_person["nickname"] }, '
90
                "could you consider increasing the severity of this top-crash bug?"
91
            )
92

93
        autofix["comment"]["body"] += f"\n\n{ self.get_documentation() }\n"
×
94
        self.autofix_changes[bugid] = autofix
×
95

96
        data[bugid] = {
×
97
            "severity": bug["severity"],
98
            "added_keywords": utils.english_list(sorted(keywords_to_add)),
99
        }
100

101
        return bug
×
102

103
    def get_matching_criteria_comment(self, signatures):
×
104
        matching_criteria = defaultdict(bool)
×
105
        for signature in signatures:
×
106
            for criterion in self.topcrashes[signature]:
×
107
                matching_criteria[criterion["criterion_name"]] |= criterion[
×
108
                    "is_startup"
109
                ]
110

111
        introduction = (
×
112
            "The bug is linked to ",
113
            (
114
                "a topcrash signature, which matches "
115
                if len(signatures) == 1
116
                else "topcrash signatures, which match "
117
            ),
118
            "the following [",
119
            "criterion" if len(matching_criteria) == 1 else "criteria",
120
            "](https://wiki.mozilla.org/CrashKill/Topcrash):\n",
121
        )
122

123
        criteria = (
×
124
            " ".join(("-", criterion_name, "(startup)\n" if is_startup else "\n"))
125
            for criterion_name, is_startup in matching_criteria.items()
126
        )
127

128
        return "".join(itertools.chain(introduction, criteria))
×
129

130
    def get_bugs(self, date="today", bug_ids=[], chunk_size=None):
×
131
        self.query_url = None
×
132
        timeout = self.get_config("bz_query_timeout")
×
133
        bugs = self.get_data()
×
134
        params_list = self.get_bz_params_list(date)
×
135

136
        searches = [
×
137
            Bugzilla(
138
                params,
139
                bughandler=self.bughandler,
140
                bugdata=bugs,
141
                timeout=timeout,
142
            ).get_data()
143
            for params in params_list
144
        ]
145

146
        for search in searches:
×
147
            search.wait()
×
148

149
        return bugs
×
150

151
    def get_bz_params_list(self, date):
×
152
        self.topcrashes = Topcrash(date).get_signatures()
×
153

154
        fields = [
×
155
            "triage_owner",
156
            "assigned_to",
157
            "severity",
158
            "keywords",
159
            "cf_crash_signature",
160
        ]
161
        params_base = {
×
162
            "include_fields": fields,
163
            "resolution": "---",
164
        }
165
        self.amend_bzparams(params_base, [])
×
166

167
        params_list = []
×
168
        for signatures in Connection.chunks(
×
169
            list(self.topcrashes.keys()),
170
            MAX_SIGNATURES_PER_QUERY,
171
        ):
172
            params = params_base.copy()
×
173
            n = int(utils.get_last_field_num(params))
×
174
            params[f"f{n}"] = "OP"
×
175
            params[f"j{n}"] = "OR"
×
176
            for signature in signatures:
×
177
                n += 1
×
178
                params[f"f{n}"] = "cf_crash_signature"
×
179
                params[f"o{n}"] = "regexp"
×
180
                # Using `(@ |@)` instead of `@ ?` and ( \]|\]) instead of ` ?]`
181
                # is a workaround. Strangely `?` stays with the encoded form (%3F)
182
                # in Bugzilla query.
183
                # params[f"v{n}"] = f"\[@ ?{re.escape(signature)} ?\]"
184
                params[f"v{n}"] = rf"\[(@ |@){re.escape(signature)}( \]|\])"
×
185
            params[f"f{n+1}"] = "CP"
×
186
            params_list.append(params)
×
187

188
        return params_list
×
189

190

191
if __name__ == "__main__":
×
192
    TopcrashAddKeyword().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