← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/faster-archive-signing-key-tests into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/faster-archive-signing-key-tests into lp:launchpad with lp:~cjwatson/launchpad/composeBuildRequest-deferred as a prerequisite.

Commit message:
Speed up tests that use archive signing keys using an in-process keyserver fixture.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1626739 in Launchpad itself: "Snapcraft build failing in Yakkety for unauthenticated stage-packages"
  https://bugs.launchpad.net/launchpad/+bug/1626739

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/faster-archive-signing-key-tests/+merge/323429

This only works for tests that only talk to the keyserver asynchronously, but for those that do it's very much faster to start one up in-process rather than using a .tac file: it saves on the order of 10 seconds per affected test on my laptop.

The tests for the fixture itself seem to hit some slightly buggy bit of Twisted and require a couple of extra spins through the reactor to clear up cancelled DelayedCalls, but fortunately testtools.deferredruntest already has machinery to tolerate that.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/faster-archive-signing-key-tests into lp:launchpad.
=== modified file 'lib/lp/archivepublisher/archivesigningkey.py'
--- lib/lp/archivepublisher/archivesigningkey.py	2016-06-17 21:38:32 +0000
+++ lib/lp/archivepublisher/archivesigningkey.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """ArchiveSigningKey implementation."""
@@ -13,8 +13,13 @@
 import os
 
 import gpgme
+from twisted.internet.threads import deferToThread
 from zope.component import getUtility
 from zope.interface import implementer
+from zope.security.proxy import (
+    ProxyFactory,
+    removeSecurityProxy,
+    )
 
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.archivepublisher.config import getPubConfig
@@ -78,7 +83,7 @@
         secret_key = getUtility(IGPGHandler).generateKey(key_displayname)
         self._setupSigningKey(secret_key)
 
-    def setSigningKey(self, key_path):
+    def setSigningKey(self, key_path, async_keyserver=False):
         """See `IArchiveSigningKey`."""
         assert self.archive.signing_key is None, (
             "Cannot override signing_keys.")
@@ -88,22 +93,21 @@
         with open(key_path) as key_file:
             secret_key_export = key_file.read()
         secret_key = getUtility(IGPGHandler).importSecretKey(secret_key_export)
-        self._setupSigningKey(secret_key)
-
-    def _setupSigningKey(self, secret_key):
-        """Mandatory setup for signing keys.
-
-        * Export the secret key into the protected disk location.
-        * Upload public key to the keyserver.
-        * Store the public GPGKey reference in the database and update
-          the context archive.signing_key.
-        """
-        self.exportSecretKey(secret_key)
-
-        gpghandler = getUtility(IGPGHandler)
+        return self._setupSigningKey(
+            secret_key, async_keyserver=async_keyserver)
+
+    def _uploadPublicSigningKey(self, secret_key):
+        """Upload the public half of a signing key to the keyserver."""
+        # The handler's security proxying doesn't protect anything useful
+        # here, and when we're running in a thread we don't have an
+        # interaction.
+        gpghandler = removeSecurityProxy(getUtility(IGPGHandler))
         pub_key = gpghandler.retrieveKey(secret_key.fingerprint)
         gpghandler.uploadPublicKey(pub_key.fingerprint)
+        return pub_key
 
+    def _storeSigningKey(self, pub_key):
+        """Store signing key reference in the database."""
         key_owner = getUtility(ILaunchpadCelebrities).ppa_key_guard
         key, _ = getUtility(IGPGKeySet).activate(
             key_owner, pub_key, pub_key.can_encrypt)
@@ -111,6 +115,31 @@
         self.archive.signing_key_fingerprint = key.fingerprint
         del get_property_cache(self.archive).signing_key
 
