]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/scan-view/share/Reporter.py
Vendor import of clang trunk r351319 (just before the release_80 branch
[FreeBSD/FreeBSD.git] / tools / scan-view / share / Reporter.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """Methods for reporting bugs."""
5
6 import subprocess, sys, os
7
8 __all__ = ['ReportFailure', 'BugReport', 'getReporters']
9
10 #
11
12 class ReportFailure(Exception):
13     """Generic exception for failures in bug reporting."""
14     def __init__(self, value):        
15         self.value = value
16
17 # Collect information about a bug.
18
19 class BugReport(object):
20     def __init__(self, title, description, files):
21         self.title = title
22         self.description = description
23         self.files = files
24
25 # Reporter interfaces.
26
27 import os
28
29 import email, mimetypes, smtplib
30 from email import encoders
31 from email.message import Message
32 from email.mime.base import MIMEBase
33 from email.mime.multipart import MIMEMultipart
34 from email.mime.text import MIMEText
35
36 #===------------------------------------------------------------------------===#
37 # ReporterParameter
38 #===------------------------------------------------------------------------===#
39
40 class ReporterParameter(object):
41   def __init__(self, n):
42     self.name = n
43   def getName(self):
44     return self.name
45   def getValue(self,r,bugtype,getConfigOption):
46      return getConfigOption(r.getName(),self.getName())
47   def saveConfigValue(self):
48     return True
49
50 class TextParameter (ReporterParameter):
51   def getHTML(self,r,bugtype,getConfigOption):
52     return """\
53 <tr>
54 <td class="form_clabel">%s:</td>
55 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
56 </tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
57
58 class SelectionParameter (ReporterParameter):
59   def __init__(self, n, values):
60     ReporterParameter.__init__(self,n)
61     self.values = values
62     
63   def getHTML(self,r,bugtype,getConfigOption):
64     default = self.getValue(r,bugtype,getConfigOption)
65     return """\
66 <tr>
67 <td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
68 %s
69 </select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
70 <option value="%s"%s>%s</option>"""%(o[0],
71                                      o[0] == default and ' selected="selected"' or '',
72                                      o[1]) for o in self.values]))
73
74 #===------------------------------------------------------------------------===#
75 # Reporters
76 #===------------------------------------------------------------------------===#
77
78 class EmailReporter(object):
79     def getName(self):
80         return 'Email'
81
82     def getParameters(self):
83         return [TextParameter(x) for x in ['To', 'From', 'SMTP Server', 'SMTP Port']]
84
85     # Lifted from python email module examples.
86     def attachFile(self, outer, path):
87         # Guess the content type based on the file's extension.  Encoding
88         # will be ignored, although we should check for simple things like
89         # gzip'd or compressed files.
90         ctype, encoding = mimetypes.guess_type(path)
91         if ctype is None or encoding is not None:
92             # No guess could be made, or the file is encoded (compressed), so
93             # use a generic bag-of-bits type.
94             ctype = 'application/octet-stream'
95         maintype, subtype = ctype.split('/', 1)
96         if maintype == 'text':
97             fp = open(path)
98             # Note: we should handle calculating the charset
99             msg = MIMEText(fp.read(), _subtype=subtype)
100             fp.close()
101         else:
102             fp = open(path, 'rb')
103             msg = MIMEBase(maintype, subtype)
104             msg.set_payload(fp.read())
105             fp.close()
106             # Encode the payload using Base64
107             encoders.encode_base64(msg)
108         # Set the filename parameter
109         msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
110         outer.attach(msg)
111
112     def fileReport(self, report, parameters):
113         mainMsg = """\
114 BUG REPORT
115 ---
116 Title: %s
117 Description: %s
118 """%(report.title, report.description)
119
120         if not parameters.get('To'):
121             raise ReportFailure('No "To" address specified.')
122         if not parameters.get('From'):
123             raise ReportFailure('No "From" address specified.')
124
125         msg = MIMEMultipart()
126         msg['Subject'] = 'BUG REPORT: %s'%(report.title)
127         # FIXME: Get config parameters
128         msg['To'] = parameters.get('To')
129         msg['From'] = parameters.get('From')
130         msg.preamble = mainMsg
131
132         msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
133         for file in report.files:
134             self.attachFile(msg, file)
135
136         try:
137             s = smtplib.SMTP(host=parameters.get('SMTP Server'),
138                              port=parameters.get('SMTP Port'))
139             s.sendmail(msg['From'], msg['To'], msg.as_string())
140             s.close()
141         except:
142             raise ReportFailure('Unable to send message via SMTP.')
143
144         return "Message sent!"
145
146 class BugzillaReporter(object):
147     def getName(self):
148         return 'Bugzilla'
149     
150     def getParameters(self):
151         return [TextParameter(x) for x in ['URL','Product']]
152
153     def fileReport(self, report, parameters):
154         raise NotImplementedError
155  
156
157 class RadarClassificationParameter(SelectionParameter):
158   def __init__(self):
159     SelectionParameter.__init__(self,"Classification",
160             [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
161              ['3', 'Performance'], ['4', 'UI/Usability'], 
162              ['6', 'Serious Bug'], ['7', 'Other']])
163
164   def saveConfigValue(self):
165     return False
166     
167   def getValue(self,r,bugtype,getConfigOption):
168     if bugtype.find("leak") != -1:
169       return '3'
170     elif bugtype.find("dereference") != -1:
171       return '2'
172     elif bugtype.find("missing ivar release") != -1:
173       return '3'
174     else:
175       return '7'
176
177 class RadarReporter(object):
178     @staticmethod
179     def isAvailable():
180         # FIXME: Find this .scpt better
181         path = os.path.join(os.path.dirname(__file__),'../share/scan-view/GetRadarVersion.scpt')
182         try:
183           p = subprocess.Popen(['osascript',path], 
184           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
185         except:
186             return False
187         data,err = p.communicate()
188         res = p.wait()
189         # FIXME: Check version? Check for no errors?
190         return res == 0
191
192     def getName(self):
193         return 'Radar'
194
195     def getParameters(self):
196         return [ TextParameter('Component'), TextParameter('Component Version'),
197                  RadarClassificationParameter() ]
198
199     def fileReport(self, report, parameters):
200         component = parameters.get('Component', '')
201         componentVersion = parameters.get('Component Version', '')
202         classification = parameters.get('Classification', '')
203         personID = ""
204         diagnosis = ""
205         config = ""
206
207         if not component.strip():
208             component = 'Bugs found by clang Analyzer'
209         if not componentVersion.strip():
210             componentVersion = 'X'
211
212         script = os.path.join(os.path.dirname(__file__),'../share/scan-view/FileRadar.scpt')
213         args = ['osascript', script, component, componentVersion, classification, personID, report.title,
214                 report.description, diagnosis, config] + [os.path.abspath(f) for f in report.files]
215 #        print >>sys.stderr, args
216         try:
217           p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
218         except:
219             raise ReportFailure("Unable to file radar (AppleScript failure).")
220         data, err = p.communicate()
221         res = p.wait()
222
223         if res:
224             raise ReportFailure("Unable to file radar (AppleScript failure).")
225
226         try:
227             values = eval(data)
228         except:
229             raise ReportFailure("Unable to process radar results.")
230
231         # We expect (int: bugID, str: message)
232         if len(values) != 2 or not isinstance(values[0], int):
233             raise ReportFailure("Unable to process radar results.")
234
235         bugID,message = values
236         bugID = int(bugID)
237         
238         if not bugID:
239             raise ReportFailure(message)
240         
241         return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
242
243 ###
244
245 def getReporters():
246     reporters = []
247     if RadarReporter.isAvailable():
248         reporters.append(RadarReporter())
249     reporters.append(EmailReporter())
250     return reporters
251