← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/explicit-proxy-bugtracker-cleanup into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/explicit-proxy-bugtracker-cleanup into lp:launchpad with lp:~cjwatson/launchpad/explicit-proxy-trac as a prerequisite.

Commit message:
Remove urllib2-based external bug tracker code, now that everything has been ported to requests.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/explicit-proxy-bugtracker-cleanup/+merge/348436
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/explicit-proxy-bugtracker-cleanup into lp:launchpad.
=== modified file 'lib/lp/bugs/externalbugtracker/__init__.py'
--- lib/lp/bugs/externalbugtracker/__init__.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/__init__.py	2018-06-23 10:08:23 +0000
@@ -14,7 +14,6 @@
     'DebBugs',
     'DebBugsDatabaseNotFound',
     'ExternalBugTracker',
-    'ExternalBugTrackerRequests',
     'GitHub',
     'InvalidBugId',
     'LookupTree',
@@ -39,7 +38,6 @@
     BugWatchUpdateError,
     BugWatchUpdateWarning,
     ExternalBugTracker,
-    ExternalBugTrackerRequests,
     InvalidBugId,
     LookupTree,
     PrivateRemoteBug,

=== modified file 'lib/lp/bugs/externalbugtracker/base.py'
--- lib/lp/bugs/externalbugtracker/base.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/base.py	2018-06-23 10:08:23 +0000
@@ -12,7 +12,6 @@
     'BugWatchUpdateError',
     'BugWatchUpdateWarning',
     'ExternalBugTracker',
-    'ExternalBugTrackerRequests',
     'InvalidBugId',
     'LookupTree',
     'LP_USER_AGENT',
@@ -28,9 +27,6 @@
     ]
 
 
-import urllib
-import urllib2
-
 import requests
 from six.moves.urllib_parse import (
     urljoin,
@@ -176,14 +172,6 @@
                 ISupportsCommentImport.providedBy(self) or
                 ISupportsBackLinking.providedBy(self)))
 
-    @ensure_no_transaction
-    def urlopen(self, request, data=None):
-        if self.url_opener:
-            func = self.url_opener.open
-        else:
-            func = urllib2.urlopen
-        return func(request, data, self.timeout)
-
     def getExternalBugTrackerToUse(self):
         """See `IExternalBugTracker`."""
         return self
@@ -261,56 +249,6 @@
         # user-agent string (Python-urllib/2.x) to access their bugzilla.
         return {'User-Agent': LP_USER_AGENT, 'Host': self.basehost}
 