+    def _setupSigningKey(self, secret_key, async_keyserver=False):
+        """Mandatory setup for signing keys.
+
+        * Export the secret key into the protected disk location.
+        * Upload public key to the keyserver.
+        * Store the public GPGKey reference in the database and update
+          the context archive.signing_key.
+        """
+        self.exportSecretKey(secret_key)
+        if async_keyserver:
+            # If we have an asynchronous keyserver running in the current
+            # thread using Twisted, then we need some contortions to ensure
+            # that the GPG handler doesn't deadlock.  This is most easily
+            # done by deferring the GPG handler work to another thread.
+            # Since that thread won't have a Zope interaction, we need to
+            # unwrap the security proxy for it.
+            d = deferToThread(
+                self._uploadPublicSigningKey, removeSecurityProxy(secret_key))
+            d.addCallback(ProxyFactory)
+            d.addCallback(self._storeSigningKey)
+            return d
+        else:
+            pub_key = self._uploadPublicSigningKey(secret_key)
+            self._storeSigningKey(pub_key)
+
     def signRepository(self, suite):
         """See `IArchiveSigningKey`."""
         assert self.archive.signing_key is not None, (

=== modified file 'lib/lp/archivepublisher/interfaces/archivesigningkey.py'
--- lib/lp/archivepublisher/interfaces/archivesigningkey.py	2016-06-17 21:07:22 +0000
+++ lib/lp/archivepublisher/interfaces/archivesigningkey.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """ArchiveSigningKey interface."""
@@ -70,9 +70,12 @@
             upload to the keyserver.
         """
 
-    def setSigningKey(key_path):
+    def setSigningKey(key_path, async_keyserver=False):
         """Set a given secret key export as the context archive signing key.
 
+        :param key_path: full path to the secret key.
+        :param async_keyserver: true if the keyserver is running
+            asynchronously in the current thread.
         :raises AssertionError: if the context archive already has a
             `signing_key`.
         :raises AssertionError: if the given 'key_path' does not exist.

=== modified file 'lib/lp/archivepublisher/tests/test_archivesigningkey.py'
--- lib/lp/archivepublisher/tests/test_archivesigningkey.py	2016-06-17 21:07:22 +0000
+++ lib/lp/archivepublisher/tests/test_archivesigningkey.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2016-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test ArchiveSigningKey."""
@@ -7,6 +7,8 @@
 
 import os
 
+from testtools.deferredruntest import AsynchronousDeferredRunTest
+from twisted.internet import defer
 from zope.component import getUtility
 
 from lp.archivepublisher.config import getPubConfig
@@ -18,14 +20,16 @@
 from lp.soyuz.enums import ArchivePurpose
 from lp.testing import TestCaseWithFactory
 from lp.testing.gpgkeys import gpgkeysdir
-from lp.testing.keyserver import KeyServerTac
+from lp.testing.keyserver import InProcessKeyServerFixture
 from lp.testing.layers import ZopelessDatabaseLayer
 
 
 class TestArchiveSigningKey(TestCaseWithFactory):
 
     layer = ZopelessDatabaseLayer
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
 
+    @defer.inlineCallbacks
     def setUp(self):
         super(TestArchiveSigningKey, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
@@ -38,9 +42,11 @@
         self.archive_root = getPubConfig(self.archive).archiveroot
         self.suite = "distroseries"
 
-        with KeyServerTac():
+        with InProcessKeyServerFixture() as keyserver:
+            yield keyserver.start()
             key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-            IArchiveSigningKey(self.archive).setSigningKey(key_path)
+            yield IArchiveSigningKey(self.archive).setSigningKey(
+                key_path, async_keyserver=True)
 
     def test_signfile_absolute_within_archive(self):
         filename = os.path.join(self.archive_root, "signme")

=== modified file 'lib/lp/archivepublisher/tests/test_publishdistro.py'
--- lib/lp/archivepublisher/tests/test_publishdistro.py	2016-11-07 16:42:23 +0000
+++ lib/lp/archivepublisher/tests/test_publishdistro.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Functional tests for publish-distro.py script."""
@@ -11,10 +11,12 @@
 import subprocess
 import sys
 
+from testtools.deferredruntest import AsynchronousDeferredRunTest
 from testtools.matchers import (
     Not,
     PathExists,
     )
+from twisted.internet import defer
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
@@ -47,13 +49,15 @@
 from lp.testing.fakemethod import FakeMethod
 from lp.testing.faketransaction import FakeTransaction
 from lp.testing.gpgkeys import gpgkeysdir
-from lp.testing.keyserver import KeyServerTac
+from lp.testing.keyserver import InProcessKeyServerFixture
 from lp.testing.layers import ZopelessDatabaseLayer
 
 
 class TestPublishDistro(TestNativePublishingBase):
     """Test the publish-distro.py script works properly."""
 
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
+
     def runPublishDistro(self, extra_args=None, distribution="ubuntutest"):
         """Run publish-distro without invoking the script.
 
@@ -222,6 +226,7 @@
         pub_source.sync()
         self.assertEqual(PackagePublishingStatus.PENDING, pub_source.status)
 
+    @defer.inlineCallbacks
     def testForPPA(self):
         """Try to run publish-distro in PPA mode.
 
@@ -247,11 +252,10 @@
         naked_archive.distribution = self.ubuntutest
 
         self.setUpRequireSigningKeys()
-        tac = KeyServerTac()
-        tac.setUp()
-        self.addCleanup(tac.tearDown)
+        yield self.useFixture(InProcessKeyServerFixture()).start()
         key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-        IArchiveSigningKey(cprov.archive).setSigningKey(key_path)
+        yield IArchiveSigningKey(cprov.archive).setSigningKey(
+            key_path, async_keyserver=True)
         name16.archive.signing_key_owner = cprov.archive.signing_key_owner
         name16.archive.signing_key_fingerprint = (
             cprov.archive.signing_key_fingerprint)
@@ -282,6 +286,7 @@
             'ppa/ubuntutest/pool/main/b/bar/bar_666.dsc')
         self.assertEqual('bar', open(bar_path).read().strip())
 
