blob: 2f05fdfd837157a5d7dea617cd28289a12f2908b [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):
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 Kremeneka4a1f722008-09-30 16:08:13 +000068#===------------------------------------------------------------------------===#
69# Reporters
70#===------------------------------------------------------------------------===#
71
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000072class EmailReporter:
73 def getName(self):
74 return 'Email'
75
Ted Kremeneka4a1f722008-09-30 16:08:13 +000076 def getParameters(self):
77 return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
Daniel Dunbar2b0662c2008-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 Dunbar55c5aa42008-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 Dunbar2b0662c2008-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 Dunbar55c5aa42008-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 Dunbar2b0662c2008-09-19 23:32:11 +0000139
140class BugzillaReporter:
141 def getName(self):
142 return 'Bugzilla'
143
Ted Kremeneka4a1f722008-09-30 16:08:13 +0000144 def getParameters(self):
145 return map(lambda x:TextParameter(x),['URL','Product'])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000146
147 def fileReport(self, report, parameters):
148 raise NotImplementedError
149
150class RadarReporter:
151 @staticmethod
152 def isAvailable():
153 # FIXME: Find this .scpt better
154 path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
Ted Kremenek5cfaf092008-09-30 05:45:59 +0000155 try:
156 p = subprocess.Popen(['osascript',path],
157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 except:
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000159 return False
160 data,err = p.communicate()
161 res = p.wait()
162 # FIXME: Check version? Check for no errors?
163 return res == 0
164
165 def getName(self):
166 return 'Radar'
167
Ted Kremeneka4a1f722008-09-30 16:08:13 +0000168 def getParameters(self):
Ted Kremenekddabf802008-09-30 16:37:50 +0000169 return [ TextParameter('Component'), TextParameter('Component Version'),
170 SelectionParameter('Classification',
171 [ ['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
172 ['3', 'Performance'], ['4', 'UI/Usability'],
173 ['6', 'Serious Bug'], ['7', 'Other'] ]) ]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000174
175 def fileReport(self, report, parameters):
176 component = parameters.get('Component', '')
177 componentVersion = parameters.get('Component Version', '')
Ted Kremenekddabf802008-09-30 16:37:50 +0000178 classification = parameters.get('Classification', '')
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000179 personID = ""
180 diagnosis = ""
181 config = ""
182
183 if not component.strip():
184 component = 'Bugs found by clang Analyzer'
185 if not componentVersion.strip():
186 componentVersion = 'X'
187
188 script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
Ted Kremenekddabf802008-09-30 16:37:50 +0000189 args = ['osascript', script, component, componentVersion, classification, personID, report.title,
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000190 report.description, diagnosis, config] + map(os.path.abspath, report.files)
191# print >>sys.stderr, args
Ted Kremenekddabf802008-09-30 16:37:50 +0000192 try:
193 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
194 except:
Daniel Dunbar61717b32008-09-20 01:43:16 +0000195 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000196 data, err = p.communicate()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000197 res = p.wait()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000198
199 if res:
Daniel Dunbar61717b32008-09-20 01:43:16 +0000200 raise ReportFailure("Unable to file radar (AppleScript failure).")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000201
Daniel Dunbar61717b32008-09-20 01:43:16 +0000202 try:
203 values = eval(data)
204 except:
205 raise ReportFailure("Unable to process radar results.")
206
207 # We expect (int: bugID, str: message)
208 if len(values) != 2 or not isinstance(values[0], int):
209 raise ReportFailure("Unable to process radar results.")
210
211 bugID,message = values
212 bugID = int(bugID)
213
214 if not bugID:
215 raise ReportFailure(message)
216
217 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000218
219###
220
221def getReporters():
222 reporters = []
223 if RadarReporter.isAvailable():
224 reporters.append(RadarReporter())
Daniel Dunbar16588e82008-09-21 19:06:51 +0000225 reporters.append(EmailReporter())
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000226 return reporters
227