1 """Methods for reporting bugs."""
3 import subprocess
, sys
, os
5 __all__
= ['ReportFailure', 'BugReport', 'getReporters']
9 class ReportFailure(Exception):
10 """Generic exception for failures in bug reporting."""
11 def __init__(self
, value
):
14 # Collect information about a bug.
17 def __init__(self
, title
, description
, files
):
19 self
.description
= description
22 # Reporter interfaces.
26 import email
, mimetypes
, smtplib
27 from email
import encoders
28 from email
.message
import Message
29 from email
.mime
.base
import MIMEBase
30 from email
.mime
.multipart
import MIMEMultipart
31 from email
.mime
.text
import MIMEText
33 #===------------------------------------------------------------------------===#
35 #===------------------------------------------------------------------------===#
37 class ReporterParameter
:
38 def __init__(self
, n
):
42 def getValue(self
,r
,bugtype
,getConfigOption
):
43 return getConfigOption(r
.getName(),self
.getName())
44 def saveConfigValue(self
):
47 class TextParameter (ReporterParameter
):
48 def getHTML(self
,r
,bugtype
,getConfigOption
):
51 <td class="form_clabel">%s:</td>
52 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
53 </tr>"""%(self
.getName(),r
.getName(),self
.getName(),self
.getValue(r
,bugtype
,getConfigOption
))
55 class SelectionParameter (ReporterParameter
):
56 def __init__(self
, n
, values
):
57 ReporterParameter
.__init
__(self
,n
)
60 def getHTML(self
,r
,bugtype
,getConfigOption
):
61 default
= self
.getValue(r
,bugtype
,getConfigOption
)
64 <td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
66 </select></td>"""%(self
.getName(),r
.getName(),self
.getName(),'\n'.join(["""\
67 <option value="%s"%s>%s</option>"""%(o
[0],
68 o
[0] == default
and ' selected="selected"' or '',
69 o
[1]) for o
in self
.values
]))
71 #===------------------------------------------------------------------------===#
73 #===------------------------------------------------------------------------===#
79 def getParameters(self
):
80 return map(lambda x
:TextParameter(x
),['To', 'From', 'SMTP Server', 'SMTP Port'])
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':
95 # Note: we should handle calculating the charset
96 msg
= MIMEText(fp
.read(), _subtype
=subtype
)
100 msg
= MIMEBase(maintype
, subtype
)
101 msg
.set_payload(fp
.read())
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
))
109 def fileReport(self
, report
, parameters
):
115 """%(report
.title
, report
.description
)
117 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.')
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
129 msg
.attach(MIMEText(mainMsg
, _subtype
='text/plain'))
130 for file in report
.files
:
131 self
.attachFile(msg
, file)
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())
139 raise ReportFailure('Unable to send message via SMTP.')
141 return "Message sent!"
143 class BugzillaReporter
:
147 def getParameters(self
):
148 return map(lambda x
:TextParameter(x
),['URL','Product'])
150 def fileReport(self
, report
, parameters
):
151 raise NotImplementedError
154 class RadarClassificationParameter(SelectionParameter
):
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']])
161 def saveConfigValue(self
):
164 def getValue(self
,r
,bugtype
,getConfigOption
):
165 if bugtype
.find("leak") != -1:
167 elif bugtype
.find("dereference") != -1:
169 elif bugtype
.find("missing ivar release") != -1:
177 # FIXME: Find this .scpt better
178 path
= os
.path
.join(os
.path
.dirname(__file__
),'Resources/GetRadarVersion.scpt')
180 p
= subprocess
.Popen(['osascript',path
],
181 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
184 data
,err
= p
.communicate()
186 # FIXME: Check version? Check for no errors?
192 def getParameters(self
):
193 return [ TextParameter('Component'), TextParameter('Component Version'),
194 RadarClassificationParameter() ]
196 def fileReport(self
, report
, parameters
):
197 component
= parameters
.get('Component', '')
198 componentVersion
= parameters
.get('Component Version', '')
199 classification
= parameters
.get('Classification', '')
204 if not component
.strip():
205 component
= 'Bugs found by clang Analyzer'
206 if not componentVersion
.strip():
207 componentVersion
= 'X'
209 script
= os
.path
.join(os
.path
.dirname(__file__
),'Resources/FileRadar.scpt')
210 args
= ['osascript', script
, component
, componentVersion
, classification
, personID
, report
.title
,
211 report
.description
, diagnosis
, config
] + map(os
.path
.abspath
, report
.files
)
212 # print >>sys.stderr, args
214 p
= subprocess
.Popen(args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
216 raise ReportFailure("Unable to file radar (AppleScript failure).")
217 data
, err
= p
.communicate()
221 raise ReportFailure("Unable to file radar (AppleScript failure).")
226 raise ReportFailure("Unable to process radar results.")
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.")
232 bugID
,message
= values
236 raise ReportFailure(message
)
238 return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID
,bugID
)
244 if RadarReporter
.isAvailable():
245 reporters
.append(RadarReporter())
246 reporters
.append(EmailReporter())