+    @defer.inlineCallbacks
     def testForPrivatePPA(self):
         """Run publish-distro in private PPA mode.
 
@@ -299,11 +304,10 @@
         self.layer.txn.commit()
 
         self.setUpRequireSigningKeys()
-        tac = KeyServerTac()
-        tac.setUp()
-        self.addCleanup(tac.tearDown)
+        yield self.useFixture(InProcessKeyServerFixture()).start()
         key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-        IArchiveSigningKey(private_ppa).setSigningKey(key_path)
+        yield IArchiveSigningKey(private_ppa).setSigningKey(
+            key_path, async_keyserver=True)
 
         # Try a plain PPA run, to ensure the private one is NOT published.
         self.runPublishDistro(['--ppa'])
@@ -398,17 +402,17 @@
             self.config.distsroot)
         self.assertNotExists(index_path)
 
+    @defer.inlineCallbacks
     def testCarefulRelease(self):
         """publish-distro can be asked to just rewrite Release files."""
         archive = self.factory.makeArchive(distribution=self.ubuntutest)
         pub_source = self.getPubSource(filecontent='foo', archive=archive)
 
         self.setUpRequireSigningKeys()
-        tac = KeyServerTac()
-        tac.setUp()
-        self.addCleanup(tac.tearDown)
+        yield self.useFixture(InProcessKeyServerFixture()).start()
         key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-        IArchiveSigningKey(archive).setSigningKey(key_path)
+        yield IArchiveSigningKey(archive).setSigningKey(
+            key_path, async_keyserver=True)
 
         self.layer.txn.commit()
 

=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
--- lib/lp/archivepublisher/tests/test_publisher.py	2016-11-07 16:42:23 +0000
+++ lib/lp/archivepublisher/tests/test_publisher.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for publisher class."""
@@ -32,6 +32,7 @@
 except ImportError:
     from backports import lzma
 import pytz
+from testtools.deferredruntest import AsynchronousDeferredRunTest
 from testtools.matchers import (
     ContainsAll,
     DirContains,
@@ -49,6 +50,7 @@
     SamePath,
     )
 import transaction
+from twisted.internet import defer
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
@@ -101,7 +103,7 @@
 from lp.testing import TestCaseWithFactory
 from lp.testing.fakemethod import FakeMethod
 from lp.testing.gpgkeys import gpgkeysdir
