blob: b951cc029b6effa9e286c6c07c80fecca6f5cb95 [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):
61 return """\
62<tr>
63<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
64%s
65</select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
66<option value="%s">%s</option>"""%(o[0],o[1]) for o in self.values]))
67
Ted Kremenek19c88202008-09-30 16:08:13 +000068#===------------------------------------------------------------------------===#
69# Reporters
70#===------------------------------------------------------------------------===#
71
Daniel Dunbare33d3682008-09-19 23:32:11 +000072class EmailReporter:
73 def getName(self):
74 return 'Email'
75
Ted Kremenek19c88202008-09-30 16:08:13 +000076 def getParameters(self):
77 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbare33d3682008-09-19 23:32:11 +000078
79 # Lifted from python email module examples.
80 def attachFile(self, outer, path):
81 # Guess the content type based on the file's extension. Encoding
82 # will be ignored, although we should check for simple things like
83 # gzip'd or compressed files.
84 ctype, encoding = mimetypes.guess_type(path)
85 if ctype is None or encoding is not None:
86 # No guess could be made, or the file is encoded (compressed), so
87 # use a generic bag-of-bits type.
88 ctype = 'application/octet-stream'
89 maintype, subtype = ctype.split('/', 1)
90 if maintype == 'text':
91 fp = open(path)
92 # Note: we should handle calculating the charset
93 msg = MIMEText(fp.read(), _subtype=subtype)
94 fp.close()
95 else:
96 fp = open(path, 'rb')
97 msg = MIMEBase(maintype, subtype)
98 msg.set_payload(fp.read())
99 fp.close()
100 # Encode the payload using Base64
101 encoders.encode_base64(msg)
102 # Set the filename parameter
103 msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
104 outer.attach(msg)
105
106 def fileReport(self, report, parameters):
107 mainMsg = """\
108BUG REPORT
109---
110Title: %s
111Description: %s
112"""%(report.title, report.description)
113
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000114 if not parameters.get('To'):
115 raise ReportFailure('No "To" address specified.')
116 if not parameters.get('From'):
117 raise ReportFailure('No "From" address specified.')
Daniel Dunbare33d3682008-09-19 23:32:11 +0000118
119 msg = MIMEMultipart()
120 msg['Subject'] = 'BUG REPORT: %s'%(report.title)
121 # FIXME: Get config parameters
122 msg['To'] = parameters.get('To')
123 msg['From'] = parameters.get('From')
124 msg.preamble = mainMsg
125
126 msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
127 for file in report.files:
128 self.attachFile(msg, file)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000129
130 try:
131 s = smtplib.SMTP(host=parameters.get('SMTP Server'),
132 port=parameters.get('SMTP Port'))
133 s.sendmail(msg['From'], msg['To'], msg.as_string())
134 s.close()
135 except:
136 raise ReportFailure('Unable to send message via SMTP.')
137
138 return "Message sent!"
Daniel Dunbare33d3682008-09-19 23:32:11 +0000139
140class BugzillaReporter:
141 def getName(self):
142 return 'Bugzilla'
143
Ted Kremenek19c88202008-09-30 16:08:13 +0000144 def getParameters(self):
145 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbare33d3682008-09-19 23:32:11 +0000146
147 def fileReport(self, report, parameters):
148 raise NotImplementedError
Ted Kremenek8ce622d2008-09-30 17:00:30 +0000149
150
151class RadarClassificationParameter(SelectionParameter):
152 def __init__(self):
153 SelectionParameter.__init__(self,"Classification",
154 [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
155 ['3', 'Performance'], ['4', 'UI/Usability'],
156 ['6', 'Serious Bug'], ['7', 'Other']])
157
158 def saveConfigValue(self):
159 return False
160
Daniel Dunbare33d3682008-09-19 23:32:11 +0000161class RadarReporter:
162 @staticmethod
163 def isAvailable():
164 # FIXME: Find this .scpt better
165 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremenek13c03d82008-09-30 05:45:59 +0000166 try:
167 p = subprocess.Popen(['osascript',path],
168 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
169 except:
Daniel Dunbare33d3682008-09-19 23:32:11 +0000170 return False
171 data,err = p.communicate()
172 res = p.wait()
173 # FIXME: Check version? Check for no errors?
174 return res == 0
175
176 def getName(self):
177 return 'Radar'
178
Ted Kremenek19c88202008-09-30 16:08:13 +0000179 def getParameters(self):
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000180 return [ TextParameter('Component'), TextParameter('Component Version'),
Ted Kremenek8ce622d2008-09-30 17:00:30 +0000181 RadarClassificationParameter() ]
Daniel Dunbare33d3682008-09-19 23:32:11 +0000182
183 def fileReport(self, report, parameters):
184 component = parameters.get('Component', '')
185 componentVersion = parameters.get('Component Version', '')
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000186 classification = parameters.get('Classification', '')
Daniel Dunbare33d3682008-09-19 23:32:11 +0000187 personID = ""
188 diagnosis = ""
189 config = ""
190
191 if not component.strip():
192 component = 'Bugs found by clang Analyzer'
193 if not componentVersion.strip():
194 componentVersion = 'X'
195
196 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000197 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
Daniel Dunbare33d3682008-09-19 23:32:11 +0000198 report.description, diagnosis, config] + map(os.path.abspath, report.files)
199# print >>sys.stderr, args
Ted Kremenekfe6fa072008-09-30 16:37:50 +0000200 try:
201 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
202 except:
Daniel Dunbar54722492008-09-20 01:43:16 +0000203 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000204 data, err = p.communicate()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000205 res = p.wait()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000206
207 if res:
Daniel Dunbar54722492008-09-20 01:43:16 +0000208 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000209
Daniel Dunbar54722492008-09-20 01:43:16 +0000210 try:
211 values = eval(data)
212 except:
213 raise ReportFailure("Unable to process radar results.")
214
215 # We expect (int: bugID, str: message)
216 if len(values) != 2 or not isinstance(values[0], int):
217 raise ReportFailure("Unable to process radar results.")
218
219 bugID,message = values
220 bugID = int(bugID)
221
222 if not bugID:
223 raise ReportFailure(message)
224
225 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000226
227###
228
229def getReporters():
230 reporters = []
231 if RadarReporter.isAvailable():
232 reporters.append(RadarReporter())
Daniel Dunbarfeee21a2008-09-21 19:06:51 +0000233 reporters.append(EmailReporter())
Daniel Dunbare33d3682008-09-19 23:32:11 +0000234 return reporters
235