blob: 9560fc1aabbb3d87d7c7471154b0ca3ed620113f [file] [log] [blame]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +00001"""Methods for reporting bugs."""
2
3import subprocess, sys, os
4
Daniel Dunbar61717b32008-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 Dunbar2b0662c2008-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 Kremeneka4a1f722008-09-30 16:08:13 +000033#===------------------------------------------------------------------------===#
34# ReporterParameter
35#===------------------------------------------------------------------------===#
36
37class ReporterParameter:
38 def __init__(self, n):
39 self.name = n
Ted Kremeneka4a1f722008-09-30 16:08:13 +000040 def getName(self):
41 return self.name
Ted Kremenekddabf802008-09-30 16:37:50 +000042 def getValue(self,r,bugtype,getConfigOption):
43 return getConfigOption(r.getName(),self.getName())
Ted Kremeneka4a1f722008-09-30 16:08:13 +000044 def saveConfigValue(self):
45 return True
46
47class TextParameter (ReporterParameter):
Ted Kremeneka4a1f722008-09-30 16:08:13 +000048 def getHTML(self,r,bugtype,getConfigOption):
49 return """\
50<tr>
Ted Kremenekddabf802008-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 Kremeneka4a1f722008-09-30 16:08:13 +000053</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
54
Ted Kremenekddabf802008-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 Kremenek2cc2d192008-09-30 17:12:32 +000061 default = self.getValue(r,bugtype,getConfigOption)
Ted Kremenekddabf802008-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 Kremenek2cc2d192008-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 Kremenekddabf802008-09-30 16:37:50 +000070
Ted Kremeneka4a1f722008-09-30 16:08:13 +000071#===------------------------------------------------------------------------===#
72# Reporters
73#===------------------------------------------------------------------------===#
74
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000075class EmailReporter:
76 def getName(self):
77 return 'Email'
78
Ted Kremeneka4a1f722008-09-30 16:08:13 +000079 def getParameters(self):
80 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbar2b0662c2008-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 Dunbar55c5aa42008-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 Dunbar2b0662c2008-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 Dunbar55c5aa42008-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 Dunbar2b0662c2008-09-19 23:32:11 +0000142
143class BugzillaReporter:
144 def getName(self):
145 return 'Bugzilla'
146
Ted Kremeneka4a1f722008-09-30 16:08:13 +0000147 def getParameters(self):
148 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000149
150 def fileReport(self, report, parameters):
151 raise NotImplementedError
Ted Kremenekf8169092008-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 Kremenek2cc2d192008-09-30 17:12:32 +0000163
164 def getValue(self,r,bugtype,getConfigOption):
Ted Kremenek0fb6ae92008-10-24 21:23:51 +0000165 if bugtype.find("leak") != -1:
Ted Kremenek2cc2d192008-09-30 17:12:32 +0000166 return '3'
Ted Kremenek5d144b52008-10-23 21:36:52 +0000167 elif bugtype.find("dereference") != -1:
168 return '2'
Ted Kremenek1ab8e722008-10-30 17:29:54 +0000169 elif bugtype.find("missing ivar release") != -1:
170 return '3'
Ted Kremenek2cc2d192008-09-30 17:12:32 +0000171 else:
172 return '7'
Ted Kremenekf8169092008-09-30 17:00:30 +0000173
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000174class RadarReporter:
175 @staticmethod
176 def isAvailable():
177 # FIXME: Find this .scpt better
178 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremenek5cfaf092008-09-30 05:45:59 +0000179 try:
180 p = subprocess.Popen(['osascript',path],
181 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
182 except:
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000183 return False
184 data,err = p.communicate()
185 res = p.wait()
186 # FIXME: Check version? Check for no errors?
187 return res == 0
188
189 def getName(self):
190 return 'Radar'
191
Ted Kremeneka4a1f722008-09-30 16:08:13 +0000192 def getParameters(self):
Ted Kremenekddabf802008-09-30 16:37:50 +0000193 return [ TextParameter('Component'), TextParameter('Component Version'),
Ted Kremenekf8169092008-09-30 17:00:30 +0000194 RadarClassificationParameter() ]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000195
196 def fileReport(self, report, parameters):
197 component = parameters.get('Component', '')
198 componentVersion = parameters.get('Component Version', '')
Ted Kremenekddabf802008-09-30 16:37:50 +0000199 classification = parameters.get('Classification', '')
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000200 personID = ""
201 diagnosis = ""
202 config = ""
203
204 if not component.strip():
205 component = 'Bugs found by clang Analyzer'
206 if not componentVersion.strip():
207 componentVersion = 'X'
208
209 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
Ted Kremenekddabf802008-09-30 16:37:50 +0000210 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000211 report.description, diagnosis, config] + map(os.path.abspath, report.files)
212# print >>sys.stderr, args
Ted Kremenekddabf802008-09-30 16:37:50 +0000213 try:
214 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
215 except:
Daniel Dunbar61717b32008-09-20 01:43:16 +0000216 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000217 data, err = p.communicate()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000218 res = p.wait()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000219
220 if res:
Daniel Dunbar61717b32008-09-20 01:43:16 +0000221 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000222
Daniel Dunbar61717b32008-09-20 01:43:16 +0000223 try:
224 values = eval(data)
225 except:
226 raise ReportFailure("Unable to process radar results.")
227
228 # We expect (int: bugID, str: message)
229 if len(values) != 2 or not isinstance(values[0], int):
230 raise ReportFailure("Unable to process radar results.")
231
232 bugID,message = values
233 bugID = int(bugID)
234
235 if not bugID:
236 raise ReportFailure(message)
237
238 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000239
240###
241
242def getReporters():
243 reporters = []
244 if RadarReporter.isAvailable():
245 reporters.append(RadarReporter())
Daniel Dunbar16588e82008-09-21 19:06:51 +0000246 reporters.append(EmailReporter())
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000247 return reporters
248