closedbugs.py 6.99 KB
Newer Older
Jonny Lamb's avatar
Jonny Lamb committed
1
2
3
4
# -*- coding: utf-8 -*-
#
#   closedbugs.py — closedbugs plugin
#
Arno Töll's avatar
Arno Töll committed
5
#   This file is part of debexpo - https://alioth.debian.org/projects/debexpo/
Jonny Lamb's avatar
Jonny Lamb committed
6
#
Jonny Lamb's avatar
Jonny Lamb committed
7
#   Copyright © 2008 Jonny Lamb <jonny@debian.org>
8
#               2011 Arno Töll <debian@toell.net>
Jonny Lamb's avatar
Jonny Lamb committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#
#   Permission is hereby granted, free of charge, to any person
#   obtaining a copy of this software and associated documentation
#   files (the "Software"), to deal in the Software without
#   restriction, including without limitation the rights to use,
#   copy, modify, merge, publish, distribute, sublicense, and/or sell
#   copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following
#   conditions:
#
#   The above copyright notice and this permission notice shall be
#   included in all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
#   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
#   OTHER DEALINGS IN THE SOFTWARE.

"""
Holds the closedbugs plugin.
"""

35
36
__author__ = 'Arno Töll'
__copyright__ = 'Copyright © 2011 Arno Töll'
Jonny Lamb's avatar
Jonny Lamb committed
37
38
__license__ = 'MIT'

39
from collections import defaultdict
Jonny Lamb's avatar
Jonny Lamb committed
40
41
import logging

42
43
import SOAPpy

Jonny Lamb's avatar
Jonny Lamb committed
44
from debexpo.lib import constants
45
from debexpo.plugins.api import *
46
from debexpo.model import meta
47

Jonny Lamb's avatar
Jonny Lamb committed
48
49
50

log = logging.getLogger(__name__)

51
@test_result
52
class ClosedbugsTest(PluginResult):
53
54
55
56
    """
    Represents the result of the 'closed bugs' test.
    """

57
58
59
    nb_closed = int_field('nb_closed')
    nb_errors = int_field('nb_errors')
    closes_wnpp = bool_field('wnpp')
60

61
62
63
64
65
66
67
68
69
    def get_bugs(self):
        q = meta.session.query(ClosedBug)
        q = q.filter_by(package_version_id = self.package_version.id)
        # exists = True)
        d = {}
        for bug in q.all():
            d.setdefault(bug.package, list()).append(bug)
        return d

70
    def __str__(self):
71
72
        if (self.closes_wnpp and self.nb_closed == 1 and
            self.nb_errors == 0):
73
74
75
76
77
78
79
80
            return 'Package closes a WNPP bug'

        strings = []
        strings.append('closes {} bug{plural}'.format(
            self.nb_closed if self.nb_closed else 'no',
            plural='s' if self.nb_closed > 1 else ''))

        if self.nb_errors:
81
            strings.append('wrongfully claims to be closing {} bug{plural}'.format(
82
83
                self.nb_errors,
                plural='s' if self.nb_closed > 1 else ''))
84
        return 'Package ' + ' and '.join(strings)
85
86
87
88
89
90

class ClosedBug(PluginResult):
    """
    Represents a bug closed by the package.
    """

91
92
93
    exists = bool_field('exists')
    belongs = bool_field('exists')
    number = int_field('number')
94
    is_error = bool_field('is_error')
95

96
97
98
    @property
    def package(self):
        return self['package']
99
100
101
102
103

    @property
    def is_wnpp(self):
        return self.package == 'wnpp'

104
    def __str__(self):
105
        if self.exists:
106
107
108
            string = '{package} ({severity}: {subject}'.format(
                package=self['package'],
                severity=self['severity'],
109
110
                subject=self['subject'])
        else:
111
            string = 'Bug #{} does not exist for'.format(self.number)
112
113

        return string
114
115
116


class ClosedBugsPlugin(QAPlugin):
117
118
    URL = "http://bugs.debian.org/cgi-bin/soap.cgi"
    NS = "Debbugs/SOAP"
