2 # -*- coding: utf-8 -*-
4 """Methods for reporting bugs."""
6 import subprocess, sys, os
8 __all__ = ['ReportFailure', 'BugReport', 'getReporters']
12 class ReportFailure(Exception):
13 """Generic exception for failures in bug reporting."""
14 def __init__(self, value):
17 # Collect information about a bug.
19 class BugReport(object):
20 def __init__(self, title, description, files):
22 self.description = description
25 # Reporter interfaces.
29 import email, mimetypes, smtplib
30 from email import encoders
31 from email.message import Message
32 from email.mime.base import MIMEBase
33 from email.mime.multipart import MIMEMultipart
34 from email.mime.text import MIMEText
36 #===------------------------------------------------------------------------===#
38 #===------------------------------------------------------------------------===#
40 class ReporterParameter(object):
41 def __init__(self, n):
45 def getValue(self,r,bugtype,getConfigOption):
46 return getConfigOption(r.getName(),self.getName())
47 def saveConfigValue(self):
50 class TextParameter (ReporterParameter):
51 def getHTML(self,r,bugtype,getConfigOption):
54 <td class="form_clabel">%s:</td>
55 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
56 </tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
58 class SelectionParameter (ReporterParameter):
59 def __init__(self, n, values):
60 ReporterParameter.__init__(self,n)
63 def getHTML(self,r,bugtype,getConfigOption):
64 default = self.getValue(r,bugtype,getConfigOption)
67 <td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
69 </select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
70 <option value="%s"%s>%s</option>"""%(o[0],
71 o[0] == default and ' selected="selected"' or '',
72 o[1]) for o in self.values]))
74 #===------------------------------------------------------------------------===#
76 #===------------------------------------------------------------------------===#
78 class EmailReporter(object):
82 def getParameters(self):
83 return [TextParameter(x) for x in ['To', 'From', 'SMTP Server', 'SMTP Port']]
85 # Lifted from python email module examples.
86 def attachFile(self, outer, path):
87 # Guess the content type based on the file's extension. Encoding
88 # will be ignored, although we should check for simple things like
89 # gzip'd or compressed files.
90 ctype, encoding = mimetypes.guess_type(path)
91 if ctype is None or encoding is not None:
92 # No guess could be made, or the file is encoded (compressed), so
93 # use a generic bag-of-bits type.
94 ctype = 'application/octet-stream'
95 maintype, subtype = ctype.split('/', 1)
96 if maintype == 'text':
98 # Note: we should handle calculating the charset
99 msg = MIMEText(fp.read(), _subtype=subtype)
102 fp = open(path, 'rb')
103 msg = MIMEBase(maintype, subtype)
104 msg.set_payload(fp.read())
106 # Encode the payload using Base64
107 encoders.encode_base64(msg)
108 # Set the filename parameter
109 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
112 def fileReport(self, report, parameters):
118 """%(report.title, report.description)
120 if not parameters.get('To'):
121 raise ReportFailure('No "To" address specified.')
122 if not parameters.get('From'):
123 raise ReportFailure('No "From" address specified.')
125 msg = MIMEMultipart()
126 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
127 # FIXME: Get config parameters
128 msg['To'] = parameters.get('To')
129 msg['From'] = parameters.get('From')
130 msg.preamble = mainMsg
132 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
133 for file in report.files:
134 self.attachFile(msg, file)
137 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
138 port=parameters.get('SMTP Port'))
139 s.sendmail(msg['From'], msg['To'], msg.as_string())
142 raise ReportFailure('Unable to send message via SMTP.')
144 return "Message sent!"
146 class BugzillaReporter(object):
150 def getParameters(self):
151 return [TextParameter(x) for x in ['URL','Product']]
153 def fileReport(self, report, parameters):
154 raise NotImplementedError
157 class RadarClassificationParameter(SelectionParameter):
159 SelectionParameter.__init__(self,"Classification",
160 [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
161 ['3', 'Performance'], ['4', 'UI/Usability'],
162 ['6', 'Serious Bug'], ['7', 'Other']])
164 def saveConfigValue(self):
167 def getValue(self,r,bugtype,getConfigOption):
168 if bugtype.find("leak") != -1:
170 elif bugtype.find("dereference") != -1:
172 elif bugtype.find("missing ivar release") != -1:
177 class RadarReporter(object):
180 # FIXME: Find this .scpt better
181 path = os.path.join(os.path.dirname(__file__),'../share/scan-view/GetRadarVersion.scpt')
183 p = subprocess.Popen(['osascript',path],
184 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
187 data,err = p.communicate()
189 # FIXME: Check version? Check for no errors?
195 def getParameters(self):
196 return [ TextParameter('Component'), TextParameter('Component Version'),
197 RadarClassificationParameter() ]
199 def fileReport(self, report, parameters):
200 component = parameters.get('Component', '')
201 componentVersion = parameters.get('Component Version', '')
202 classification = parameters.get('Classification', '')
207 if not component.strip():
208 component = 'Bugs found by clang Analyzer'
209 if not componentVersion.strip():
210 componentVersion = 'X'
212 script = os.path.join(os.path.dirname(__file__),'../share/scan-view/FileRadar.scpt')
213 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
214 report.description, diagnosis, config] + [os.path.abspath(f) for f in report.files]
215 # print >>sys.stderr, args
217 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
219 raise ReportFailure("Unable to file radar (AppleScript failure).")
220 data, err = p.communicate()
224 raise ReportFailure("Unable to file radar (AppleScript failure).")
229 raise ReportFailure("Unable to process radar results.")
231 # We expect (int: bugID, str: message)
232 if len(values) != 2 or not isinstance(values[0], int):
233 raise ReportFailure("Unable to process radar results.")
235 bugID,message = values
239 raise ReportFailure(message)
241 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
247 if RadarReporter.isAvailable():
248 reporters.append(RadarReporter())
249 reporters.append(EmailReporter())