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