-from lp.testing.keyserver import KeyServerTac
+from lp.testing.keyserver import InProcessKeyServerFixture
 from lp.testing.layers import (
     LaunchpadZopelessLayer,
     ZopelessDatabaseLayer,
@@ -2927,6 +2929,8 @@
 class TestPublisherRepositorySignatures(TestPublisherBase):
     """Testing `Publisher` signature behaviour."""
 
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
+
     archive_publisher = None
 
     def tearDown(self):
@@ -3005,6 +3009,7 @@
         self.assertNotIn('Release.gpg', sync_args[1])
         self.assertNotIn('InRelease', sync_args[1])
 
+    @defer.inlineCallbacks
     def testRepositorySignatureWithSigningKey(self):
         """Check publisher behaviour when signing repositories.
 
@@ -3016,12 +3021,12 @@
         self.assertTrue(cprov.archive.signing_key is None)
 
         # Start the test keyserver, so the signing_key can be uploaded.
-        tac = KeyServerTac()
-        tac.setUp()
+        yield self.useFixture(InProcessKeyServerFixture()).start()
 
         # Set a signing key for Celso's PPA.
         key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-        IArchiveSigningKey(cprov.archive).setSigningKey(key_path)
+        yield IArchiveSigningKey(cprov.archive).setSigningKey(
+            key_path, async_keyserver=True)
         self.assertTrue(cprov.archive.signing_key is not None)
 
         self.setupPublisher(cprov.archive)
@@ -3061,9 +3066,6 @@
         self.assertThat(
             sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
 
-        # All done, turn test-keyserver off.
-        tac.tearDown()
-
 
 class TestPublisherLite(TestCaseWithFactory):
     """Lightweight unit tests for the publisher."""
@@ -3299,7 +3301,9 @@
     """Unit tests for DirectoryHash object, signing functionality."""
 
     layer = ZopelessDatabaseLayer
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
 
+    @defer.inlineCallbacks
     def setUp(self):
         super(TestDirectoryHashSigning, self).setUp()
         self.temp_dir = self.makeTemporaryDirectory()
@@ -3313,13 +3317,11 @@
         self.suite = "distroseries"
 
         # Setup a keyserver so we can install the archive key.
-        tac = KeyServerTac()
-        tac.setUp()
-
-        key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-        IArchiveSigningKey(self.archive).setSigningKey(key_path)
-
-        tac.tearDown()
+        with InProcessKeyServerFixture() as keyserver:
+            yield keyserver.start()
+            key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
+            yield IArchiveSigningKey(self.archive).setSigningKey(
+                key_path, async_keyserver=True)
 
     def test_basic_directory_add_signed(self):
         tmpdir = unicode(self.makeTemporaryDirectory())

=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
--- lib/lp/archivepublisher/tests/test_signing.py	2016-06-22 08:54:11 +0000
+++ lib/lp/archivepublisher/tests/test_signing.py	2017-04-29 15:42:19 +0000
@@ -1,4 +1,4 @@
-# Copyright 2012-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test UEFI custom uploads."""
@@ -10,6 +10,8 @@
 import tarfile
 
 from fixtures import MonkeyPatch
+from testtools.deferredruntest import AsynchronousDeferredRunTest
+from twisted.internet import defer
 from zope.component import getUtility
 
 from lp.archivepublisher.config import getPubConfig
@@ -31,7 +33,7 @@
 from lp.testing import TestCaseWithFactory
 from lp.testing.fakemethod import FakeMethod
 from lp.testing.gpgkeys import gpgkeysdir
-from lp.testing.keyserver import KeyServerTac
+from lp.testing.keyserver import InProcessKeyServerFixture
 from lp.testing.layers import ZopelessDatabaseLayer
 
 
@@ -87,6 +89,7 @@
 class TestSigningHelpers(TestCaseWithFactory):
 
     layer = ZopelessDatabaseLayer
+    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
 
     def setUp(self):
         super(TestSigningHelpers, self).setUp()
@@ -122,10 +125,13 @@
         if not os.path.exists(pubconf.temproot):
             os.makedirs(pubconf.temproot)
 
+    @defer.inlineCallbacks
     def setUpArchiveKey(self):
-        with KeyServerTac():
+        with InProcessKeyServerFixture() as keyserver:
+            yield keyserver.start()
             key_path = os.path.join(gpgkeysdir, 'ppa-sample@xxxxxxxxxxxxxxxxx')
-            IArchiveSigningKey(self.archive).setSigningKey(key_path)
+            yield IArchiveSigningKey(self.archive).setSigningKey(
+                key_path, async_keyserver=True)
 
     def setUpUefiKeys(self, create=True):
         self.key = os.path.join(self.signing_dir, "uefi.key")
@@ -648,11 +654,12 @@
              "1.0", "SHA256SUMS")
         self.assertTrue(os.path.exists(sha256file))
 
+    @defer.inlineCallbacks
     def test_checksumming_tree_signed(self):
         # Specifying no options should leave us with an open tree,
         # confirm it is checksummed.  Supply an archive signing key
         # which should trigger signing of the checksum file.
-        self.setUpArchiveKey()
+        yield self.setUpArchiveKey()
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.openArchive("test", "1.0", "amd64")

