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

mozilla / relman-auto-nag / #4510

pending completion
#4510

push

coveralls-python

suhaibmujahid
Merge remote-tracking branch 'upstream/master' into unhide-security

641 of 3228 branches covered (19.86%)

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

1817 of 8050 relevant lines covered (22.57%)

0.23 hits per line

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

69.4
/bugbot/rules/regression_set_status_flags.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 libmozdata.bugzilla import Bugzilla
1✔
6

7
from bugbot import logger, utils
1✔
8
from bugbot.bzcleaner import BzCleaner
1✔
9

10

11
class RegressionSetStatusFlags(BzCleaner):
1✔
12
    def __init__(self):
1✔
13
        super().__init__()
1✔
14
        if not self.init_versions():
1!
15
            return
×
16

17
        self.status_esr = utils.get_flag(self.versions["esr_previous"], "status", "esr")
1✔
18
        self.status_esr_next = utils.get_flag(self.versions["esr"], "status", "esr")
1✔
19
        self.status_changes = {}
1✔
20
        self.component_exception = set(
1✔
21
            utils.get_config(self.name(), "component_exception")
22
        )
23

24
    def description(self):
1✔
25
        return "Set release status flags based on info from the regressing bug"
1✔
26

27
    def get_bz_params(self, date):
1✔
28
        start_date, _ = self.get_dates(date)
×
29

30
        # Find all bugs with regressed_by information which were open after start_date or
31
        # whose regressed_by field was set after start_date.
32

33
        return {
×
34
            "include_fields": ["regressed_by", "_custom", "product", "component"],
35
            "f1": "OP",
36
            "j1": "OR",
37
            "f2": "creation_ts",
38
            "o2": "greaterthan",
39
            "v2": start_date,
40
            "f3": "regressed_by",
41
            "o3": "changedafter",
42
            "v3": start_date,
43
            "f4": "CP",
44
            "f5": "regressed_by",
45
            "o5": "isnotempty",
46
            "f6": "cf_status_firefox_release",
47
            "o6": "nowords",
48
            "v6": "fixed,verified",
49
            "resolution": ["---", "FIXED"],
50
            "bug_status_type": "notequals",
51
            "bug_status": "UNCONFIRMED",
52
        }
53

54
    def get_extra_for_template(self):
1✔
55
        if self.status_esr == self.status_esr_next:
×
56
            return [f"esr{self.versions['esr']}"]
×
57
        return [f"esr{self.versions['esr_previous']}", f"esr{self.versions['esr']}"]
×
58

59
    def handle_bug(self, bug, data):
1✔
60
        pc = f"{bug['product']}::{bug['component']}"
×
61
        if pc in self.component_exception:
×
62
            return
×
63

64
        bugid = bug["id"]
×
65
        if len(bug["regressed_by"]) != 1:
×
66
            # either we don't have access to the regressor, or there's more than one, either way leave things alone
67
            return
×
68
        bug["regressed_by"] = bug["regressed_by"][0]
×
69
        data[str(bugid)] = bug
×
70

71
    def get_flags_from_regressing_bugs(self, bugids):
1✔
72
        def bug_handler(bug, data):
×
73
            data[bug["id"]] = bug
×
74

75
        data = {}
×
76
        Bugzilla(
×
77
            bugids=list(bugids), bughandler=bug_handler, bugdata=data
78
        ).get_data().wait()
79

80
        return data
×
81

82
    @staticmethod
1✔
83
    def _is_latest_status_flag_wontfix(bug: dict) -> bool:
1✔
84
        """Check if the latest status flag is wontfix or fix-optional."""
85
        versions_status = sorted(
1✔
86
            (int(flag[len("cf_status_firefox") :]), status)
87
            for flag, status in bug.items()
88
            if status != "---"
89
            and flag.startswith("cf_status_firefox")
90
            and "esr" not in flag
91
        )
92

93
        if not versions_status:
1✔
94
            return False
1✔
95

96
        status = versions_status[-1][1]
1✔
97

98
        return status in ("wontfix", "fix-optional")
1✔
99

100
    def get_status_changes(self, bugs):
1✔
101
        bugids = {info["regressed_by"] for info in bugs.values()}
1✔
102
        if not bugids:
1!
103
            return {}
×
104

105
        data = self.get_flags_from_regressing_bugs(bugids)
1✔
106

107
        filtered_bugs = {}
1✔
108
        for bugid, info in bugs.items():
1✔
109
            regressor = info["regressed_by"]
1✔
110
            if regressor not in data:
1!
111
                logger.error(
×
112
                    "Rule %s: regressor %s is not found",
113
                    self.name(),
114
                    regressor,
115
                )
116
                # This should be OK in local environment where we don't have
117
                # access to all bugs, but it is not OK in production.
118
                continue
×
119

120
            regression_versions = sorted(
1✔
121
                v
122
                for v in data[regressor]
123
                if v.startswith("cf_status_firefox")
124
                and data[regressor][v] in ("fixed", "verified")
125
            )
