home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / python2.6 / dist-packages / apport / crashdb_impl / launchpad.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-10-12  |  30.1 KB  |  760 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Crash database implementation for Launchpad.
  5.  
  6. Copyright (C) 2007 Canonical Ltd.
  7. Author: Martin Pitt <martin.pitt@ubuntu.com>
  8.  
  9. This program is free software; you can redistribute it and/or modify it
  10. under the terms of the GNU General Public License as published by the
  11. Free Software Foundation; either version 2 of the License, or (at your
  12. option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
  13. the full text of the license.
  14. '''
  15. import urllib
  16. import tempfile
  17. import shutil
  18. import os.path as os
  19. import re
  20. import gzip
  21. import sys
  22. from cStringIO import StringIO
  23. import launchpadbugs.storeblob as launchpadbugs
  24. import launchpadbugs.connector as Connector
  25. import apport.crashdb as apport
  26. import apport
  27. Bug = Connector.ConnectBug()
  28. BugList = Connector.ConnectBugList()
  29.  
  30. def get_source_version(distro, package, hostname):
  31.     """Return the version of given source package in the latest release of
  32.     given distribution.
  33.  
  34.     If 'distro' is None, we will look for a launchpad project . 
  35.     """
  36.     if distro:
  37.         result = urllib.urlopen('https://%s/%s/+source/%s' % (hostname, distro, package)).read()
  38.         m = re.search('href="/%s/\\w+/\\+source/%s/([^"]+)"' % (distro, re.escape(package)), result)
  39.         if not m:
  40.             raise ValueError, 'source package %s does not exist in %s' % (package, distro)
  41.         m
  42.     else:
  43.         result = urllib.urlopen('https://%s/%s/+series' % (hostname, package)).read()
  44.         m = re.search('href="/%s/([^"]+)"' % re.escape(package), result)
  45.         if not m:
  46.             raise ValueError, 'Series for %s does not exist in Launchpad' % package
  47.         m
  48.     return m.group(1)
  49.  
  50.  
  51. class CrashDatabase(apport.crashdb.CrashDatabase):
  52.     '''Launchpad implementation of crash database interface.'''
  53.     
  54.     def __init__(self, cookie_file, bugpattern_baseurl, options):
  55.         '''Initialize Launchpad crash database connection. 
  56.         
  57.         You need to specify a Mozilla-style cookie file for download() and
  58.         update(). For upload() and get_comment_url() you can use None.'''
  59.         apport.crashdb.CrashDatabase.__init__(self, cookie_file, bugpattern_baseurl, options)
  60.         self.distro = options.get('distro')
  61.         self.arch_tag = 'need-%s-retrace' % apport.packaging.get_system_architecture()
  62.         self.options = options
  63.         self.cookie_file = cookie_file
  64.         if self.options.get('staging', False):
  65.             HTTPCONNECTION = HTTPCONNECTION
  66.             import launchpadbugs.lpconstants
  67.             Bug.set_connection_mode(HTTPCONNECTION.MODE.STAGING)
  68.             BugList.set_connection_mode(HTTPCONNECTION.MODE.STAGING)
  69.             self.hostname = 'staging.launchpad.net'
  70.         else:
  71.             self.hostname = 'launchpad.net'
  72.         if self.cookie_file:
  73.             Bug.authentication = self.cookie_file
  74.             BugList.authentication = self.cookie_file
  75.         
  76.  
  77.     
  78.     def upload(self, report, progress_callback = None):
  79.         '''Upload given problem report return a handle for it. 
  80.         
  81.         This should happen noninteractively. 
  82.         
  83.         If the implementation supports it, and a function progress_callback is
  84.         passed, that is called repeatedly with two arguments: the number of
  85.         bytes already sent, and the total number of bytes to send. This can be
  86.         used to provide a proper upload progress indication on frontends.'''
  87.         hdr = { }
  88.         hdr['Tags'] = 'apport-%s' % report['ProblemType'].lower()
  89.         if report.has_key('Tags'):
  90.             hdr['Tags'] += ' ' + report['Tags']
  91.         
  92.         a = report.get('PackageArchitecture')
  93.         if not a or a == 'all':
  94.             a = report.get('Architecture')
  95.         
  96.         if a:
  97.             hdr['Tags'] += ' ' + a
  98.         
  99.         if 'CoreDump' in report and a:
  100.             hdr['Tags'] += ' need-%s-retrace' % a
  101.             if report['DistroRelease'].split()[0] == 'Ubuntu':
  102.                 hdr['Private'] = 'yes'
  103.                 hdr['Subscribers'] = 'apport'
  104.             
  105.         elif report.has_key('Traceback'):
  106.             hdr['Tags'] += ' need-duplicate-check'
  107.             if report['DistroRelease'].split()[0] == 'Ubuntu':
  108.                 hdr['Private'] = 'yes'
  109.                 hdr['Subscribers'] = 'apport'
  110.             
  111.         
  112.         mime = tempfile.TemporaryFile()
  113.         report.write_mime(mime, extra_headers = hdr, skip_keys = [
  114.             'Date'])
  115.         mime.flush()
  116.         mime.seek(0)
  117.         ticket = launchpadbugs.storeblob.upload(mime, progress_callback, staging = self.options.get('staging', False))
  118.         if not ticket:
  119.             raise AssertionError
  120.         return ticket
  121.  
  122.     
  123.     def get_comment_url(self, report, handle):
  124.         '''Return an URL that should be opened after report has been uploaded
  125.         and upload() returned handle.
  126.  
  127.         Should return None if no URL should be opened (anonymous filing without
  128.         user comments); in that case this function should do whichever
  129.         interactive steps it wants to perform.'''
  130.         args = { }
  131.         title = report.standard_title()
  132.         if title:
  133.             args['field.title'] = title
  134.         
  135.         project = self.options.get('project')
  136.         if 'ThirdParty' in report:
  137.             project = report['SourcePackage']
  138.         
  139.         if not project:
  140.             if report.has_key('SourcePackage'):
  141.                 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (self.hostname, self.distro, report['SourcePackage'], handle, urllib.urlencode(args))
  142.             return 'https://bugs.%s/%s/+filebug/%s?%s' % (self.hostname, self.distro, handle, urllib.urlencode(args))
  143.         project
  144.         return 'https://bugs.%s/%s/+filebug/%s?%s' % (self.hostname, project, handle, urllib.urlencode(args))
  145.  
  146.     
  147.     def download(self, id):
  148.         '''Download the problem report from given ID and return a Report.'''
  149.         report = apport.Report()
  150.         attachment_path = tempfile.mkdtemp()
  151.         Bug.content_types.append('application/x-gzip')
  152.         
  153.         try:
  154.             b = Bug(id)
  155.             m = re.search('(ProblemType:.*)$', b.description_raw, re.S)
  156.             if not m:
  157.                 m = re.search('^--- \\r?$[\\r\\n]*(.*)', b.description_raw, re.M | re.S)
  158.             
  159.             if not m:
  160.                 raise AssertionError, 'bug description must contain standard apport format data'
  161.             description = m.group(1).replace('\xc2\xa0', ' ')
  162.             if '\r\n\r\n' in description:
  163.                 if 'Uname:' in description:
  164.                     (part1, part2) = description.split('Uname:', 1)
  165.                     description = part1.replace('\r\n\r\n', '\r\n') + 'Uname:' + part2.split('\r\n\r\n', 1)[0]
  166.                 else:
  167.                     description = description.replace('\r\n\r\n', '\r\n')
  168.             
  169.             report.load(StringIO(description))
  170.             report['Date'] = b.date.ctime()
  171.             if 'ProblemType' not in report:
  172.                 if 'apport-bug' in b.tags:
  173.                     report['ProblemType'] = 'Bug'
  174.                 elif 'apport-crash' in b.tags:
  175.                     report['ProblemType'] = 'Crash'
  176.                 elif 'apport-kernelcrash' in b.tags:
  177.                     report['ProblemType'] = 'KernelCrash'
  178.                 elif 'apport-package' in b.tags:
  179.                     report['ProblemType'] = 'Package'
  180.                 else:
  181.                     raise ValueError, 'cannot determine ProblemType from tags: ' + str(b.tags)
  182.             'apport-bug' in b.tags
  183.             for att in b.attachments.filter((lambda a: re.match('Dependencies.txt|CoreDump.gz|ProcMaps.txt|Traceback.txt|DpkgTerminalLog.txt', a.lp_filename))):
  184.                 key = os.path.splitext(att.lp_filename)[0]
  185.                 att.download(os.path.join(attachment_path, att.lp_filename))
  186.                 if att.lp_filename.endswith('.txt'):
  187.                     report[key] = att.text
  188.                     continue
  189.                 if att.lp_filename.endswith('.gz'):
  190.                     report[key] = gzip.GzipFile(fileobj = StringIO(att.text)).read()
  191.                     continue
  192.                 raise Exception, 'Unknown attachment type: ' + att.lp_filename
  193.             
  194.             return report
  195.         finally:
  196.             shutil.rmtree(attachment_path)
  197.  
  198.  
  199.     
  200.     def update(self, id, report, comment = ''):
  201.         '''Update the given report ID with the retraced results from the report
  202.         (Stacktrace, ThreadStacktrace, StacktraceTop; also Disassembly if
  203.         desired) and an optional comment.'''
  204.         bug = Bug(id)
  205.         comment += '\n\nStacktraceTop:' + report['StacktraceTop'].decode('utf-8', 'replace').encode('utf-8')
  206.         tmpdir = tempfile.mkdtemp()
  207.         t = { }
  208.         
  209.         try:
  210.             t[0] = open(os.path.join(tmpdir, 'Stacktrace.txt'), 'w+')
  211.             t[0].write(report['Stacktrace'])
  212.             t[0].flush()
  213.             t[0].seek(0)
  214.             att = Bug.NewAttachment(localfileobject = t[0], description = 'Stacktrace.txt (retraced)')
  215.             new_comment = Bug.NewComment(subject = 'Symbolic stack trace', text = comment, attachment = att)
  216.             bug.comments.add(new_comment)
  217.             t[1] = open(os.path.join(tmpdir, 'ThreadStacktrace.txt'), 'w+')
  218.             t[1].write(report['ThreadStacktrace'])
  219.             t[1].flush()
  220.             t[1].seek(0)
  221.             att = Bug.NewAttachment(localfileobject = t[1], description = 'ThreadStacktrace.txt (retraced)')
  222.             new_comment = Bug.NewComment(subject = 'Symbolic threaded stack trace', attachment = att)
  223.             bug.comments.add(new_comment)
  224.             if report.has_key('StacktraceSource'):
  225.                 t[2] = open(os.path.join(tmpdir, 'StacktraceSource.txt'), 'w+')
  226.                 t[2].write(report['StacktraceSource'])
  227.                 t[2].flush()
  228.                 t[2].seek(0)
  229.                 att = Bug.NewAttachment(localfileobject = t[2], description = 'StacktraceSource.txt')
  230.                 new_comment = Bug.NewComment(subject = 'Stack trace with source code', attachment = att)
  231.                 bug.comments.add(new_comment)
  232.             
  233.             if report.has_key('SourcePackage') and bug.sourcepackage == 'ubuntu':
  234.                 bug.set_sourcepackage(report['SourcePackage'])
  235.         finally:
  236.             shutil.rmtree(tmpdir)
  237.  
  238.         if report.has_useful_stacktrace():
  239.             bug.attachments.remove(func = (lambda a: if not a.lp_filename:
  240. passre.match('^CoreDump.gz$', a.description)))
  241.             bug.commit()
  242.             
  243.             try:
  244.                 bug.importance = 'Medium'
  245.                 bug.commit()
  246.             except IOError:
  247.                 pass
  248.             except:
  249.                 None<EXCEPTION MATCH>IOError
  250.             
  251.  
  252.         None<EXCEPTION MATCH>IOError
  253.         bug.commit()
  254.         for x in t.itervalues():
  255.             x.close()
  256.         
  257.         self._subscribe_triaging_team(bug, report)
  258.  
  259.     
  260.     def get_distro_release(self, id):
  261.         """Get 'DistroRelease: <release>' from the given report ID and return
  262.         it."""
  263.         bug = Bug(url = 'https://%s/bugs/%s' % (self.hostname, str(id)))
  264.         m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)
  265.         if m:
  266.             return m.group(1)
  267.         raise ValueError, 'URL does not contain DistroRelease: field'
  268.  
  269.     
  270.     def get_unretraced(self):
  271.         '''Return an ID set of all crashes which have not been retraced yet and
  272.         which happened on the current host architecture.'''
  273.         bugs = BugList('https://bugs.%s/ubuntu/+bugs?field.tag=%s' % (self.hostname, self.arch_tag))
  274.         return set((lambda .0: for i in .0:
  275. int(i))(bugs))
  276.  
  277.     
  278.     def get_dup_unchecked(self):
  279.         '''Return an ID set of all crashes which have not been checked for
  280.         being a duplicate.
  281.  
  282.         This is mainly useful for crashes of scripting languages such as
  283.         Python, since they do not need to be retraced. It should not return
  284.         bugs that are covered by get_unretraced().'''
  285.         bugs = BugList('https://bugs.%s/ubuntu/+bugs?field.tag=need-duplicate-check&batch=300' % self.hostname)
  286.         return set((lambda .0: for i in .0:
  287. int(i))(bugs))
  288.  
  289.     
  290.     def get_unfixed(self):
  291.         '''Return an ID set of all crashes which are not yet fixed.
  292.  
  293.         The list must not contain bugs which were rejected or duplicate.
  294.         
  295.         This function should make sure that the returned list is correct. If
  296.         there are any errors with connecting to the crash database, it should
  297.         raise an exception (preferably IOError).'''
  298.         bugs = BugList('https://bugs.%s/ubuntu/+bugs?field.tag=apport-crash&batch=300' % self.hostname)
  299.         return set((lambda .0: for i in .0:
  300. int(i))(bugs))
  301.  
  302.     
  303.     def get_fixed_version(self, id):
  304.         """Return the package version that fixes a given crash.
  305.  
  306.         Return None if the crash is not yet fixed, or an empty string if the
  307.         crash is fixed, but it cannot be determined by which version. Return
  308.         'invalid' if the crash report got invalidated, such as closed a
  309.         duplicate or rejected.
  310.  
  311.         This function should make sure that the returned result is correct. If
  312.         there are any errors with connecting to the crash database, it should
  313.         raise an exception (preferably IOError)."""
  314.         
  315.         try:
  316.             b = Bug(id)
  317.         except Bug.Error.LPUrlError:
  318.             e = None
  319.             if e.value.startswith('Page not found'):
  320.                 return 'invalid'
  321.             raise 
  322.         except:
  323.             e.value.startswith('Page not found')
  324.  
  325.         if b.status == 'Fix Released':
  326.             if b.sourcepackage:
  327.                 
  328.                 try:
  329.                     return get_source_version(self.distro, b.sourcepackage, self.hostname)
  330.                 except ValueError:
  331.                     e.value.startswith('Page not found')
  332.                     e.value.startswith('Page not found')
  333.                     return ''
  334.                 
  335.  
  336.             e.value.startswith('Page not found')<EXCEPTION MATCH>ValueError
  337.             return ''
  338.         if b.status == 'Invalid' or b.duplicate_of:
  339.             return 'invalid'
  340.  
  341.     
  342.     def duplicate_of(self, id):
  343.         '''Return master ID for a duplicate bug.
  344.  
  345.         If the bug is not a duplicate, return None.
  346.         '''
  347.         b = Bug(id)
  348.         return b.duplicate_of
  349.  
  350.     
  351.     def close_duplicate(self, id, master):
  352.         '''Mark a crash id as duplicate of given master ID.
  353.         
  354.         If master is None, id gets un-duplicated.
  355.         '''
  356.         bug = Bug(id)
  357.         if master:
  358.             m = Bug(master)
  359.             if m.duplicate_of:
  360.                 master = m.duplicate_of
  361.             
  362.             bug.attachments.remove(func = (lambda a: re.match('^(CoreDump.gz$|Stacktrace.txt|ThreadStacktrace.txt|Dependencies.txt$|ProcMaps.txt$|ProcStatus.txt$|Registers.txt$|Disassembly.txt$)', a.lp_filename)))
  363.             if bug.private:
  364.                 bug.private = None
  365.             
  366.             bug.commit()
  367.             bug = Bug(id)
  368.             bug.duplicate_of = int(master)
  369.         else:
  370.             bug.duplicate_of = None
  371.         bug.commit()
  372.  
  373.     
  374.     def mark_regression(self, id, master):
  375.         """Mark a crash id as reintroducing an earlier crash which is
  376.         already marked as fixed (having ID 'master')."""
  377.         bug = Bug(id)
  378.         comment = Bug.NewComment(subject = 'Possible regression detected', text = 'This crash has the same stack trace characteristics as bug #%i. However, the latter was already fixed in an earlier package version than the one in this report. This might be a regression or because the problem is in a dependent package.' % master)
  379.         bug.comments.add(comment)
  380.         bug.tags.append('regression-retracer')
  381.         bug.commit()
  382.  
  383.     
  384.     def mark_retraced(self, id):
  385.         '''Mark crash id as retraced.'''
  386.         b = Bug(id)
  387.         if self.arch_tag in b.tags:
  388.             b.tags.remove(self.arch_tag)
  389.         
  390.         b.commit()
  391.  
  392.     
  393.     def mark_retrace_failed(self, id, invalid_msg = None):
  394.         """Mark crash id as 'failed to retrace'."""
  395.         b = Bug(id)
  396.         if invalid_msg:
  397.             comment = Bug.NewComment(subject = 'Crash report cannot be processed', text = invalid_msg)
  398.             b.comments.add(comment)
  399.             b.status = 'Invalid'
  400.             b.attachments.remove(func = (lambda a: re.match('^(CoreDump.gz$|Stacktrace.txt|ThreadStacktrace.txt|Dependencies.txt$|ProcMaps.txt$|ProcStatus.txt$|Registers.txt$|Disassembly.txt$)', a.lp_filename)))
  401.         elif 'apport-failed-retrace' not in b.tags:
  402.             b.tags.append('apport-failed-retrace')
  403.         
  404.         b.commit()
  405.  
  406.     
  407.     def _mark_dup_checked(self, id, report):
  408.         '''Mark crash id as checked for being a duplicate.'''
  409.         b = Bug(id)
  410.         if 'need-duplicate-check' in b.tags:
  411.             b.tags.remove('need-duplicate-check')
  412.         
  413.         self._subscribe_triaging_team(b, report)
  414.         b.commit()
  415.  
  416.     
  417.     def _subscribe_triaging_team(self, bug, report):
  418.         '''Subscribe the right triaging team to the bug.'''
  419.         if report['DistroRelease'].split()[0] != 'Ubuntu':
  420.             return None
  421.         
  422.         try:
  423.             bug.subscriptions.add('ubuntu-crashes-universe')
  424.         except ValueError:
  425.             report['DistroRelease'].split()[0] != 'Ubuntu'
  426.             report['DistroRelease'].split()[0] != 'Ubuntu'
  427.         except:
  428.             report['DistroRelease'].split()[0] != 'Ubuntu'
  429.  
  430.  
  431.  
  432. if __name__ == '__main__':
  433.     import unittest
  434.     import urllib2
  435.     import cookielib
  436.     crashdb = None
  437.     segv_report = None
  438.     python_report = None
  439.     
  440.     class _Tests(unittest.TestCase):
  441.         test_package = 'coreutils'
  442.         test_srcpackage = 'coreutils'
  443.         known_test_id = 89040
  444.         known_test_id2 = 302779
  445.         
  446.         def setUp(self):
  447.             global crashdb
  448.             if not crashdb:
  449.                 crashdb = self._get_instance()
  450.             
  451.             self.crashdb = crashdb
  452.             self.ref_report = apport.Report()
  453.             self.ref_report.add_os_info()
  454.             self.ref_report.add_user_info()
  455.  
  456.         
  457.         def _file_segv_report(self):
  458.             '''File a SEGV crash report.
  459.  
  460.             Return crash ID.
  461.             '''
  462.             r = apport.report._ApportReportTest._generate_sigsegv_report()
  463.             r.add_package_info(self.test_package)
  464.             r.add_os_info()
  465.             r.add_gdb_info()
  466.             r.add_user_info()
  467.             self.assertEqual(r.standard_title(), 'crash crashed with SIGSEGV in f()')
  468.             handle = self.crashdb.upload(r)
  469.             self.assert_(handle)
  470.             url = self.crashdb.get_comment_url(r, handle)
  471.             self.assert_(url)
  472.             id = self._fill_bug_form(url)
  473.             self.assert_(id > 0)
  474.             return id
  475.  
  476.         
  477.         def test_1_report_segv(self):
  478.             '''upload() and get_comment_url() for SEGV crash
  479.             
  480.             This needs to run first, since it sets segv_report.
  481.             '''
  482.             global segv_report
  483.             id = self._file_segv_report()
  484.             segv_report = id
  485.             print >>sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
  486.  
  487.         
  488.         def test_1_report_python(self):
  489.             '''upload() and get_comment_url() for Python crash
  490.             
  491.             This needs to run early, since it sets python_report.
  492.             '''
  493.             global python_report
  494.             r = apport.Report('Crash')
  495.             r['ExecutablePath'] = '/bin/foo'
  496.             r['Traceback'] = 'Traceback (most recent call last):\n  File "/bin/foo", line 67, in fuzz\n    print weird\nNameError: global name \'weird\' is not defined'
  497.             r.add_package_info(self.test_package)
  498.             r.add_os_info()
  499.             r.add_user_info()
  500.             self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')
  501.             handle = self.crashdb.upload(r)
  502.             self.assert_(handle)
  503.             url = self.crashdb.get_comment_url(r, handle)
  504.             self.assert_(url)
  505.             id = self._fill_bug_form(url)
  506.             self.assert_(id > 0)
  507.             python_report = id
  508.             print >>sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
  509.  
  510.         
  511.         def test_2_download(self):
  512.             '''download()'''
  513.             r = self.crashdb.download(segv_report)
  514.             self.assertEqual(r['ProblemType'], 'Crash')
  515.             self.assertEqual(r['DistroRelease'], self.ref_report['DistroRelease'])
  516.             self.assertEqual(r['Architecture'], self.ref_report['Architecture'])
  517.             self.assertEqual(r['Uname'], self.ref_report['Uname'])
  518.             self.assertEqual(r.get('NonfreeKernelModules'), self.ref_report.get('NonfreeKernelModules'))
  519.             self.assertEqual(r.get('UserGroups'), self.ref_report.get('UserGroups'))
  520.             self.assertEqual(r['Signal'], '11')
  521.             self.assert_(r['ExecutablePath'].endswith('/crash'))
  522.             self.assertEqual(r['SourcePackage'], self.test_srcpackage)
  523.             self.assert_(r['Package'].startswith(self.test_package + ' '))
  524.             self.assert_('f (x=42)' in r['Stacktrace'])
  525.             self.assert_('f (x=42)' in r['StacktraceTop'])
  526.             self.assert_(len(r['CoreDump']) > 1000)
  527.             self.assert_('Dependencies' in r)
  528.  
  529.         
  530.         def test_3_update(self):
  531.             '''update()'''
  532.             r = self.crashdb.download(segv_report)
  533.             r['StacktraceTop'] = '?? ()'
  534.             r['Stacktrace'] = 'long\ntrace'
  535.             r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
  536.             self.crashdb.update(segv_report, r, 'I can has a better retrace?')
  537.             r = self.crashdb.download(segv_report)
  538.             self.assert_('CoreDump' in r)
  539.             r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
  540.             r['Stacktrace'] = 'long\ntrace'
  541.             r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
  542.             self.crashdb.update(segv_report, r, 'good retrace!')
  543.             r = self.crashdb.download(segv_report)
  544.             self.failIf('CoreDump' in r)
  545.  
  546.         
  547.         def test_get_distro_release(self):
  548.             '''get_distro_release()'''
  549.             self.assertEqual(self.crashdb.get_distro_release(segv_report), self.ref_report['DistroRelease'])
  550.  
  551.         
  552.         def test_duplicates(self):
  553.             '''duplicate handling'''
  554.             self.assertEqual(self.crashdb.duplicate_of(segv_report), None)
  555.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
  556.             self.crashdb.close_duplicate(segv_report, self.known_test_id)
  557.             self.assertEqual(self.crashdb.duplicate_of(segv_report), self.known_test_id)
  558.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), 'invalid')
  559.             self.crashdb.close_duplicate(segv_report, None)
  560.             self.assertEqual(self.crashdb.duplicate_of(segv_report), None)
  561.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
  562.             r = self.crashdb.download(segv_report)
  563.             self.failIf('CoreDump' in r)
  564.             self.crashdb.close_duplicate(self.known_test_id, self.known_test_id2)
  565.             self.crashdb.close_duplicate(segv_report, self.known_test_id)
  566.             self.assertEqual(self.crashdb.duplicate_of(segv_report), self.known_test_id2)
  567.             self.crashdb.close_duplicate(self.known_test_id, None)
  568.             self.crashdb.close_duplicate(self.known_test_id2, None)
  569.             self.crashdb.close_duplicate(segv_report, None)
  570.  
  571.         
  572.         def test_marking_segv(self):
  573.             '''processing status markings for signal crashes'''
  574.             unretraced_before = self.crashdb.get_unretraced()
  575.             self.assert_(segv_report in unretraced_before)
  576.             self.failIf(python_report in unretraced_before)
  577.             self.crashdb.mark_retraced(segv_report)
  578.             unretraced_after = self.crashdb.get_unretraced()
  579.             self.failIf(segv_report in unretraced_after)
  580.             self.assertEqual(unretraced_before, unretraced_after.union(set([
  581.                 segv_report])))
  582.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
  583.             self._mark_needs_retrace(segv_report)
  584.             self.crashdb.mark_retraced(segv_report)
  585.             self.crashdb.mark_retrace_failed(segv_report)
  586.             unretraced_after = self.crashdb.get_unretraced()
  587.             self.failIf(segv_report in unretraced_after)
  588.             self.assertEqual(unretraced_before, unretraced_after.union(set([
  589.                 segv_report])))
  590.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), None)
  591.             self._mark_needs_retrace(segv_report)
  592.             self.crashdb.mark_retraced(segv_report)
  593.             self.crashdb.mark_retrace_failed(segv_report, "I don't like you")
  594.             unretraced_after = self.crashdb.get_unretraced()
  595.             self.failIf(segv_report in unretraced_after)
  596.             self.assertEqual(unretraced_before, unretraced_after.union(set([
  597.                 segv_report])))
  598.             self.assertEqual(self.crashdb.get_fixed_version(segv_report), 'invalid')
  599.  
  600.         
  601.         def test_marking_python(self):
  602.             '''processing status markings for interpreter crashes'''
  603.             unchecked_before = self.crashdb.get_dup_unchecked()
  604.             self.assert_(python_report in unchecked_before)
  605.             self.failIf(segv_report in unchecked_before)
  606.             self.crashdb._mark_dup_checked(python_report, self.ref_report)
  607.             unchecked_after = self.crashdb.get_dup_unchecked()
  608.             self.failIf(python_report in unchecked_after)
  609.             self.assertEqual(unchecked_before, unchecked_after.union(set([
  610.                 python_report])))
  611.             self.assertEqual(self.crashdb.get_fixed_version(python_report), None)
  612.  
  613.         
  614.         def test_update_invalid(self):
  615.             '''updating a invalid crash
  616.             
  617.             This simulates a race condition where a crash being processed gets
  618.             invalidated by marking it as a duplicate.
  619.             '''
  620.             id = self._file_segv_report()
  621.             print >>sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
  622.             r = self.crashdb.download(id)
  623.             self.crashdb.close_duplicate(id, segv_report)
  624.             r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
  625.             r['Stacktrace'] = 'long\ntrace'
  626.             r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
  627.             self.crashdb.update(id, r, 'good retrace!')
  628.             r = self.crashdb.download(id)
  629.             self.failIf('CoreDump' in r)
  630.  
  631.         
  632.         def _get_instance(klass):
  633.             '''Create a CrashDB instance'''
  634.             return CrashDatabase(os.path.expanduser('~/.lpcookie.txt'), '', {
  635.                 'distro': 'ubuntu',
  636.                 'staging': True })
  637.  
  638.         _get_instance = classmethod(_get_instance)
  639.         
  640.         def _fill_bug_form(self, url):
  641.             '''Fill form for a distro bug and commit the bug.
  642.  
  643.             Return the report ID.
  644.             '''
  645.             cj = cookielib.MozillaCookieJar()
  646.             cj.load(self.crashdb.cookie_file)
  647.             opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
  648.             re_pkg = re.compile('<input type="text" value="([^"]+)" id="field.packagename"')
  649.             re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
  650.             re_tags = re.compile('<input.*id="field.tags".*value="([^"]+)"')
  651.             url = url.replace('+filebug/', '+filebug-advanced/')
  652.             res = opener.open(url)
  653.             self.assertEqual(res.getcode(), 200)
  654.             content = res.read()
  655.             m_pkg = re_pkg.search(content)
  656.             m_title = re_title.search(content)
  657.             m_tags = re_tags.search(content)
  658.             url = url.split('?')[0]
  659.             args = {
  660.                 'packagename_option': 'choose',
  661.                 'field.packagename': m_pkg.group(1),
  662.                 'field.title': m_title.group(1),
  663.                 'field.tags': m_tags.group(1),
  664.                 'field.comment': 'ZOMG!',
  665.                 'field.actions.submit_bug': '1' }
  666.             res = opener.open(url, data = urllib.urlencode(args))
  667.             self.assertEqual(res.getcode(), 200)
  668.             self.assert_('+source/%s/+bug/' % m_pkg.group(1) in res.geturl())
  669.             id = res.geturl().split('/')[-1]
  670.             return int(id)
  671.  
  672.         
  673.         def _fill_bug_form_project(self, url):
  674.             '''Fill form for a project bug and commit the bug.
  675.  
  676.             Return the report ID.
  677.             '''
  678.             cj = cookielib.MozillaCookieJar()
  679.             cj.load(self.crashdb.cookie_file)
  680.             opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
  681.             m = re.search('launchpad.net/([^/]+)/\\+filebug', url)
  682.             if not m:
  683.                 raise AssertionError
  684.             project = m.group(1)
  685.             re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
  686.             re_tags = re.compile('<input.*id="field.tags".*value="([^"]+)"')
  687.             url = url.replace('+filebug/', '+filebug-advanced/')
  688.             res = opener.open(url)
  689.             self.assertEqual(res.getcode(), 200)
  690.             content = res.read()
  691.             m_title = re_title.search(content)
  692.             m_tags = re_tags.search(content)
  693.             url = url.split('?')[0]
  694.             args = {
  695.                 'field.title': m_title.group(1),
  696.                 'field.tags': m_tags.group(1),
  697.                 'field.comment': 'ZOMG!',
  698.                 'field.actions.submit_bug': '1' }
  699.             res = opener.open(url, data = urllib.urlencode(args))
  700.             self.assertEqual(res.getcode(), 200)
  701.             self.assert_('launchpad.net/%s/+bug' % project in res.geturl())
  702.             id = res.geturl().split('/')[-1]
  703.             return int(id)
  704.  
  705.         
  706.         def _mark_needs_retrace(self, id):
  707.             '''Mark a report ID as needing retrace.'''
  708.             b = Bug(id)
  709.             if self.crashdb.arch_tag not in b.tags:
  710.                 b.tags.append(self.crashdb.arch_tag)
  711.             
  712.             b.commit()
  713.  
  714.         
  715.         def _mark_needs_dupcheck(self, id):
  716.             '''Mark a report ID as needing duplicate check.'''
  717.             b = Bug(id)
  718.             if 'need-duplicate-check' not in b.tags:
  719.                 b.tags.append('need-duplicate-check')
  720.             
  721.             b.commit()
  722.  
  723.         
  724.         def test_project(self):
  725.             '''reporting crashes against a project instead of a distro'''
  726.             crashdb = CrashDatabase(os.path.expanduser('~/.lpcookie.txt'), '', {
  727.                 'project': 'langpack-o-matic',
  728.                 'staging': True })
  729.             self.assertEqual(crashdb.distro, None)
  730.             r = apport.Report('Crash')
  731.             r['ExecutablePath'] = '/bin/foo'
  732.             r['Traceback'] = 'Traceback (most recent call last):\n  File "/bin/foo", line 67, in fuzz\n    print weird\nNameError: global name \'weird\' is not defined'
  733.             r.add_os_info()
  734.             r.add_user_info()
  735.             self.assertEqual(r.standard_title(), 'foo crashed with NameError in fuzz()')
  736.             handle = crashdb.upload(r)
  737.             self.assert_(handle)
  738.             url = crashdb.get_comment_url(r, handle)
  739.             self.assert_('launchpad.net/langpack-o-matic/+filebug' in url)
  740.             id = self._fill_bug_form_project(url)
  741.             self.assert_(id > 0)
  742.             print >>sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
  743.             r = crashdb.download(id)
  744.             r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
  745.             r['Stacktrace'] = 'long\ntrace'
  746.             r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
  747.             crashdb.update(id, r, 'good retrace!')
  748.             r = crashdb.download(id)
  749.             self.assertEqual(crashdb.get_fixed_version(id), None)
  750.             crashdb.close_duplicate(id, self.known_test_id)
  751.             self.assertEqual(crashdb.duplicate_of(id), self.known_test_id)
  752.             self.assertEqual(crashdb.get_fixed_version(id), 'invalid')
  753.             crashdb.close_duplicate(id, None)
  754.             self.assertEqual(crashdb.duplicate_of(id), None)
  755.             self.assertEqual(crashdb.get_fixed_version(id), None)
  756.  
  757.  
  758.     unittest.main()
  759.  
  760.