=== modified file 'lib/lp/testing/keyserver/__init__.py'
--- lib/lp/testing/keyserver/__init__.py	2013-01-07 02:40:55 +0000
+++ lib/lp/testing/keyserver/__init__.py	2017-04-29 15:42:19 +0000
@@ -1,8 +1,10 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the GNU
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the GNU
 # Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
+    'InProcessKeyServerFixture',
     'KeyServerTac',
     ]
 
 from lp.testing.keyserver.harness import KeyServerTac
+from lp.testing.keyserver.inprocess import InProcessKeyServerFixture

=== added file 'lib/lp/testing/keyserver/inprocess.py'
--- lib/lp/testing/keyserver/inprocess.py	1970-01-01 00:00:00 +0000
+++ lib/lp/testing/keyserver/inprocess.py	2017-04-29 15:42:19 +0000
@@ -0,0 +1,69 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""In-process keyserver fixture."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+    'InProcessKeyServerFixture',
+    ]
+
+from textwrap import dedent
+
+from fixtures import (
+    Fixture,
+    TempDir,
+    )
+from twisted.internet import (
+    defer,
+    endpoints,
+    reactor,
+    )
+from twisted.python.compat import nativeString
+from twisted.web import server
+
+from lp.services.config import config
+from lp.testing.keyserver.web import KeyServerResource
+
+
+class InProcessKeyServerFixture(Fixture):
+    """A fixture that runs an in-process key server.
+
+    This is much faster than the out-of-process `KeyServerTac`, but it can
+    only be used if all the tests relying on it are asynchronous.
+
+    Users of this fixture must call the `start` method, which returns a
+    `Deferred`, and arrange for that to get back to the reactor.  This is
+    necessary because the basic fixture API does not allow `setUp` to return
+    anything.  For example:
+
+        class TestSomething(TestCase):
+
+            run_tests_with = AsynchronousDeferredRunTest.make_factory(
+                timeout=10)
+
+            @defer.inlineCallbacks
+            def setUp(self):
+                super(TestSomething, self).setUp()
+                yield self.useFixture(InProcessKeyServerFixture()).start()
+    """
+
+    @defer.inlineCallbacks
+    def start(self):
+        resource = KeyServerResource(self.useFixture(TempDir()).path)
+        endpoint = endpoints.serverFromString(reactor, nativeString("tcp:0"))
+        port = yield endpoint.listen(server.Site(resource))
+        self.addCleanup(port.stopListening)
+        config.push("in-process-key-server-fixture", dedent("""
+            [gpghandler]
+            port: %s
+            """) % port.getHost().port)
+        self.addCleanup(config.pop, "in-process-key-server-fixture")
+
+    @property
+    def url(self):
+        """The URL that the web server will be running on."""
+        return ("http://%s:%d"; % (
+            config.gpghandler.host, config.gpghandler.port)).encode("UTF-8")

=== added file 'lib/lp/testing/keyserver/tests/test_inprocess.py'
--- lib/lp/testing/keyserver/tests/test_inprocess.py	1970-01-01 00:00:00 +0000
+++ lib/lp/testing/keyserver/tests/test_inprocess.py	2017-04-29 15:42:19 +0000
@@ -0,0 +1,44 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""In-process keyserver fixture tests."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+
+from testtools.deferredruntest import (
+    AsynchronousDeferredRunTestForBrokenTwisted,
+    )
+from twisted.internet import defer
+from twisted.web.client import getPage
+
+from lp.services.config import config
+from lp.testing import TestCase
+from lp.testing.keyserver import InProcessKeyServerFixture
+from lp.testing.keyserver.web import GREETING
+
+
+class TestInProcessKeyServerFixture(TestCase):
+
+    run_tests_with = AsynchronousDeferredRunTestForBrokenTwisted.make_factory(
+        timeout=10)
+
+    @defer.inlineCallbacks
+    def test_url(self):
+        # The url is the one that gpghandler is configured to hit.
+        fixture = self.useFixture(InProcessKeyServerFixture())
+        yield fixture.start()
+        self.assertEqual(
+            ("http://%s:%d"; % (
+                config.gpghandler.host,
+                config.gpghandler.port)).encode("UTF-8"),
+            fixture.url)
+
+    @defer.inlineCallbacks
+    def test_starts_properly(self):
+        # The fixture starts properly and we can load the page.
+        fixture = self.useFixture(InProcessKeyServerFixture())
+        yield fixture.start()
+        content = yield getPage(fixture.url)
+        self.assertEqual(GREETING, content)


Follow ups