-    def _fetchPage(self, page, data=None):
-        """Fetch a page from the remote server.
-
-        A BugTrackerConnectError will be raised if anything goes wrong.
-        """
-        if not isinstance(page, urllib2.Request):
-            page = urllib2.Request(page, headers=self._getHeaders())
-        try:
-            return self.urlopen(page, data)
-        except (urllib2.HTTPError, urllib2.URLError) as val:
-            raise BugTrackerConnectError(self.baseurl, val)
-
-    def _getPage(self, page):
-        """GET the specified page on the remote HTTP server."""
-        request = urllib2.Request(
-            "%s/%s" % (self.baseurl, page), headers=self._getHeaders())
-        return self._fetchPage(request).read()
-
-    def _post(self, url, data):
-        """Post to a given URL."""
-        request = urllib2.Request(url, headers=self._getHeaders())
-        return self._fetchPage(request, data=data)
-
-    def _postPage(self, page, form, repost_on_redirect=False):
-        """POST to the specified page and form.
-
-        :param form: is a dict of form variables being POSTed.
-        :param repost_on_redirect: override RFC-compliant redirect handling.
-            By default, if the POST receives a redirect response, the
-            request to the redirection's target URL will be a GET.  If
-            `repost_on_redirect` is True, this method will do a second POST
-            instead.  Do this only if you are sure that repeated POST to
-            this page is safe, as is usually the case with search forms.
-        """
-        url = "%s/%s" % (self.baseurl, page)
-        post_data = urllib.urlencode(form)
-        response = self._post(url, data=post_data)
-
-        if repost_on_redirect and response.url != url:
-            response = self._post(response.url, data=post_data)
-
-        return response.read()
-
-
-class ExternalBugTrackerRequests(ExternalBugTracker):
-    """An external bug tracker that uses `requests`.
-
-    This is temporary until all bug tracker types have been converted.
-    """
-
     @ensure_no_transaction
     def makeRequest(self, method, url, **kwargs):
         """Make a request.

=== modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py'
--- lib/lp/bugs/externalbugtracker/bugzilla.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/bugzilla.py	2018-06-23 10:08:23 +0000
@@ -32,7 +32,7 @@
     BugNotFound,
     BugTrackerAuthenticationError,
     BugTrackerConnectError,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     UnknownRemoteImportanceError,
@@ -61,7 +61,7 @@
     )
 
 
-class Bugzilla(ExternalBugTrackerRequests):
+class Bugzilla(ExternalBugTracker):
     """An ExternalBugTracker for dealing with remote Bugzilla systems."""
 
     batch_query_threshold = 0  # Always use the batch method.

=== modified file 'lib/lp/bugs/externalbugtracker/github.py'
--- lib/lp/bugs/externalbugtracker/github.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/github.py	2018-06-23 10:08:23 +0000
@@ -25,7 +25,7 @@
 from lp.bugs.externalbugtracker import (
     BugTrackerConnectError,
     BugWatchUpdateError,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     UnknownRemoteStatusError,
     UnparsableBugTrackerVersion,
     )
@@ -120,7 +120,7 @@
     """The GitHub Issues URL is malformed."""
 
 
-class GitHub(ExternalBugTrackerRequests):
+class GitHub(ExternalBugTracker):
     """An `ExternalBugTracker` for dealing with GitHub issues."""
 
     # Avoid eating through our rate limit unnecessarily.

=== modified file 'lib/lp/bugs/externalbugtracker/mantis.py'
--- lib/lp/bugs/externalbugtracker/mantis.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/mantis.py	2018-06-23 10:08:23 +0000
@@ -24,7 +24,7 @@
     BugNotFound,
     BugTrackerConnectError,
     BugWatchUpdateError,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     UnknownRemoteStatusError,
@@ -165,7 +165,7 @@
             raise UnparsableBugData("Exception parsing CSV file: %s." % error)
 
 
-class Mantis(ExternalBugTrackerRequests):
+class Mantis(ExternalBugTracker):
     """An `ExternalBugTracker` for dealing with Mantis instances.
 
     For a list of tested Mantis instances and their behaviour when

=== modified file 'lib/lp/bugs/externalbugtracker/roundup.py'
--- lib/lp/bugs/externalbugtracker/roundup.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/roundup.py	2018-06-23 10:08:23 +0000
@@ -13,7 +13,7 @@
 
 from lp.bugs.externalbugtracker import (
     BugNotFound,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     UnknownRemoteStatusError,
@@ -42,7 +42,7 @@
         for (key, value) in items)
 
 
