forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			252 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Python
		
	
	
	
| #!/usr/bin/env python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| """Methods for reporting bugs."""
 | |
| 
 | |
| import subprocess, sys, os
 | |
| 
 | |
| __all__ = ['ReportFailure', 'BugReport', 'getReporters']
 | |
| 
 | |
| #
 | |
| 
 | |
| class ReportFailure(Exception):
 | |
|     """Generic exception for failures in bug reporting."""
 | |
|     def __init__(self, value):        
 | |
|         self.value = value
 | |
| 
 | |
| # Collect information about a bug.
 | |
| 
 | |
| class BugReport:
 | |
|     def __init__(self, title, description, files):
 | |
|         self.title = title
 | |
|         self.description = description
 | |
|         self.files = files
 | |
| 
 | |
| # Reporter interfaces.
 | |
| 
 | |
| import os
 | |
| 
 | |
| import email, mimetypes, smtplib
 | |
| from email import encoders
 | |
| from email.message import Message
 | |
| from email.mime.base import MIMEBase
 | |
| from email.mime.multipart import MIMEMultipart
 | |
| from email.mime.text import MIMEText
 | |
| 
 | |
| #===------------------------------------------------------------------------===#
 | |
| # ReporterParameter
 | |
| #===------------------------------------------------------------------------===#
 | |
| 
 | |
| class ReporterParameter:
 | |
|   def __init__(self, n):
 | |
|     self.name = n
 | |
|   def getName(self):
 | |
|     return self.name
 | |
|   def getValue(self,r,bugtype,getConfigOption):
 | |
|      return getConfigOption(r.getName(),self.getName())
 | |
|   def saveConfigValue(self):
 | |
|     return True
 | |
| 
 | |
| class TextParameter (ReporterParameter):
 | |
|   def getHTML(self,r,bugtype,getConfigOption):
 | |
|     return """\
 | |
| <tr>
 | |
| <td class="form_clabel">%s:</td>
 | |
| <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
 | |
| </tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
 | |
| 
 | |
| class SelectionParameter (ReporterParameter):
 | |
|   def __init__(self, n, values):
 | |
|     ReporterParameter.__init__(self,n)
 | |
|     self.values = values
 | |
|     
 | |
|   def getHTML(self,r,bugtype,getConfigOption):
 | |
|     default = self.getValue(r,bugtype,getConfigOption)
 | |
|     return """\
 | |
| <tr>
 | |
| <td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
 | |
| %s
 | |
| </select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
 | |
| <option value="%s"%s>%s</option>"""%(o[0],
 | |
|                                      o[0] == default and ' selected="selected"' or '',
 | |
|                                      o[1]) for o in self.values]))
 | |
| 
 | |
| #===------------------------------------------------------------------------===#
 | |
| # Reporters
 | |
| #===------------------------------------------------------------------------===#
 | |
| 
 | |
| class EmailReporter:
 | |
|     def getName(self):
 | |
|         return 'Email'
 | |
| 
 | |
|     def getParameters(self):
 | |
|         return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
 | |
| 
 | |
|     # Lifted from python email module examples.
 | |
|     def attachFile(self, outer, path):
 | |
|         # Guess the content type based on the file's extension.  Encoding
 | |
|         # will be ignored, although we should check for simple things like
 | |
|         # gzip'd or compressed files.
 | |
|         ctype, encoding = mimetypes.guess_type(path)
 | |
|         if ctype is None or encoding is not None:
 | |
|             # No guess could be made, or the file is encoded (compressed), so
 | |
|             # use a generic bag-of-bits type.
 | |
|             ctype = 'application/octet-stream'
 | |
|         maintype, subtype = ctype.split('/', 1)
 | |
|         if maintype == 'text':
 | |
|             fp = open(path)
 | |
|             # Note: we should handle calculating the charset
 | |
|             msg = MIMEText(fp.read(), _subtype=subtype)
 | |
|             fp.close()
 | |
|         else:
 | |
|             fp = open(path, 'rb')
 | |
|             msg = MIMEBase(maintype, subtype)
 | |
|             msg.set_payload(fp.read())
 | |
|             fp.close()
 | |
|             # Encode the payload using Base64
 | |
|             encoders.encode_base64(msg)
 | |
|         # Set the filename parameter
 | |
|         msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
 | |
|         outer.attach(msg)
 | |
| 
 | |
|     def fileReport(self, report, parameters):
 | |
|         mainMsg = """\
 | |
| BUG REPORT
 | |
| ---
 | |
| Title: %s
 | |
| Description: %s
 | |
