← Back to team overview

apport-hackers team mailing list archive

[Merge] lp:~matsubara/apport/no-more-screenscraping into lp:apport

 

Diogo Matsubara has proposed merging lp:~matsubara/apport/no-more-screenscraping into lp:apport.

Requested reviews:
  Apport upstream developers (apport-hackers)


Hi Martin,

this branch changes apport/crashdb_impl/launchpad.py to use launchpadlib rather than screenscraping to handle the file bug workflow. It also provides a way to run the launchpad tests against the development launchpad instance (launchpad.dev). When I run the tests against the launchpad.dev instance I get only one test failure, which I hope you can help me sort out before landing this code.

To run the tests:
APPORT_LAUNCHPAD_INSTANCE=dev python -tt apport/crashdb_impl/launchpad.py

Let me know what you think and what changes are necessary to make this branch landable on apport trunk.

Thanks!
-- 
https://code.launchpad.net/~matsubara/apport/no-more-screenscraping/+merge/34494
Your team Apport upstream developers is requested to review the proposed merge of lp:~matsubara/apport/no-more-screenscraping into lp:apport.
=== modified file 'apport/crashdb_impl/launchpad.py'
--- apport/crashdb_impl/launchpad.py	2010-06-16 11:03:07 +0000
+++ apport/crashdb_impl/launchpad.py	2010-09-02 21:45:52 +0000
@@ -10,12 +10,12 @@
 # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 # the full text of the license.
 
-import urllib, tempfile, shutil, os.path, re, gzip, sys, socket, ConfigParser
+import urllib, tempfile, os.path, re, gzip, sys
 import email
 from cStringIO import StringIO
 
 from launchpadlib.errors import HTTPError
-from launchpadlib.launchpad import Launchpad, STAGING_SERVICE_ROOT, EDGE_SERVICE_ROOT
+from launchpadlib.launchpad import Launchpad
 
 import apport.crashdb
 import apport
@@ -26,7 +26,7 @@
     for attachment in attachments:
         try:
             f = attachment.data.open()
-        except HTTPError, e:
+        except HTTPError:
             print >> sys.stderr, 'ERROR: Broken attachment on bug, ignoring'
             continue
         name = f.filename
@@ -52,8 +52,9 @@
         - distro: Name of the distribution in Launchpad
         - project: Name of the project in Launchpad
         (Note that exactly one of "distro" or "project" must be given.)
-        - staging: If set, this uses staging instead of production (optional).
-          This can be overriden or set by $APPORT_STAGING environment.
+        - launchpad_instance: If set, this uses the given launchpad instance
+          instead of production (optional). This can be overriden or set by
+          $APPORT_LAUNCHPAD_INSTANCE environment.
         - cache_dir: Path to a permanent cache directory; by default it uses a
           temporary one. (optional). This can be overridden or set by
           $APPORT_LAUNCHPAD_CACHE environment.
@@ -62,11 +63,13 @@
         - escalation_tag: This adds the given tag to a bug once it gets more
           than 10 duplicates.
         '''
-        if os.getenv('APPORT_STAGING'):
-            options['staging'] = True
+        if os.getenv('APPORT_LAUNCHPAD_INSTANCE'):
+            options['launchpad_instance'] = os.getenv(
+                'APPORT_LAUNCHPAD_INSTANCE')
         if not auth:
-            if options.get('staging'):
-                auth = default_credentials_path + '.staging'
+            lp_instance = options.get('launchpad_instance')
+            if lp_instance:
+                auth = default_credentials_path + '.' + lp_instance
             else:
                 auth = default_credentials_path
         apport.crashdb.CrashDatabase.__init__(self, auth,
@@ -94,10 +97,10 @@
         if self.__launchpad:
             return self.__launchpad
 
-        if self.options.get('staging'):
-            launchpad_instance = STAGING_SERVICE_ROOT
+        if self.options.get('launchpad_instance'):
+            launchpad_instance = self.options.get('launchpad_instance')
         else:
-            launchpad_instance = EDGE_SERVICE_ROOT
+            launchpad_instance = 'edge'
 
         auth_dir = os.path.dirname(self.auth)
         if auth_dir and not os.path.isdir(auth_dir):
@@ -184,11 +187,35 @@
         mime.flush()
         mime.seek(0)
 
-        ticket = upload_blob(mime, progress_callback,
-                staging=self.options.get('staging', False))
+        ticket = upload_blob(
+            mime, progress_callback, hostname=self.get_hostname())
         assert ticket
         return ticket
 
+    def get_target(self, report):
+        """Return the bug_target for this report."""
+        project = self.options.get('project')
+        if report.has_key('SourcePackage'):
+            bug_target = self.lp_distro.getSourcePackage(
+                name=report['SourcePackage'])
+        elif project:
+            bug_target = self.launchpad.projects[project]
+        else:
+            bug_target = self.lp_distro
+        return bug_target
+
+    def get_hostname(self):
+        """Return the hostname for the Launchpad instance."""
+        launchpad_instance = self.options.get('launchpad_instance')
+        if launchpad_instance:
+            if launchpad_instance == 'staging':
+                hostname = 'staging.launchpad.net'
+            else:
+                hostname = 'launchpad.dev'
+        else:
+            hostname = 'launchpad.net'
+        return hostname
+
     def get_comment_url(self, report, handle):
         '''Return an URL that should be opened after report has been uploaded
         and upload() returned handle.