Jonny Lamb's avatar
Jonny Lamb committed
119

120
    @importercmd
Jonny Lamb's avatar
Jonny Lamb committed
121
122
123
124
125
    def test_closed_bugs(self):
        """
        Check to make sure the bugs closed belong to the package.
        """

126
        if 'Closes' not in self.changes:
127
128
129
            log.debug('Package does not close any bugs')
            return

130
131
        log.debug('Checking whether the bugs closed in the package belongs'
                  ' to the package')
Jonny Lamb's avatar
Jonny Lamb committed
132

133
        bugs = [int(x) for x in self.changes['Closes'].split()]
Jonny Lamb's avatar
Jonny Lamb committed
134

135
136
137
138
139
140
141
142
        test_result = self.new_test_result(nb_closed=0,
                                           nb_errors=0,
                                           wnpp='false')

        if not bugs:
            log.debug('Package does not close any bugs')
            return

143
        if bugs:
144
145
            log.debug('Creating SOAP proxy to bugs.debian.org')
            try:
146
                server = SOAPpy.SOAPProxy(self.URL, self.NS, simplify_objects = 1)
147
148
                bugs_retrieved = server.get_status(*bugs)

149
            except Exception as e:
150
151
                log.critical('An error occurred when creating the SOAP proxy at "%s"'
                            ' (ns: "%s"): %s' % (self.URL, self.NS, e))
152
                return
153
154
155
156
157
158
159
160
161
162
163
164

            if 'item' in bugs_retrieved:
                bugs_retrieved = bugs_retrieved['item']
            else:
                bugs_retrieved = []
            # Force argument to be a list, SOAPpy returns a
            # dictionary instead of a dictionary list if only one
            # bug was found
            if not isinstance(bugs_retrieved, list):
                bugs_retrieved = [bugs_retrieved]

            raw_bugs = {}
165
166
167

            # Index bugs retrieved
            for bug in bugs_retrieved:
168
                if 'key' in bug and 'value' in bug:
169
                    raw_bugs[int(bug['key'])] = bug['value']
170
                else:
Jonny Lamb's avatar
Jonny Lamb committed
171
172
                    continue

173
            severity = constants.PLUGIN_SEVERITY_INFO
174

175
            for bug in bugs:
176
177
                bug_result = self.new_result(ClosedBug, number=bug)
                if not bug in raw_bugs:
178
                    log.debug('{} does not exist'.format(bug))
179
180
                    bug_result.exists = False
                    bug_result.belongs = False
181
182
                    bug_result.is_error = True
                    test_result.nb_errors += 1
183
                    test_result['severity'] = max(severity, constants.PLUGIN_SEVERITY_ERROR)
184
                    continue
Jonny Lamb's avatar
Jonny Lamb committed
185

186
                bug_result.exists = True
187
                log.debug('Found bug {}'.format(bug))
188

189
                package = raw_bugs[bug]['package']
190
                bug_result['package'] = package
191
192
                bug_result['subject'] = raw_bugs[bug]['subject']
                bug_result['severity'] = raw_bugs[bug]['severity']
193

194
195
                source = raw_bugs[bug]['source'].split(', ')

196

197
198
199
200
201
                if package == 'wnpp':
                    log.debug('Package closes a wnpp bug')
                    test_result.nb_closed += 1
                    test_result.closes_wnpp = True
                    bug_result.belongs = True
Jonny Lamb's avatar
Jonny Lamb committed
202

203
                elif self.changes['Source'] in source:
204
205
                    test_result.nb_closed += 1
                    bug_result.belongs = True
206

207
                else:
208
                    bug_result.belongs = False
209
210
211
                    test_result.nb_errors += 1
                    test_result['severity'] = max(severity,
                                                  constants.PLUGIN_SEVERITY_ERROR)
Jonny Lamb's avatar
Jonny Lamb committed
212
213
214


plugin = ClosedBugsPlugin
215
models = [
216
    ClosedbugsTest,
217
218
    ClosedBug,
    ]