126
            if not regression_versions:
1!
127
                # don't know what to do, ignore
128
                continue
×
129
            if regression_versions[0].startswith("cf_status_firefox_esr"):
1!
130
                # shouldn't happen: esrXX sorts after YY
131
                continue
×
132
            regressed_version = int(regression_versions[0][len("cf_status_firefox") :])
1✔
133

134
            fixed_versions = sorted(
1✔
135
                v
136
                for v in info
137
                if v.startswith("cf_status_firefox")
138
                and info[v] in ("fixed", "verified")
139
            )
140
            if len(fixed_versions) > 0 and fixed_versions[0].startswith(
1!
141
                "cf_status_firefox_esr"
142
            ):
143
                # shouldn't happen: esrXX sorts after YY
144
                continue
×
145
            fixed_version = (
1✔
146
                int(fixed_versions[0][len("cf_status_firefox") :])
147
                if len(fixed_versions) > 0
148
                else None
149
            )
150

151
            # If the latest status flag is wontfix or fix-optional, we ignore
152
            # setting flags with the status "affected".
153
            is_latest_wontfix = self._is_latest_status_flag_wontfix(info)
1✔
154

155
            self.status_changes[bugid] = {}
1✔
156
            for channel in ("release", "beta", "central"):
1✔
157
                v = int(self.versions[channel])
1✔
158
                flag = utils.get_flag(v, "status", channel)
1✔
159
                info[channel] = info[flag]
1✔
160
                if info[flag] != "---":
1✔
161
                    # XXX maybe check for consistency?
162
                    continue
1✔
163
                if fixed_version is not None and v >= fixed_version:
1!
164
                    # Bug was fixed in an earlier version, don't set the flag
165
                    continue
×
166
                if v >= regressed_version:
1✔
167
                    if is_latest_wontfix:
1!
168
                        continue
×
169
                    self.status_changes[bugid][flag] = "affected"
1✔
170
                    info[channel] = "affected"
1✔
171
                else:
172
                    self.status_changes[bugid][flag] = "unaffected"
1✔
173
                    info[channel] = "unaffected"
1✔
174
                filtered_bugs[bugid] = info
1✔
175

176
            esr_versions = set([self.versions["esr"], self.versions["esr_previous"]])
1✔
177
            for v in esr_versions:
1✔
178
                info.setdefault("esr", {})
1✔
179
                flag = utils.get_flag(v, "status", "esr")
1✔
180
                if flag not in info:
1!
181
                    info["esr"][f"esr{v}"] = "n/a"
×
182
                    continue
×
183
                info["esr"][f"esr{v}"] = info[flag]
1✔
184
                if info[flag] != "---":
1!
185
                    # XXX maybe check for consistency?
186
                    continue
×
187
                if fixed_version is not None and int(v) >= fixed_version:
1!
188
                    # Bug was fixed in an earlier version, don't set the flag
189
                    continue
×
190
                if data[regressor].get(flag) in ("fixed", "verified"):
1✔
191
                    # regressor was uplifted, so the regression affects this branch
192
                    if is_latest_wontfix:
1!
193
                        continue
×
194
                    self.status_changes[bugid][flag] = "affected"
1✔
195
                    info["esr"][f"esr{v}"] = "affected"
1✔
196
                elif int(v) >= regressed_version:
1✔
197
                    # regression from before this branch, also affected
198
                    if is_latest_wontfix:
1!
199
                        continue
×
200
                    self.status_changes[bugid][flag] = "affected"
1✔
201
                    info["esr"][f"esr{v}"] = "affected"
1✔
202
                else:
203
                    self.status_changes[bugid][flag] = "unaffected"
1✔
204
                    info["esr"][f"esr{v}"] = "unaffected"
1✔
205
                filtered_bugs[bugid] = info
1✔
206

207
        for bugid in filtered_bugs:
1✔
208
            regressor = bugs[bugid]["regressed_by"]
1✔
209
            self.status_changes[bugid]["comment"] = {
1✔
210
                "body": f"{self.description()} {regressor}",
211
                # if the regressing bug is private (security or otherwise
212
                # confidential), don't leak its number through our comment (the
213
                # regressed_by field is not public in that case)
214
                "is_private": bool(data[regressor].get("groups")),
215
            }
216
            self.status_changes[bugid]["keywords"] = {"add": ["regression"]}
1✔
217

218
        return filtered_bugs
1✔
219

220
    def get_bugs(self, *args, **kwargs):
1✔
221
        bugs = super().get_bugs(*args, **kwargs)
1✔
222
        bugs = self.get_status_changes(bugs)
1✔
223
        return bugs
1✔
224

225
    def get_autofix_change(self):
1✔
226
        return self.status_changes
×
227

228
    def columns(self):
1✔
229
        return ["id", "summary", "regressed_by", "central", "beta", "release", "esr"]
×
230

231

232
if __name__ == "__main__":
1!
233
    RegressionSetStatusFlags().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