-class Roundup(ExternalBugTrackerRequests):
+class Roundup(ExternalBugTracker):
     """An ExternalBugTracker descendant for handling Roundup bug trackers."""
 
     _status_fields_map = {

=== modified file 'lib/lp/bugs/externalbugtracker/rt.py'
--- lib/lp/bugs/externalbugtracker/rt.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/rt.py	2018-06-23 10:08:23 +0000
@@ -15,7 +15,7 @@
 from lp.bugs.externalbugtracker import (
     BugNotFound,
     BugTrackerConnectError,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     UnknownRemoteStatusError,
@@ -29,7 +29,7 @@
 from lp.services.webapp.url import urlparse
 
 
-class RequestTracker(ExternalBugTrackerRequests):
+class RequestTracker(ExternalBugTracker):
     """`ExternalBugTracker` subclass for handling RT imports."""
 
     ticket_url = 'REST/1.0/ticket/%s/show'

=== modified file 'lib/lp/bugs/externalbugtracker/sourceforge.py'
--- lib/lp/bugs/externalbugtracker/sourceforge.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/sourceforge.py	2018-06-23 10:08:23 +0000
@@ -11,7 +11,7 @@
 
 from lp.bugs.externalbugtracker import (
     BugNotFound,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     PrivateRemoteBug,
@@ -27,7 +27,7 @@
 from lp.services.webapp import urlsplit
 
 
-class SourceForge(ExternalBugTrackerRequests):
+class SourceForge(ExternalBugTracker):
     """An ExternalBugTracker for SourceForge bugs."""
 
     # We only allow ourselves to update one SourceForge bug at a time to

=== modified file 'lib/lp/bugs/externalbugtracker/tests/test_externalbugtracker.py'
--- lib/lp/bugs/externalbugtracker/tests/test_externalbugtracker.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/tests/test_externalbugtracker.py	2018-06-23 10:08:23 +0000
@@ -5,9 +5,6 @@
 
 __metaclass__ = type
 
-from StringIO import StringIO
-import urllib2
-
 import responses
 from testtools.matchers import (
     ContainsDict,
@@ -21,7 +18,6 @@
 from lp.bugs.externalbugtracker.base import (
     BugTrackerConnectError,
     ExternalBugTracker,
-    ExternalBugTrackerRequests,
     LP_USER_AGENT,
     )
 from lp.bugs.externalbugtracker.debbugs import DebBugs
@@ -30,15 +26,8 @@
     ISupportsCommentImport,
     ISupportsCommentPushing,
     )
-from lp.testing import (
-    monkey_patch,
-    TestCase,
-    )
-from lp.testing.fakemethod import FakeMethod
-from lp.testing.layers import (
-    ZopelessDatabaseLayer,
-    ZopelessLayer,
-    )
+from lp.testing import TestCase
+from lp.testing.layers import ZopelessDatabaseLayer
 
 
 @implementer(ISupportsBackLinking)
@@ -110,81 +99,27 @@
             tracker = DebBugs(self.base_url)
             self.assertFalse(tracker.sync_comments)
 
-    def _makeFakePostForm(self, base_url, page=None):
-        """Create a fake `urllib2.urlopen` result."""
-        content = "<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()
-        fake_form = StringIO(content)
-        if page is None:
-            page = self.factory.getUniqueString()
-        fake_form.url = base_url + page
-        return fake_form
-
-    def _fakeExternalBugTracker(self, base_url, fake_form):
-        """Create an `ExternalBugTracker` with a fake `_post` method."""
-        bugtracker = ExternalBugTracker(base_url)
-        bugtracker._post = FakeMethod(result=fake_form)
-        return bugtracker
-
+    @responses.activate
     def test_postPage_returns_response_page(self):
         # _postPage posts, then returns the page text it gets back from
         # the server.
         base_url = "http://example.com/";
         form = self.factory.getUniqueString()
-        fake_form = self._makeFakePostForm(base_url, page=form)
-        bugtracker = self._fakeExternalBugTracker(base_url, fake_form)
-        self.assertEqual(fake_form.getvalue(), bugtracker._postPage(form, {}))
-
-    def test_postPage_does_not_repost_on_redirect(self):
-        # By default, if the POST redirects, _postPage leaves urllib2 to
-        # handle it in the normal, RFC-compliant way.
-        base_url = "http://example.com/";
-        form = self.factory.getUniqueString()
-        fake_form = self._makeFakePostForm(base_url)
-        bugtracker = self._fakeExternalBugTracker(base_url, fake_form)
-
-        bugtracker._postPage(form, {})
-
-        self.assertEqual(1, bugtracker._post.call_count)
-        args, kwargs = bugtracker._post.calls[0]
-        self.assertEqual((base_url + form, ), args)
-
-    def test_postPage_can_repost_on_redirect(self):
-        # Some pages (that means you, BugZilla bug-search page!) can
-        # redirect on POST, but without honouring the POST.  Standard
-        # urllib2 behaviour is to redirect to a GET, but if the caller
-        # says it's safe, _postPage can re-do the POST at the new URL.
-        base_url = "http://example.com/";
-        form = self.factory.getUniqueString()
-        fake_form = self._makeFakePostForm(base_url)
-        bugtracker = self._fakeExternalBugTracker(base_url, fake_form)
-
-        bugtracker._postPage(form, form={}, repost_on_redirect=True)
-
-        self.assertEqual(2, bugtracker._post.call_count)
-        last_args, last_kwargs = bugtracker._post.calls[-1]
-        self.assertEqual((fake_form.url, ), last_args)
-
-    @responses.activate
-    def test_requests_postPage_returns_response_page(self):
-        # _postPage posts, then returns the page text it gets back from
-        # the server.
-        base_url = "http://example.com/";
-        form = self.factory.getUniqueString()
         fake_form = "<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()
-        bugtracker = ExternalBugTrackerRequests(base_url)
+        bugtracker = ExternalBugTracker(base_url)
         transaction.commit()
         responses.add("POST", base_url + form, body=fake_form)
         self.assertEqual(fake_form, bugtracker._postPage(form, {}).text)
 
     @responses.activate
-    def test_requests_postPage_does_not_repost_on_redirect(self):
+    def test_postPage_does_not_repost_on_redirect(self):
         # By default, if the POST redirects, _postPage leaves requests to
         # handle it in the normal, RFC-compliant way.
         base_url = "http://example.com/";
         form = self.factory.getUniqueString()
         target = self.factory.getUniqueString()
         fake_form = "<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()
-        bugtracker = ExternalBugTrackerRequests(base_url)
+        bugtracker = ExternalBugTracker(base_url)
         transaction.commit()
         responses.add(
             "POST", base_url + form, status=302,
@@ -200,7 +135,7 @@
             ]))
 
     @responses.activate
-    def test_requests_postPage_can_repost_on_redirect(self):
+    def test_postPage_can_repost_on_redirect(self):
         # Some pages (that means you, BugZilla bug-search page!) can
         # redirect on POST, but without honouring the POST.  Standard
         # requests behaviour is to redirect to a GET, but if the caller
@@ -209,7 +144,7 @@
         form = self.factory.getUniqueString()
         target = self.factory.getUniqueString()
         fake_form = "<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()
-        bugtracker = ExternalBugTrackerRequests(base_url)
+        bugtracker = ExternalBugTracker(base_url)
         transaction.commit()
         responses.add(
             "POST", base_url + form, status=302,
@@ -228,46 +163,13 @@
 class TestExternalBugTracker(TestCase):
     """Tests for various methods of the ExternalBugTracker."""
 
-    layer = ZopelessLayer
-
-    def test_post_raises_on_404(self):
-        # When posting, a 404 is converted to a BugTrackerConnectError.
-        base_url = "http://example.com/";
-        bugtracker = ExternalBugTracker(base_url)
-
-        def raise404(request, data, timeout=None):
-            raise urllib2.HTTPError('url', 404, 'Not Found', None, None)
-
-        with monkey_patch(urllib2, urlopen=raise404):
-            self.assertRaises(
-                BugTrackerConnectError,
-                bugtracker._post, 'some-url', {'post-data': 'here'})
-
-    def test_post_sends_host(self):
-        # When posting, a Host header is sent.
-        base_host = 'example.com'
-        base_url = 'http://%s/' % base_host
-        bugtracker = ExternalBugTracker(base_url)
-
-        def assert_headers(request, data, timeout=None):
-            self.assertContentEqual(
-                [('User-agent', LP_USER_AGENT), ('Host', base_host)],
-                request.header_items())
-
-        with monkey_patch(urllib2, urlopen=assert_headers):
-            bugtracker._post('some-url', {'post-data': 'here'})
-
-
-class TestExternalBugTrackerRequests(TestCase):
-    """Tests for various methods of the ExternalBugTrackerRequests."""
-
     layer = ZopelessDatabaseLayer
 
     @responses.activate
     def test_postPage_raises_on_404(self):
         # When posting, a 404 is converted to a BugTrackerConnectError.
         base_url = "http://example.com/";
-        bugtracker = ExternalBugTrackerRequests(base_url)
+        bugtracker = ExternalBugTracker(base_url)
         transaction.commit()
         responses.add("POST", base_url + "some-url", status=404)
         self.assertRaises(
@@ -279,7 +181,7 @@
         # When posting, a Host header is sent.
         base_host = 'example.com'
         base_url = 'http://%s/' % base_host
-        bugtracker = ExternalBugTrackerRequests(base_url)
+        bugtracker = ExternalBugTracker(base_url)
         transaction.commit()
         responses.add("POST", base_url + "some-url")
         bugtracker._postPage('some-url', {'post-data': 'here'})

=== modified file 'lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py'
--- lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py	2018-06-23 10:08:23 +0000
@@ -6,58 +6,17 @@
 __metaclass__ = type
 
 import socket
-from urllib2 import URLError
 from xml.parsers.expat import ExpatError
 
 from fixtures import MockPatch
 import requests
 import responses
 
-from lp.bugs.externalbugtracker.xmlrpc import (
-    RequestsTransport,
-    UrlLib2Transport,
-    )
-from lp.bugs.tests.externalbugtracker import (
-    ensure_response_parser_is_expat,
-    UrlLib2TransportTestHandler,
-    )
+from lp.bugs.externalbugtracker.xmlrpc import RequestsTransport
+from lp.bugs.tests.externalbugtracker import ensure_response_parser_is_expat
 from lp.testing import TestCase
 
 
-class TestUrlLib2Transport(TestCase):
-    """Tests for `UrlLib2Transport`."""
-
-    def test_expat_error(self):
-        # Malformed XML-RPC responses cause xmlrpclib to raise an ExpatError.
-        handler = UrlLib2TransportTestHandler()
-        handler.setResponse("<params><mis></match></params>")
-        transport = UrlLib2Transport("http://not.real/";)
-        transport.opener.add_handler(handler)
-
-        # The Launchpad production environment selects Expat at present. This
-        # is quite strict compared to the other parsers that xmlrpclib can
-        # possibly select.
-        ensure_response_parser_is_expat(transport)
-
-        self.assertRaises(
-            ExpatError, transport.request,
-            'www.example.com', 'xmlrpc', "<methodCall />")
-
-    def test_unicode_url(self):
-        # Python's httplib doesn't like Unicode URLs much. Ensure that
-        # they don't cause it to crash, and we get a post-serialisation
-        # connection error instead.
-        self.useFixture(MockPatch(
-            "socket.getaddrinfo",
-            side_effect=socket.gaierror(
-                socket.EAI_NONAME, "Name or service not known")))
-        transport = UrlLib2Transport(u"http://test.invalid/";)
-        self.assertRaisesWithContent(
-            URLError, '<urlopen error [Errno -2] Name or service not known>',
-            transport.request, u"test.invalid", u"xmlrpc",
-            u"\N{SNOWMAN}".encode('utf-8'))
-
-
 class TestRequestsTransport(TestCase):
     """Tests for `RequestsTransport`."""
 

=== modified file 'lib/lp/bugs/externalbugtracker/trac.py'
--- lib/lp/bugs/externalbugtracker/trac.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/trac.py	2018-06-23 10:08:23 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Trac ExternalBugTracker implementation."""
@@ -24,7 +24,7 @@
     BugNotFound,
     BugTrackerAuthenticationError,
     BugTrackerConnectError,
-    ExternalBugTrackerRequests,
+    ExternalBugTracker,
     InvalidBugId,
     LookupTree,
     UnknownRemoteStatusError,
@@ -60,7 +60,7 @@
 FAULT_TICKET_NOT_FOUND = 1001
 
 
-class Trac(ExternalBugTrackerRequests):
+class Trac(ExternalBugTracker):
     """An ExternalBugTracker instance for handling Trac bugtrackers."""
 
     ticket_url = 'ticket/%i?format=csv'

=== modified file 'lib/lp/bugs/externalbugtracker/xmlrpc.py'
--- lib/lp/bugs/externalbugtracker/xmlrpc.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/externalbugtracker/xmlrpc.py	2018-06-23 10:08:23 +0000
@@ -1,26 +1,15 @@
 # Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-"""XMLRPC transports which use urllib2 or requests."""
+"""An XMLRPC transport which uses requests."""
 
 __metaclass__ = type
 __all__ = [
     'RequestsTransport',
-    'UrlLib2Transport',
-    'XMLRPCRedirectHandler',
     ]
 
 
-from cookielib import Cookie
-from cStringIO import StringIO
 from io import BytesIO
-from urllib2 import (
-    build_opener,
-    HTTPCookieProcessor,
-    HTTPError,
-    HTTPRedirectHandler,
-    Request,
-    )
 from urlparse import (
     urlparse,
     urlunparse,
@@ -42,98 +31,6 @@
 from lp.services.utils import traceback_info
 
 
-class XMLRPCRedirectHandler(HTTPRedirectHandler):
-    """A handler for HTTP redirections of XML-RPC requests."""
-
-    def redirect_request(self, req, fp, code, msg, headers, newurl):
-        """Return a Request or None in response to a redirect.
-
-        See `urllib2.HTTPRedirectHandler`.
-
-        If the original request is a POST request, the request's payload
-        will be preserved in the redirect and the returned request will
-        also be a POST request.
-        """
-        # If we can't handle this redirect,
-        # HTTPRedirectHandler.redirect_request() will raise an
-        # HTTPError. We call the superclass here in the old fashion
-        # since HTTPRedirectHandler isn't a new-style class.
-        new_request = HTTPRedirectHandler.redirect_request(
-            self, req, fp, code, msg, headers, newurl)
-
-        # If the old request is a POST request, the payload will be
-        # preserved. Note that we don't need to test for the POST-ness
-        # of the old request; if its data attribute - its payload - is
-        # not None it's a POST request, if it's None it's a GET request.
-        # We can therefore just copy the data from the old request to
-        # the new without worrying about breaking things.
-        new_request.data = req.data
-        new_request.timeout = req.timeout
-        return new_request
-
-
-class UrlLib2Transport(Transport):
-    """An XMLRPC transport which uses urllib2.
-
-    This XMLRPC transport uses the Python urllib2 module to make the request,
-    with proxying handled by that module's semantics (though underdocumented).
-    It also handles cookies correctly, and in addition allows specifying the
-    cookie explicitly by setting `self.auth_cookie`.
-
-    Note: this transport isn't fit for general XMLRPC use. It is just good
-    enough for some of our external bug tracker implementations.
-
-    :param endpoint: The URL of the XMLRPC server.
-    """
-
-    verbose = False
-
-    def __init__(self, endpoint, cookie_jar=None):
-        Transport.__init__(self, use_datetime=True)
-        self.scheme, self.host = urlparse(endpoint)[:2]
-        assert self.scheme in ('http', 'https'), (
-            "Unsupported URL scheme: %s" % self.scheme)
-        self.cookie_processor = HTTPCookieProcessor(cookie_jar)
-        self.redirect_handler = XMLRPCRedirectHandler()
-        self.opener = build_opener(
-            self.cookie_processor, self.redirect_handler)
-        self.timeout = config.checkwatches.default_socket_timeout
-
-    def setCookie(self, cookie_str):
-        """Set a cookie for the transport to use in future connections."""
-        name, value = cookie_str.split('=')
-        cookie = Cookie(
-            version=0, name=name, value=value,
-            port=None, port_specified=False,
-            domain=self.host, domain_specified=True,
-            domain_initial_dot=None,
-            path='', path_specified=False,
-            secure=False, expires=False, discard=None,
-            comment=None, comment_url=None, rest=None)
-        self.cookie_processor.cookiejar.set_cookie(cookie)
-
-    def request(self, host, handler, request_body, verbose=0):
-        """Make an XMLRPC request.
-
-        Uses the configured proxy server to make the connection.
-        """
-        url = urlunparse((self.scheme, host, handler, '', '', ''))
-        # httplib can raise a UnicodeDecodeError when using a Unicode
-        # URL, a non-ASCII body and a proxy. http://bugs.python.org/issue12398
-        if isinstance(url, unicode):
-            url = url.encode('utf-8')
-        headers = {'Content-type': 'text/xml'}
-        request = Request(url, request_body, headers)
-        try:
-            response = self.opener.open(request, timeout=self.timeout).read()
-        except HTTPError as he:
-            raise ProtocolError(
-                request.get_full_url(), he.code, he.msg, he.hdrs)
-        else:
-            traceback_info(response)
-            return self.parse_response(StringIO(response))
-
-
 class RequestsTransport(Transport):
     """An XML-RPC transport which uses requests.
 

=== modified file 'lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt'
--- lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt	2018-06-23 10:08:23 +0000
@@ -1,200 +1,3 @@
-XMLRPC urllib2 transport
-------------------------
-
-When using XMLRPC for connecting to external bug trackers, we need to
-use a special transport, which processes http cookies correctly, and
-which can connect through an http proxy server.
-
-    >>> from lp.bugs.tests.externalbugtracker import (
-    ...     UrlLib2TransportTestHandler)
-    >>> from lp.bugs.externalbugtracker.xmlrpc import (
-    ...     UrlLib2Transport)
-
-UrlLib2Transport accepts a CookieJar as an optional parameter upon creation.
-This allows us to share a CookieJar - and therefore the cookie it contains -
-between different transports or URL openers.
-
-    >>> from cookielib import CookieJar
-    >>> jar = CookieJar()
-    >>> transport = UrlLib2Transport('http://example.com', jar)
-    >>> transport.cookie_processor.cookiejar == jar
-    True
-
-We patch the opener to return a fixed response without actually opening a
-connection.  The response returns the request-url as an XMLRPC parameter, and
-sets a cookie from the server, 'foo=bar'.
-
-    >>> test_handler = UrlLib2TransportTestHandler()
-    >>> transport.opener.add_handler(test_handler)
-
-Before sending the request, the transport's cookie jar is empty.
-
-    >>> transport.cookie_processor.cookiejar
-    <...CookieJar[]>
-
-    >>> request_body = """<?xml version="1.0"?>
-    ... <methodCall>
-    ...   <methodName>examples.testMethod</methodName>
-    ...   <params>
-    ...     <param>
-    ...       <value>
-    ...         <int>42</int>
-    ...       </value>
-    ...     </param>
-    ...   </params>
-    ... </methodCall>
-    ... """
-    >>> transport.request('www.example.com', 'xmlrpc', request_body)
-    ('http://www.example.com/xmlrpc',)
-
-We received the url as the single XMLRPC result, and the cookie jar now
-contains the 'foo=bar' cookie sent by the server.
-
-    >>> transport.cookie_processor.cookiejar
-    <...CookieJar[Cookie(version=0, name='foo', value='bar'...)]>
-
-In addition to cookies sent by the server, we can set cookies locally.
-
-    >>> transport.setCookie('ding=dong')
-    >>> transport.cookie_processor.cookiejar
-    <...CookieJar[Cookie(version=0, name='ding', value='dong'...),
-                         Cookie(version=0, name='foo', value='bar'...)]>
-
-If an error occurs trying to make the request, an
-``xmlrpclib.ProtocolError`` is raised.
-
-    >>> from urllib2 import HTTPError
-    >>> test_handler.setError(
-    ...     HTTPError(
-    ...         'http://www.example.com/xmlrpc', 500, 'Internal Error', {},
-    ...          None),
-    ...     'http://www.example.com/xmlrpc')
-    >>> request_body = """<?xml version="1.0"?>
-    ... <methodCall>
-    ...   <methodName>examples.testError</methodName>
-    ...   <params>
-    ...     <param>
-    ...       <value>
-    ...         <int>42</int>
-    ...       </value>
-    ...     </param>
-    ...   </params>
-    ... </methodCall>
-    ... """
-    >>> transport.request('www.example.com', 'xmlrpc', request_body)
-    Traceback (most recent call last):
-    ...
-    ProtocolError: <ProtocolError for http://www.example.com/xmlrpc: 500
-    Internal Error>
-
-If the transport encounters a redirect response it will make its request
-to the location indicated in that response rather than the original
-location.
-
-    >>> test_handler.setRedirect('http://www.example.com/xmlrpc/redirected')
-    >>> request_body = """<?xml version="1.0"?>
-    ... <methodCall>
-    ...   <methodName>examples.whatever</methodName>
-    ...   <params>
-    ...     <param>
-    ...       <value>
-    ...         <int>42</int>
-    ...       </value>
-    ...     </param>
-    ...   </params>
-    ... </methodCall>
-    ... """
-    >>> transport.request('www.example.com', 'xmlrpc', request_body)
-    ('http://www.example.com/xmlrpc/redirected',)
-
-
-The XMLRPCRedirectHandler
-=========================
-
-The UrlLib2Transport uses a custom HTTP redirection handler to handle
-redirect responses. This is a subclass of urllib2's HTTPRedirectHandler.
-
-    >>> from lp.bugs.externalbugtracker.xmlrpc import XMLRPCRedirectHandler
-    >>> from urllib2 import HTTPRedirectHandler, Request
-
-    >>> transport.opener.handlers
-    [... <lp.bugs...XMLRPCRedirectHandler instance ...]
-
-    >>> issubclass(XMLRPCRedirectHandler, HTTPRedirectHandler)
-    True
-
-XMLRPCRedirectHandler overrides HTTPRedirectHandler's redirect_request()
-method. XMLRPCRedirectHandler.redirect_request() will return a
-urllib2.Request object that is set to POST to the new target URL
-specified by an HTTP 30x redirect response (as opposed to
-HTTPRedirectHandler.redirect_request(), which will return a GET request
-to the new target URL).
-
-    >>> request_body = """<?xml version="1.0"?>
-    ... <methodCall>
-    ...   <methodName>examples.exampleRequest</methodName>
-    ...   <params>
-    ...     <param>
-    ...       <value>
-    ...         <int>42</int>
-    ...       </value>
-    ...     </param>
-    ...   </params>
-    ... </methodCall>
-    ... """
-    >>> request_to_be_redirected = Request(
-    ...     'http://www.example.com', data=request_body)
-    >>> request_to_be_redirected.timeout = 30
-
-    >>> handler = XMLRPCRedirectHandler()
-    >>> redirected_request = handler.redirect_request(
-    ...     request_to_be_redirected, None, 302, 'Moved', {},
-    ...     newurl='http://www.example.com/redirected')
-
-The new request will be a POST request to the URL specified in
-redirect_request()'s newurl parameter. The payload of the request will
-be the XML-RPC method call.
-
-    >>> print redirected_request.get_method()
-    POST
-
-    >>> print redirected_request.get_full_url()
-    http://www.example.com/redirected
-
-    >>> print redirected_request.data
-    <?xml version="1.0"?>
-    <methodCall>
-      <methodName>examples.exampleRequest</methodName>
-      <params>
-        <param>
-          <value>
-            <int>42</int>
-          </value>
-        </param>
-      </params>
-    </methodCall>
-    >>> redirected_request.timeout == request_to_be_redirected.timeout
-    True
-
-If an XMLRPCRedirectHandler is passed a GET request to redirect, the new
-request will be a GET request with no payload.
-
-    >>> request_to_be_redirected = Request('http://www.example.com')
-    >>> request_to_be_redirected.timeout = 30
-    >>> redirected_request = handler.redirect_request(
-    ...     request_to_be_redirected, None, 302, 'Moved', {},
-    ...     newurl='http://www.example.com/redirected')
-
-    >>> print redirected_request.get_method()
-    GET
-
-    >>> print redirected_request.get_full_url()
-    http://www.example.com/redirected
-
-    >>> print redirected_request.data
-    None
-
-
 XMLRPC requests transport
 -------------------------
 

=== modified file 'lib/lp/bugs/tests/externalbugtracker.py'
--- lib/lp/bugs/tests/externalbugtracker.py	2018-06-23 10:08:23 +0000
+++ lib/lp/bugs/tests/externalbugtracker.py	2018-06-23 10:08:23 +0000
@@ -11,16 +11,10 @@
     datetime,
     timedelta,
     )
-from httplib import HTTPMessage
 import os
 import random
 import re
-from StringIO import StringIO
 import time
-from urllib2 import (
-    BaseHandler,
-    Request,
-    )
 import xmlrpclib
 
 import responses
@@ -1698,86 +1692,6 @@
         return bug
 
 
-class UrlLib2TransportTestInfo:
-    """A url info object for use in the test, returning
-    a hard-coded cookie header.
-    """
-    cookies = 'foo=bar'
-
-    def getheaders(self, header):
-        """Return the hard-coded cookie header."""
-        if header.lower() in ('cookie', 'set-cookie', 'set-cookie2'):
-            return [self.cookies]
-
-
-class UrlLib2TransportTestHandler(BaseHandler):
-    """A test urllib2 handler returning a hard-coded response."""
-
-    def __init__(self):
-        self.redirect_url = None
-        self.raise_error = None
-        self.response = None
-        self.accessed_urls = []
-
-    def setRedirect(self, new_url):
-        """The next call of default_open() will redirect to `url`."""
-        self.redirect_url = new_url
-
-    def setError(self, error, url):
-        """Raise `error` when `url` is accessed."""
-        self.raise_error = error
-        self.raise_url = url
-
-    def setResponse(self, response):
-        self.response = response
-
-    def default_open(self, req):
-        """Catch all requests and return a hard-coded response.
-
-        The response body is an XMLRPC response. In addition we set the
-        info of the response to contain a cookie.
-        """
-        assert isinstance(req, Request), (
-            'Expected a urllib2.Request, got %s' % req)
-
-        self.accessed_urls.append(req.get_full_url())
-        if (self.raise_error is not None and
-              req.get_full_url() == self.raise_url):
-            error = self.raise_error
-            self.raise_error = None
-            raise error
-        elif self.redirect_url is not None:
-            headers = HTTPMessage(StringIO())
-            headers['location'] = self.redirect_url
-            response = StringIO()
-            response.info = lambda: headers
-            response.geturl = req.get_full_url
-            response.code = 302
-            response.msg = 'Moved'
-            self.redirect_url = None
-            response = self.parent.error(
-                'http', req, response, 302, 'Moved', headers)
-        elif self.response is not None:
-            response = StringIO(self.response)
-            info = UrlLib2TransportTestInfo()
-            response.info = lambda: info
-            response.code = 200
-            response.geturl = req.get_full_url
-            response.msg = ''
-            self.response = None
-        else:
-            xmlrpc_response = xmlrpclib.dumps(
-                (req.get_full_url(), ), methodresponse=True)
-            response = StringIO(xmlrpc_response)
-            info = UrlLib2TransportTestInfo()
-            response.info = lambda: info
-            response.code = 200
-            response.geturl = req.get_full_url
-            response.msg = ''
-
-        return response
-
-
 def ensure_response_parser_is_expat(transport):
     """Ensure the transport always selects the Expat-based response parser.
 


Follow ups