blob: 41af16a49bfaa3189c2e0842792548022e2b3afc [file] [log] [blame]
Daniel Dunbare33d3682008-09-19 23:32:11 +00001"""Methods for reporting bugs."""
2
3import subprocess, sys, os
4
Daniel Dunbar54722492008-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 Dunbare33d3682008-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 Kremenek19c88202008-09-30 16:08:13 +000033#===------------------------------------------------------------------------===#
34# ReporterParameter
35#===------------------------------------------------------------------------===#
36
37class ReporterParameter:
38 def __init__(self, n):
39 self.name = n
Ted Kremenek19c88202008-09-30 16:08:13 +000040 def getName(self):
41 return self.name
Ted Kremenekfe6fa072008-09-30 16:37:50 +000042 def getValue(self,r,bugtype,getConfigOption):
43 return getConfigOption(r.getName(),self.getName())
Ted Kremenek19c88202008-09-30 16:08:13 +000044 def saveConfigValue(self):
45 return True
46
47class TextParameter (ReporterParameter):
Ted Kremenek19c88202008-09-30 16:08:13 +000048 def getHTML(self,r,bugtype,getConfigOption):
49 return """\
50<tr>
Ted Kremenekfe6fa072008-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 Kremenek19c88202008-09-30 16:08:13 +000053</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
54
Ted Kremenekfe6fa072008-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 Kremenekba78cc02008-09-30 17:12:32 +000061 default = self.getValue(r,bugtype,getConfigOption)
Ted Kremenekfe6fa072008-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 Kremenekba78cc02008-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 Kremenekfe6fa072008-09-30 16:37:50 +000070
Ted Kremenek19c88202008-09-30 16:08:13 +000071#===------------------------------------------------------------------------===#
72# Reporters
73#===------------------------------------------------------------------------===#
74
Daniel Dunbare33d3682008-09-19 23:32:11 +000075class EmailReporter:
76 def getName(self):
77 return 'Email'
78
Ted Kremenek19c88202008-09-30 16:08:13 +000079 def getParameters(self):
80 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbare33d3682008-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 Dunbar47cceff2008-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 Dunbare33d3682008-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 Dunbar47cceff2008-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 Dunbare33d3682008-09-19 23:32:11 +0000142
143class BugzillaReporter:
144 def getName(self):
145 return 'Bugzilla'
146
Ted Kremenek19c88202008-09-30 16:08:13 +0000147 def getParameters(self):
148 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbare33d3682008-09-19 23:32:11 +0000149
150 def fileReport(self, report, parameters):
151 raise NotImplementedError
Ted Kremenek8ce622d2008-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 Kremenekba78cc02008-09-30 17:12:32 +0000163
164 def getValue(self,r,bugtype,getConfigOption):
Ted Kremenek03373df2008-10-24 21:23:51 +0000165 if bugtype.find("leak") != -1:
Ted Kremenekba78cc02008-09-30 17:12:32 +0000166 return '3'
Ted Kremenek7ab94472008-10-23 21:36:52 +0000167 elif bugtype.find("dereference") != -1:
168 return '2'
Ted Kremenekba78cc02008-09-30 17:12:32 +0000169 else:
170 return '7'
Ted Kremenek8ce622d2008-09-30 17:00:30 +0000171
Daniel Dunbare33d3682008-09-19 23:32:11 +0000172class RadarReporter:
173 @staticmethod
174 def isAvailable():
175 # FIXME: Find this .scpt better
176 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremenek13c03d82008-09-30 05:45:59 +0000177 try:
178 p = subprocess.Popen(['osascript',path],
179 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
180 except:
Daniel Dunbare33d3682008-09-19 23:32:11 +0000181 return False
182 data,err = p.communicate()
183 res = p.wait()
184 # FIXME: Check version? Check for no errors?
185 return res == 0
186
187 def getName(self):
188 return 'Radar'
189
Ted Kremenek19c88202008-09-30 16:08:13 +0000190 def getParameters(self):
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000191 return [ TextParameter('Component'), TextParameter('Component Version'),
Ted Kremenek8ce622d2008-09-30 17:00:30 +0000192 RadarClassificationParameter() ]
Daniel Dunbare33d3682008-09-19 23:32:11 +0000193
194 def fileReport(self, report, parameters):
195 component = parameters.get('Component', '')
196 componentVersion = parameters.get('Component Version', '')
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000197 classification = parameters.get('Classification', '')
Daniel Dunbare33d3682008-09-19 23:32:11 +0000198 personID = ""
199 diagnosis = ""
200 config = ""
201
202 if not component.strip():
203 component = 'Bugs found by clang Analyzer'
204 if not componentVersion.strip():
205 componentVersion = 'X'
206
207 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000208 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
Daniel Dunbare33d3682008-09-19 23:32:11 +0000209 report.description, diagnosis, config] + map(os.path.abspath, report.files)
210# print >>sys.stderr, args
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000211 try:
212 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
213 except:
Daniel Dunbar54722492008-09-20 01:43:16 +0000214 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000215 data, err = p.communicate()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000216 res = p.wait()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000217
218 if res:
Daniel Dunbar54722492008-09-20 01:43:16 +0000219 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000220
Daniel Dunbar54722492008-09-20 01:43:16 +0000221 try:
222 values = eval(data)
223 except:
224 raise ReportFailure("Unable to process radar results.")
225
226 # We expect (int: bugID, str: message)
227 if len(values) != 2 or not isinstance(values[0], int):
228 raise ReportFailure("Unable to process radar results.")
229
230 bugID,message = values
231 bugID = int(bugID)
232
233 if not bugID:
234 raise ReportFailure(message)
235
236 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000237
238###
239
240def getReporters():
241 reporters = []
242 if RadarReporter.isAvailable():
243 reporters.append(RadarReporter())
Daniel Dunbarfeee21a2008-09-21 19:06:51 +0000244 reporters.append(EmailReporter())
Daniel Dunbare33d3682008-09-19 23:32:11 +0000245 return reporters
246