@@ -202,13 +229,10 @@
         if title:
             args['field.title'] = title
 
-        if self.options.get('staging'):
-            hostname = 'staging.launchpad.net'
-        else:
-            hostname = 'launchpad.net'
+        hostname = self.get_hostname()
 
         project = self.options.get('project')
-        
+
         if not project:
             if report.has_key('SourcePackage'):
                 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (
@@ -780,7 +804,7 @@
     def https_open(self, req):
         return self.do_open(HTTPSProgressConnection, req)
 
-def upload_blob(blob, progress_callback = None, staging=False):
+def upload_blob(blob, progress_callback = None, hostname='launchpad.net'):
     '''Upload blob (file-like object) to Launchpad.
 
     progress_callback can be set to a function(sent, total) which is regularly
@@ -790,19 +814,20 @@
 
     Return None on error, or the ticket number on success.
 
-    By default this uses the production Launchpad instance. Set staging=True to
-    use staging.launchpad.net (for testing).
+    By default this uses the production Launchpad hostname. Set
+    hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another
+    instance for testing.
     '''
+    #XXX 2010-08-05 matsubara bug=315358
+    # Once bug 315358 is fixed, this function can be converted to use the API
+    # rather than use screenscraping.
     ticket = None
 
     global _https_upload_callback
     _https_upload_callback = progress_callback
 
     opener = urllib2.build_opener(HTTPSProgressHandler, multipartpost_handler.MultipartPostHandler)
-    if staging:
-        url = 'https://staging.launchpad.net/+storeblob'
-    else:
-        url = 'https://launchpad.net/+storeblob'
+    url = 'https://%s/+storeblob' % hostname
     result = opener.open(url,
         { 'FORM_SUBMIT': '1', 'field.blob': blob })
     ticket = result.info().get('X-Launchpad-Blob-Token')
@@ -814,19 +839,17 @@
 #
 
 if __name__ == '__main__':
-    import unittest, urllib2, cookielib
+    import unittest, urllib2
 
     crashdb = None
     segv_report = None
     python_report = None
 
     class _T(unittest.TestCase):
-        # this assumes that a source package 'coreutils' exists and builds a
-        # binary package 'coreutils'
-        test_package = 'coreutils'
-        test_srcpackage = 'coreutils'
-        known_test_id = 302779
-        known_test_id2 = 89040
+        # this assumes that a source package 'alsa-utils' exists and builds a
+        # binary package 'alsa-utils'
+        test_package = 'alsa-utils'
+        test_srcpackage = 'alsa-utils'
 
         #
         # Generic tests, should work for all CrashDB implementations
@@ -843,7 +866,72 @@
             self.ref_report = apport.Report()
             self.ref_report.add_os_info()
             self.ref_report.add_user_info()
-            self.ref_report['SourcePackage'] = 'coreutils'
+            self.ref_report['SourcePackage'] = 'alsa-utils'
+
+            # Objects tests rely on.
+            self.uncommon_description_bug = self._file_uncommon_description_bug()
+            self._create_project('langpack-o-matic')
+
+            # XXX Should create new bug reports, not reuse those.
+            self.known_test_id = self.uncommon_description_bug.id
+            self.known_test_id2 = self._file_uncommon_description_bug().id
+
+        def _create_project(self, name):
+            """Create a project using launchpadlib to be used by tests."""
+            project = self.crashdb.launchpad.projects[name]
+            if not project:
+                self.crashdb.launchpad.projects.new_project(
+                    description=name + 'description',
+                    display_name=name,
+                    name=name,
+                    summary=name + 'summary',
+                    title=name + 'title')
+
+        def _file_uncommon_description_bug(self):
+            """File a bug report with an uncommon description.
+
+            Example taken from real LP bug 269539. It contains only
+            ProblemType/Architecture/DistroRelease in the description.
+            """
+            title = (
+                u'Three-way merge issues with menu.lst file during kernel'
+                'updates: "Conflicts found! Please edit'
+                '`/var/run/grub/menu.lst\' and sort them out manually."')
+            uncommon_description = (
+                u'Ubuntu 8.10 failed to install 2.6.27.3 kernal during'
+                'update\n\nProblemType: Package\nArchitecture:'
+                'amd64\nDistroRelease: Ubuntu 8.10\n\nDuring a kernel upgrade,'
+                'under certain conditions, if the user select "Do a 3-way'
+                'merge" for menu.lst then the following error'
+                'occurs:\n\nupdate-initramfs: Generating'
+                '/boot/initrd.img-2.6.27-3-generic\nRunning postinst hook'
+                'script /sbin/update-grub.\nSearching for GRUB installation'
+                'directory ... found: /boot/grub\nSearching for default file'
+                '... found: /boot/grub/default\nTesting for an existing GRUB'
+                'menu.lst file ... found: /boot/grub/menu.lst\nSearching for'
+                'splash image ... none found, skipping ...\nFound kernel:'
+                '/boot/vmlinuz-2.6.27-3-generic\nFound kernel:'
+                '/boot/last-good-boot/vmlinuz\nFound kernel:'
+                '/boot/vmlinuz-2.6.27-2-generic\nFound kernel:'
+                '/boot/vmlinuz-2.6.26-5-generic\nFound kernel:'
+                '/boot/memtest86+.bin\nMerging changes into the new version\n\n'
+                'Conflicts found! Please edit `/var/run/grub/menu.lst\' and'
+                'sort them out manually.\n The file'
+                '`/var/run/grub/menu.lst.ucf-new\' has a record of the failed'
+                'merge of the configuration file.\n\nUser postinst hook script'
+                '[/sbin/update-grub] exited with value 3\ndpkg: error'
+                'processing linux-image-2.6.27-3-generic (--configure):\n'
+                'subprocess post-installation script returned error exit status'
+                '3\n\n\n')
+            bug_target = self.crashdb.lp_distro
+            return self.crashdb.launchpad.bugs.createBug(
+                title=title, description=uncommon_description,
+                target=bug_target)
+
+        @property
+        def hostname(self):
+            """Get the hostname for the given crashdb."""
+            return self.crashdb.get_hostname()
 
         def _file_segv_report(self):
             '''File a SEGV crash report.
@@ -863,10 +951,10 @@
 
             handle = self.crashdb.upload(r)
             self.assert_(handle)
-            url = self.crashdb.get_comment_url(r, handle)
-            self.assert_(url)
+            bug_target = self.crashdb.get_target(r)
+            self.assert_(bug_target)
 
-            id = self._fill_bug_form(url)
+            id = self._file_bug(bug_target, r, handle)
             self.assert_(id > 0)
             return id
 
@@ -878,7 +966,7 @@
             global segv_report
             id = self._file_segv_report()
             segv_report = id
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
         def test_1_report_python(self):
             '''upload() and get_comment_url() for Python crash
@@ -899,14 +987,14 @@
 
             handle = self.crashdb.upload(r)
             self.assert_(handle)
-            url = self.crashdb.get_comment_url(r, handle)
-            self.assert_(url)
+            bug_target = self.crashdb.get_target(r)
+            self.assert_(bug_target)
 
-            id = self._fill_bug_form(url)
+            id = self._file_bug(bug_target, r, handle)
             self.assert_(id > 0)
             global python_report
             python_report = id
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
         def test_2_download(self):
             '''download()'''
@@ -994,12 +1082,14 @@
 
         def test_update_description(self):
             '''update() with changing description'''
-
-            id = self._fill_bug_form(
-                'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
-                    'field.title=testbug&field.actions.search=' % self.crashdb.distro)
+            bug_target = self.crashdb.lp_distro.getSourcePackage(name='pmount')
+            bug = self.crashdb.launchpad.bugs.createBug(
+                description='test description for test bug.',
+                target=bug_target,
+                title='testbug')
+            id = bug.id
             self.assert_(id > 0)
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
             r = apport.Report('Bug')
 
@@ -1024,14 +1114,16 @@
         def test_update_comment(self):
             '''update() with appending comment'''
 
+            bug_target = self.crashdb.lp_distro.getSourcePackage(name='pmount')
             # we need to fake an apport description separator here, since we
             # want to be lazy and use download() for checking the result
-            id = self._fill_bug_form(
-                'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
-                    'field.title=testbug&field.actions.search=' %
-                    self.crashdb.distro, comment='Pr0blem\n\n--- \nProblemType: Bug')
+            bug = self.crashdb.launchpad.bugs.createBug(
+                description='Pr0blem\n\n--- \nProblemType: Bug',
+                target=bug_target,
+                title='testbug')
+            id = bug.id
             self.assert_(id > 0)
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
             r = apport.Report('Bug')
 
@@ -1057,11 +1149,14 @@
         def test_update_filter(self):
             '''update() with a key filter'''
 
-            id = self._fill_bug_form(
-                'https://bugs.staging.launchpad.net/%s/+source/bash/+filebug?'
-                    'field.title=testbug&field.actions.search=' % self.crashdb.distro)
+            bug_target = self.crashdb.lp_distro.getSourcePackage(name='pmount')
+            bug = self.crashdb.launchpad.bugs.createBug(
+                description='test description for test bug',
+                target=bug_target,
+                title='testbug')
+            id = bug.id
             self.assert_(id > 0)
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
             r = apport.Report('Bug')
 
@@ -1211,7 +1306,7 @@
             invalidated by marking it as a duplicate.
             '''
             id = self._file_segv_report()
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
             r = self.crashdb.download(id)
 
@@ -1246,120 +1341,85 @@
         @classmethod
         def _get_instance(klass):
             '''Create a CrashDB instance'''
-
-            return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 
-                    {'distro': 'ubuntu', 'staging': True})
-
-        def _fill_bug_form(self, url, comment=None):
-            '''Fill form for a distro bug and commit the bug.
-
-            Return the report ID.
-            '''
-            cj = cookielib.MozillaCookieJar()
-            cj.load(os.path.expanduser('~/.lpcookie.txt'))
-            opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-            opener.addheaders = [('Referer', url)]
-
-            re_pkg = re.compile('\+source/([\w]+)/')
-            re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
-            re_tags = re.compile('<input.*id="field.tags".*value="([^"]*)"')
-
-            # parse default field values from reporting page
-            while True:
-                res = opener.open(url)
-                try:
-                    self.assertEqual(res.getcode(), 200)
-                except AttributeError:
-                    pass # getcode() is new in Python 2.6
-                content = res.read()
-
-                if 'Please wait while bug data is processed' in content:
-                    print '.',
-                    sys.stdout.flush()
-                    time.sleep(5)
-                    continue
-
-                break
-
-            m_pkg = re_pkg.search(url)
-            m_title = re_title.search(content)
-            m_tags = re_tags.search(content)
-
-            # strip off GET arguments from URL
-            url = url.split('?')[0]
-
-            # create request to file bug
-            args = {
-                'packagename_option': 'choose',
-                'field.packagename': m_pkg.group(1),
-                'field.title': m_title.group(1),
-                'field.tags': m_tags.group(1),
-                'field.comment': comment or 'ZOMG!',
-                'field.actions.submit_bug': '1',
-            }
-
-            res = opener.open(url, data=urllib.urlencode(args))
-            try:
-                self.assertEqual(res.getcode(), 200)
-            except AttributeError:
-                pass # getcode() is new in Python 2.6
-            self.assert_('+source/%s/+bug/' % m_pkg.group(1) in res.geturl())
-            id = res.geturl().split('/')[-1]
-            return int(id)
-
-        def _fill_bug_form_project(self, url):
-            '''Fill form for a project bug and commit the bug.
-
-            Return the report ID.
-            '''
-            cj = cookielib.MozillaCookieJar()
-            cj.load(os.path.expanduser('~/.lpcookie.txt'))
-            opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-            opener.addheaders = [('Referer', url)]
-
-            m = re.search('launchpad.net/([^/]+)/\+filebug', url)
-            assert m
-            project = m.group(1)
-
-            re_title = re.compile('<input.*id="field.title".*value="([^"]+)"')
-            re_tags = re.compile('<input.*id="field.tags".*value="([^"]+)"')
-
-            # parse default field values from reporting page
-            while True:
-                res = opener.open(url)
-                try:
-                    self.assertEqual(res.getcode(), 200)
-                except AttributeError:
-                    pass # getcode() is new in Python 2.6
-                content = res.read()
-
-                if 'Please wait while bug data is processed' in content:
-                    print '.',
-                    sys.stdout.flush()
-                    time.sleep(5)
-                    continue
-
-                break
-
-            m_title = re_title.search(content)
-            m_tags = re_tags.search(content)
-
-            # strip off GET arguments from URL
-            url = url.split('?')[0]
-
-            # create request to file bug
-            args = {
-                'field.title': m_title.group(1),
-                'field.tags': m_tags.group(1),
-                'field.comment': 'ZOMG!',
-                'field.actions.submit_bug': '1',
-            }
-
-            res = opener.open(url, data=urllib.urlencode(args))
-            self.assertEqual(res.getcode(), 200)
-            self.assert_(('launchpad.net/%s/+bug' % project) in res.geturl())
-            id = res.geturl().split('/')[-1]
-            return int(id)
+            launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE')
+
+            return CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
+                    {'distro': 'ubuntu',
+                     'launchpad_instance': launchpad_instance})
+
+        def _get_librarian_hostname(self):
+            """Return the librarian hostname according to the LP hostname used."""
+            hostname = self.crashdb.get_hostname()
+            if 'staging' in hostname:
+                return 'staging.launchpadlibrarian.net'
+            else:
+                return 'launchpad.dev:58080'
+
+        def _file_bug(self, bug_target, report, handle, comment=None):
+            """File a bug using launchpadlib."""
+            bug_title = report.get('Title', report.standard_title())
+
+            blob_info = self.crashdb.launchpad.temporary_blobs.fetch(
+                token=handle)
+            # XXX 2010-08-03 matsubara bug=612990:
+            #     Can't fetch the blob directly, so let's load it from the
+            #     representation.
+            blob = self.crashdb.launchpad.load(blob_info['self_link'])
+            #XXX Need to find a way to trigger the job that process the blob
+            # rather polling like this. This makes the test suite take forever
+            # to run.
+            while not blob.hasBeenProcessed():
+                time.sleep(1)
+
+            # processed_blob contains info about privacy, additional comments
+            # and attachments.
+            processed_blob = blob.getProcessedData()
+
+            bug = self.crashdb.launchpad.bugs.createBug(
+                description=processed_blob['extra_description'],
+                private=processed_blob['private'],
+                tags=processed_blob['initial_tags'],
+                target=bug_target,
+                title=bug_title)
+
+            for comment in processed_blob['comments']:
+                bug.newMessage(content=comment)
+
+            # Ideally, one would be able to retrieve the attachment content
+            # from the ProblemReport object or from the processed_blob.
+            # Unfortunately the processed_blob only give us the Launchpad
+            # librarian file_alias_id, so that's why we need to
+            # download it again and upload to the bug report. It'd be even
+            # better if addAttachment could work like linkAttachment, the LP
+            # api used in the +filebug web UI, but there are security concerns
+            # about the way linkAttachment works.
+            librarian_url = 'http://%s' % self._get_librarian_hostname()
+            for attachment in processed_blob['attachments']:
+                filename = description = attachment['description']
+                # Download the attachment data.
+                data = urllib.urlopen(urllib.basejoin(librarian_url,
+                    str(attachment['file_alias_id']) + '/' + filename)).read()
+                # Add the attachment to the newly created bug report.
+                bug.addAttachment(
+                    comment=filename,
+                    data=data,
+                    filename=filename,
+                    description=description)
+
+            for subscriber in processed_blob['subscribers']:
+                sub = self.crashdb.launchpad.people[subscriber]
+                if sub:
+                    bug.subscribe(person=sub)
+
+            for submission_key in processed_blob['hwdb_submission_keys']:
+                # XXX 2010-08-04 matsubara bug=628889:
+                #     Can't fetch the submission directly, so let's load it
+                #     from the representation.
+                submission = self.crashdb.launchpad.load(
+                    'https://api.%s/beta/+hwdb/+submission/%s'
+                    % (self.crashdb.get_hostname(), submission_key))
+                bug.linkHWSubmission(submission=submission)
+            return int(bug.id)
 
         def _mark_needs_retrace(self, id):
             '''Mark a report ID as needing retrace.'''
@@ -1406,10 +1466,12 @@
         def test_project(self):
             '''reporting crashes against a project instead of a distro'''
 
+            launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE')
             # crash database for langpack-o-matic project (this does not have
             # packages in any distro)
-            crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 
-                {'project': 'langpack-o-matic', 'staging': True})
+            crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
+                {'project': 'langpack-o-matic',
+                 'launchpad_instance': launchpad_instance})
             self.assertEqual(crashdb.distro, None)
 
             # create Python crash report
@@ -1426,12 +1488,12 @@
             # file it
             handle = crashdb.upload(r)
             self.assert_(handle)
-            url = crashdb.get_comment_url(r, handle)
-            self.assert_('launchpad.net/langpack-o-matic/+filebug' in url)
+            bug_target = crashdb.get_target(r)
+            self.assertEqual(bug_target.name, 'langpack-o-matic')
 
-            id = self._fill_bug_form_project(url)
+            id = self._file_bug(bug_target, r, handle)
             self.assert_(id > 0)
-            print >> sys.stderr, '(https://staging.launchpad.net/bugs/%i) ' % id,
+            print >> sys.stderr, '(https://%s/bugs/%i) ' % (self.hostname, id),
 
             # update
             r = crashdb.download(id)
@@ -1454,7 +1516,7 @@
             '''download() of uncommon description formats'''
 
             # only ProblemType/Architecture/DistroRelease in description
-            r = self.crashdb.download(269539)
+            r = self.crashdb.download(self.uncommon_description_bug.id)
             self.assertEqual(r['ProblemType'], 'Package')
             self.assertEqual(r['Architecture'], 'amd64')
             self.assert_(r['DistroRelease'].startswith('Ubuntu '))
@@ -1465,10 +1527,12 @@
 
             assert segv_report, 'you need to run test_1_report_segv() first'
 
-            db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '', 
-                    {'distro': 'ubuntu', 'staging': True,
-                        'escalation_tag': 'omgkittens',
-                        'escalation_subscription': 'apport-hackers'})
+            launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE')
+            db = CrashDatabase(os.environ.get('LP_CREDENTIALS'), '',
+                    {'distro': 'ubuntu',
+                     'launchpad_instance': launchpad_instance,
+                     'escalation_tag': 'omgkittens',
+                     'escalation_subscription': 'apport-hackers'})
 
             count = 0
             p = db.launchpad.people[db.options['escalation_subscription']].self_link
@@ -1481,13 +1545,13 @@
                     db.close_duplicate(b, segv_report)
                     b = db.launchpad.bugs[segv_report]
                     has_escalation_tag = db.options['escalation_tag'] in b.tags
-                    has_escalation_subsciption = any([s.person_link == p for s in b.subscriptions])
+                    has_escalation_subscription = any([s.person_link == p for s in b.subscriptions])
                     if count <= 10:
                         self.failIf(has_escalation_tag)
-                        self.failIf(has_escalation_subsciption)
+                        self.failIf(has_escalation_subscription)
                     else:
                         self.assert_(has_escalation_tag)
-                        self.assert_(has_escalation_subsciption)
+                        self.assert_(has_escalation_subscription)
             finally:
                 for b in range(first_dup, first_dup+count):
                     print 'R%i' % b,
@@ -1510,7 +1574,7 @@
             t.target = self.crashdb.launchpad.distributions['ubuntu'].getSourcePackage(name='pmount')
             t.status = 'Invalid'
             t.lp_save()
-            b.addTask(target=self.crashdb.launchpad.projects['coreutils'])
+            b.addTask(target=self.crashdb.launchpad.projects['alsa-utils'])
             b.addTask(target=self.crashdb.launchpad.distributions['ubuntu'])
 
             self.crashdb._mark_dup_checked(python_report, self.ref_report)
@@ -1522,11 +1586,11 @@
 
             # upstream task should be unmodified
             b = self.crashdb.launchpad.bugs[python_report]
-            self.assertEqual(b.bug_tasks[0].bug_target_name, 'coreutils')
+            self.assertEqual(b.bug_tasks[0].bug_target_name, 'alsa-utils')
             self.assertEqual(b.bug_tasks[0].status, 'New')
 
             # package-less distro task should have package name fixed
-            self.assertEqual(b.bug_tasks[1].bug_target_name, 'coreutils (Ubuntu)')
+            self.assertEqual(b.bug_tasks[1].bug_target_name, 'alsa-utils (Ubuntu)')
             self.assertEqual(b.bug_tasks[1].status, 'New')
 
             # invalid pmount task should be unmodified


Follow ups