| """%(report.title, report.description)
 | |
| 
 | |
|         if not parameters.get('To'):
 | |
|             raise ReportFailure('No "To" address specified.')
 | |
|         if not parameters.get('From'):
 | |
|             raise ReportFailure('No "From" address specified.')
 | |
| 
 | |
|         msg = MIMEMultipart()
 | |
|         msg['Subject'] = 'BUG REPORT: %s'%(report.title)
 | |
|         # FIXME: Get config parameters
 | |
|         msg['To'] = parameters.get('To')
 | |
|         msg['From'] = parameters.get('From')
 | |
|         msg.preamble = mainMsg
 | |
| 
 | |
|         msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
 | |
|         for file in report.files:
 | |
|             self.attachFile(msg, file)
 | |
| 
 | |
|         try:
 | |
|             s = smtplib.SMTP(host=parameters.get('SMTP Server'),
 | |
|                              port=parameters.get('SMTP Port'))
 | |
|             s.sendmail(msg['From'], msg['To'], msg.as_string())
 | |
|             s.close()
 | |
|         except:
 | |
|             raise ReportFailure('Unable to send message via SMTP.')
 | |
| 
 | |
|         return "Message sent!"
 | |
| 
 | |
| class BugzillaReporter:
 | |
|     def getName(self):
 | |
|         return 'Bugzilla'
 | |
|     
 | |
|     def getParameters(self):
 | |
|         return map(lambda x:TextParameter(x),['URL','Product'])
 | |
| 
 | |
|     def fileReport(self, report, parameters):
 | |
|         raise NotImplementedError
 | |
|  
 | |
| 
 | |
| class RadarClassificationParameter(SelectionParameter):
 | |
|   def __init__(self):
 | |
|     SelectionParameter.__init__(self,"Classification",
 | |
|             [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
 | |
|              ['3', 'Performance'], ['4', 'UI/Usability'], 
 | |
|              ['6', 'Serious Bug'], ['7', 'Other']])
 | |
| 
 | |
|   def saveConfigValue(self):
 | |
|     return False
 | |
|     
 | |
|   def getValue(self,r,bugtype,getConfigOption):
 | |
|     if bugtype.find("leak") != -1:
 | |
|       return '3'
 | |
|     elif bugtype.find("dereference") != -1:
 | |
|       return '2'
 | |
|     elif bugtype.find("missing ivar release") != -1:
 | |
|       return '3'
 | |
|     else:
 | |
|       return '7'
 | |
| 
 | |
| class RadarReporter:
 | |
|     @staticmethod
 | |
|     def isAvailable():
 | |
|         # FIXME: Find this .scpt better
 | |
|         path = os.path.join(os.path.dirname(__file__),'../share/scan-view/GetRadarVersion.scpt')
 | |
|         try:
 | |
|           p = subprocess.Popen(['osascript',path], 
 | |
|           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | |
|         except:
 | |
|             return False
 | |
|         data,err = p.communicate()
 | |
|         res = p.wait()
 | |
|         # FIXME: Check version? Check for no errors?
 | |
|         return res == 0
 | |
| 
 | |
|     def getName(self):
 | |
|         return 'Radar'
 | |
| 
 | |
|     def getParameters(self):
 | |
|         return [ TextParameter('Component'), TextParameter('Component Version'),
 | |
|                  RadarClassificationParameter() ]
 | |
| 
 | |
|     def fileReport(self, report, parameters):
 | |
|         component = parameters.get('Component', '')
 | |
|         componentVersion = parameters.get('Component Version', '')
 | |
|         classification = parameters.get('Classification', '')
 | |
|         personID = ""
 | |
|         diagnosis = ""
 | |
|         config = ""
 | |
| 
 | |
|         if not component.strip():
 | |
|             component = 'Bugs found by clang Analyzer'
 | |
|         if not componentVersion.strip():
 | |
|             componentVersion = 'X'
 | |
| 
 | |
|         script = os.path.join(os.path.dirname(__file__),'../share/scan-view/FileRadar.scpt')
 | |
|         args = ['osascript', script, component, componentVersion, classification, personID, report.title,
 | |
|                 report.description, diagnosis, config] + map(os.path.abspath, report.files)
 | |
| #        print >>sys.stderr, args
 | |
|         try:
 | |
|           p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | |
|         except:
 | |
|             raise ReportFailure("Unable to file radar (AppleScript failure).")
 | |
|         data, err = p.communicate()
 | |
|         res = p.wait()
 | |
| 
 | |
|         if res:
 | |
|             raise ReportFailure("Unable to file radar (AppleScript failure).")
 | |
| 
 | |
|         try:
 | |
|             values = eval(data)
 | |
|         except:
 | |
|             raise ReportFailure("Unable to process radar results.")
 | |
| 
 | |
|         # We expect (int: bugID, str: message)
 | |
|         if len(values) != 2 or not isinstance(values[0], int):
 | |
|             raise ReportFailure("Unable to process radar results.")
 | |
| 
 | |
|         bugID,message = values
 | |
|         bugID = int(bugID)
 | |
|         
 | |
|         if not bugID:
 | |
|             raise ReportFailure(message)
 | |
|         
 | |
|         return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
 | |
| 
 | |
| ###
 | |
| 
 | |
| def getReporters():
 | |
|     reporters = []
 | |
|     if RadarReporter.isAvailable():
 | |
|         reporters.append(RadarReporter())
 | |
|     reporters.append(EmailReporter())
 | |
|     return reporters
 | |
| 
 |