blob: d2c107b62e1ce5e301c64df7fc6f90dc4a4dffd3 [file] [log] [blame]
Daniel Dunbard4c23372008-09-19 23:32:11 +00001"""Methods for reporting bugs."""
2
3import subprocess, sys, os
4
Daniel Dunbar025b48d2008-09-20 01:43:16 +00005__all__ = ['ReportFailure', 'BugReport', 'getReporters']
6
7#
8
9class ReportFailure(Exception):
10 """Generic exception for failures in bug reporting."""
11 def __init__(self, value):
12 self.value = value
Daniel Dunbard4c23372008-09-19 23:32:11 +000013
14# Collect information about a bug.
15
16class BugReport:
17 def __init__(self, title, description, files):
18 self.title = title
19 self.description = description
20 self.files = files
21
22# Reporter interfaces.
23
24import os
25
26import email, mimetypes, smtplib
27from email import encoders
28from email.message import Message
29from email.mime.base import MIMEBase
30from email.mime.multipart import MIMEMultipart
31from email.mime.text import MIMEText
32
Ted Kremenek094ff0b2008-09-30 16:08:13 +000033#===------------------------------------------------------------------------===#
34# ReporterParameter
35#===------------------------------------------------------------------------===#
36
37class ReporterParameter:
38 def __init__(self, n):
39 self.name = n
Ted Kremenek094ff0b2008-09-30 16:08:13 +000040 def getName(self):
41 return self.name
Ted Kremenekb1662992008-09-30 16:37:50 +000042 def getValue(self,r,bugtype,getConfigOption):
43 return getConfigOption(r.getName(),self.getName())
Ted Kremenek094ff0b2008-09-30 16:08:13 +000044 def saveConfigValue(self):
45 return True
46
47class TextParameter (ReporterParameter):
Ted Kremenek094ff0b2008-09-30 16:08:13 +000048 def getHTML(self,r,bugtype,getConfigOption):
49 return """\
50<tr>
Ted Kremenekb1662992008-09-30 16:37:50 +000051<td class="form_clabel">%s:</td>
52<td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
Ted Kremenek094ff0b2008-09-30 16:08:13 +000053</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
54
Ted Kremenekb1662992008-09-30 16:37:50 +000055class SelectionParameter (ReporterParameter):
56 def __init__(self, n, values):
57 ReporterParameter.__init__(self,n)
58 self.values = values
59
60 def getHTML(self,r,bugtype,getConfigOption):
Ted Kremenek9f403162008-09-30 17:12:32 +000061 default = self.getValue(r,bugtype,getConfigOption)
Ted Kremenekb1662992008-09-30 16:37:50 +000062 return """\
63<tr>
64<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
65%s
66</select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
Ted Kremenek9f403162008-09-30 17:12:32 +000067<option value="%s"%s>%s</option>"""%(o[0],
68 o[0] == default and ' selected="selected"' or '',
69 o[1]) for o in self.values]))
Ted Kremenekb1662992008-09-30 16:37:50 +000070
Ted Kremenek094ff0b2008-09-30 16:08:13 +000071#===------------------------------------------------------------------------===#
72# Reporters
73#===------------------------------------------------------------------------===#
74
Daniel Dunbard4c23372008-09-19 23:32:11 +000075class EmailReporter:
76 def getName(self):
77 return 'Email'
78
Ted Kremenek094ff0b2008-09-30 16:08:13 +000079 def getParameters(self):
80 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbard4c23372008-09-19 23:32:11 +000081
82 # Lifted from python email module examples.
83 def attachFile(self, outer, path):
84 # Guess the content type based on the file's extension. Encoding
85 # will be ignored, although we should check for simple things like
86 # gzip'd or compressed files.
87 ctype, encoding = mimetypes.guess_type(path)
88 if ctype is None or encoding is not None:
89 # No guess could be made, or the file is encoded (compressed), so
90 # use a generic bag-of-bits type.
91 ctype = 'application/octet-stream'
92 maintype, subtype = ctype.split('/', 1)
93 if maintype == 'text':
94 fp = open(path)
95 # Note: we should handle calculating the charset
96 msg = MIMEText(fp.read(), _subtype=subtype)
97 fp.close()
98 else:
99 fp = open(path, 'rb')
100 msg = MIMEBase(maintype, subtype)
101 msg.set_payload(fp.read())
102 fp.close()
103 # Encode the payload using Base64
104 encoders.encode_base64(msg)
105 # Set the filename parameter
106 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
107 outer.attach(msg)
108
109 def fileReport(self, report, parameters):
110 mainMsg = """\
111BUG REPORT
112---
113Title: %s
114Description: %s
115"""%(report.title, report.description)
116
Daniel Dunbar553fdc62008-09-22 01:21:30 +0000117 if not parameters.get('To'):
118 raise ReportFailure('No "To" address specified.')
119 if not parameters.get('From'):
120 raise ReportFailure('No "From" address specified.')
Daniel Dunbard4c23372008-09-19 23:32:11 +0000121
122 msg = MIMEMultipart()
123 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
124 # FIXME: Get config parameters
125 msg['To'] = parameters.get('To')
126 msg['From'] = parameters.get('From')
127 msg.preamble = mainMsg
128
129 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
130 for file in report.files:
131 self.attachFile(msg, file)
Daniel Dunbar553fdc62008-09-22 01:21:30 +0000132
133 try:
134 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
135 port=parameters.get('SMTP Port'))
136 s.sendmail(msg['From'], msg['To'], msg.as_string())
137 s.close()
138 except:
139 raise ReportFailure('Unable to send message via SMTP.')
140
141 return "Message sent!"
Daniel Dunbard4c23372008-09-19 23:32:11 +0000142
143class BugzillaReporter:
144 def getName(self):
145 return 'Bugzilla'
146
Ted Kremenek094ff0b2008-09-30 16:08:13 +0000147 def getParameters(self):
148 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbard4c23372008-09-19 23:32:11 +0000149
150 def fileReport(self, report, parameters):
151 raise NotImplementedError
Ted Kremenekfb0fef92008-09-30 17:00:30 +0000152
153
154class RadarClassificationParameter(SelectionParameter):
155 def __init__(self):
156 SelectionParameter.__init__(self,"Classification",
157 [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
158 ['3', 'Performance'], ['4', 'UI/Usability'],
159 ['6', 'Serious Bug'], ['7', 'Other']])
160
161 def saveConfigValue(self):
162 return False
Ted Kremenek9f403162008-09-30 17:12:32 +0000163
164 def getValue(self,r,bugtype,getConfigOption):
165 if bugtype.startswith("leak"):
166 return '3'
167 else:
168 return '7'
Ted Kremenekfb0fef92008-09-30 17:00:30 +0000169
Daniel Dunbard4c23372008-09-19 23:32:11 +0000170class RadarReporter:
171 @staticmethod
172 def isAvailable():
173 # FIXME: Find this .scpt better
174 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremeneka930b562008-09-30 05:45:59 +0000175 try:
176 p = subprocess.Popen(['osascript',path],
177 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
178 except:
Daniel Dunbard4c23372008-09-19 23:32:11 +0000179 return False
180 data,err = p.communicate()
181 res = p.wait()
182 # FIXME: Check version? Check for no errors?
183 return res == 0
184
185 def getName(self):
186 return 'Radar'
187
Ted Kremenek094ff0b2008-09-30 16:08:13 +0000188 def getParameters(self):
Ted Kremenekb1662992008-09-30 16:37:50 +0000189 return [ TextParameter('Component'), TextParameter('Component Version'),
Ted Kremenekfb0fef92008-09-30 17:00:30 +0000190 RadarClassificationParameter() ]
Daniel Dunbard4c23372008-09-19 23:32:11 +0000191
192 def fileReport(self, report, parameters):
193 component = parameters.get('Component', '')
194 componentVersion = parameters.get('Component Version', '')
Ted Kremenekb1662992008-09-30 16:37:50 +0000195 classification = parameters.get('Classification', '')
Daniel Dunbard4c23372008-09-19 23:32:11 +0000196 personID = ""
197 diagnosis = ""
198 config = ""
199
200 if not component.strip():
201 component = 'Bugs found by clang Analyzer'
202 if not componentVersion.strip():
203 componentVersion = 'X'
204
205 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
Ted Kremenekb1662992008-09-30 16:37:50 +0000206 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
Daniel Dunbard4c23372008-09-19 23:32:11 +0000207 report.description, diagnosis, config] + map(os.path.abspath, report.files)
208# print >>sys.stderr, args
Ted Kremenekb1662992008-09-30 16:37:50 +0000209 try:
210 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
211 except:
Daniel Dunbar025b48d2008-09-20 01:43:16 +0000212 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbard4c23372008-09-19 23:32:11 +0000213 data, err = p.communicate()
Daniel Dunbard4c23372008-09-19 23:32:11 +0000214 res = p.wait()
Daniel Dunbard4c23372008-09-19 23:32:11 +0000215
216 if res:
Daniel Dunbar025b48d2008-09-20 01:43:16 +0000217 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbard4c23372008-09-19 23:32:11 +0000218
Daniel Dunbar025b48d2008-09-20 01:43:16 +0000219 try:
220 values = eval(data)
221 except:
222 raise ReportFailure("Unable to process radar results.")
223
224 # We expect (int: bugID, str: message)
225 if len(values) != 2 or not isinstance(values[0], int):
226 raise ReportFailure("Unable to process radar results.")
227
228 bugID,message = values
229 bugID = int(bugID)
230
231 if not bugID:
232 raise ReportFailure(message)
233
234 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbard4c23372008-09-19 23:32:11 +0000235
236###
237
238def getReporters():
239 reporters = []
240 if RadarReporter.isAvailable():
241 reporters.append(RadarReporter())
Daniel Dunbara00b7a82008-09-21 19:06:51 +0000242 reporters.append(EmailReporter())
Daniel Dunbard4c23372008-09-19 23:32:11 +0000243 return reporters
244