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