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

Proposed by Colin Watson
Status: Merged
Merged at revision: 18380
Proposed branch: lp:~cjwatson/launchpad/faster-archive-signing-key-tests
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/composeBuildRequest-deferred
Diff against target: 594 lines (+222/-56)
9 files modified
lib/lp/archivepublisher/archivesigningkey.py (+44/-15)
lib/lp/archivepublisher/interfaces/archivesigningkey.py (+5/-2)
lib/lp/archivepublisher/tests/test_archivesigningkey.py (+10/-4)
lib/lp/archivepublisher/tests/test_publishdistro.py (+18/-14)
lib/lp/archivepublisher/tests/test_publisher.py (+17/-15)
lib/lp/archivepublisher/tests/test_signing.py (+12/-5)
lib/lp/testing/keyserver/__init__.py (+3/-1)
lib/lp/testing/keyserver/inprocess.py (+69/-0)
lib/lp/testing/keyserver/tests/test_inprocess.py (+44/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/faster-archive-signing-key-tests
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+323429@code.launchpad.net

Commit message

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

Description of the change

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.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/archivesigningkey.py'
2--- lib/lp/archivepublisher/archivesigningkey.py 2016-06-17 21:38:32 +0000
3+++ lib/lp/archivepublisher/archivesigningkey.py 2017-04-29 15:42:19 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """ArchiveSigningKey implementation."""
10@@ -13,8 +13,13 @@
11 import os
12
13 import gpgme
14+from twisted.internet.threads import deferToThread
15 from zope.component import getUtility
16 from zope.interface import implementer
17+from zope.security.proxy import (
18+ ProxyFactory,
19+ removeSecurityProxy,
20+ )
21
22 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
23 from lp.archivepublisher.config import getPubConfig
24@@ -78,7 +83,7 @@
25 secret_key = getUtility(IGPGHandler).generateKey(key_displayname)
26 self._setupSigningKey(secret_key)
27
28- def setSigningKey(self, key_path):
29+ def setSigningKey(self, key_path, async_keyserver=False):
30 """See `IArchiveSigningKey`."""
31 assert self.archive.signing_key is None, (
32 "Cannot override signing_keys.")
33@@ -88,22 +93,21 @@
34 with open(key_path) as key_file:
35 secret_key_export = key_file.read()
36 secret_key = getUtility(IGPGHandler).importSecretKey(secret_key_export)
37- self._setupSigningKey(secret_key)
38-
39- def _setupSigningKey(self, secret_key):
40- """Mandatory setup for signing keys.
41-
42- * Export the secret key into the protected disk location.
43- * Upload public key to the keyserver.
44- * Store the public GPGKey reference in the database and update
45- the context archive.signing_key.
46- """
47- self.exportSecretKey(secret_key)
48-
49- gpghandler = getUtility(IGPGHandler)
50+ return self._setupSigningKey(
51+ secret_key, async_keyserver=async_keyserver)
52+
53+ def _uploadPublicSigningKey(self, secret_key):
54+ """Upload the public half of a signing key to the keyserver."""
55+ # The handler's security proxying doesn't protect anything useful
56+ # here, and when we're running in a thread we don't have an
57+ # interaction.
58+ gpghandler = removeSecurityProxy(getUtility(IGPGHandler))
59 pub_key = gpghandler.retrieveKey(secret_key.fingerprint)
60 gpghandler.uploadPublicKey(pub_key.fingerprint)
61+ return pub_key
62
63+ def _storeSigningKey(self, pub_key):
64+ """Store signing key reference in the database."""
65 key_owner = getUtility(ILaunchpadCelebrities).ppa_key_guard
66 key, _ = getUtility(IGPGKeySet).activate(
67 key_owner, pub_key, pub_key.can_encrypt)
68@@ -111,6 +115,31 @@
69 self.archive.signing_key_fingerprint = key.fingerprint
70 del get_property_cache(self.archive).signing_key
71
72+ def _setupSigningKey(self, secret_key, async_keyserver=False):
73+ """Mandatory setup for signing keys.
74+
75+ * Export the secret key into the protected disk location.
76+ * Upload public key to the keyserver.
77+ * Store the public GPGKey reference in the database and update
78+ the context archive.signing_key.
79+ """
80+ self.exportSecretKey(secret_key)
81+ if async_keyserver:
82+ # If we have an asynchronous keyserver running in the current
83+ # thread using Twisted, then we need some contortions to ensure
84+ # that the GPG handler doesn't deadlock. This is most easily
85+ # done by deferring the GPG handler work to another thread.
86+ # Since that thread won't have a Zope interaction, we need to
87+ # unwrap the security proxy for it.
88+ d = deferToThread(
89+ self._uploadPublicSigningKey, removeSecurityProxy(secret_key))
90+ d.addCallback(ProxyFactory)
91+ d.addCallback(self._storeSigningKey)
92+ return d
93+ else:
94+ pub_key = self._uploadPublicSigningKey(secret_key)
95+ self._storeSigningKey(pub_key)
96+
97 def signRepository(self, suite):
98 """See `IArchiveSigningKey`."""
99 assert self.archive.signing_key is not None, (
100
101=== modified file 'lib/lp/archivepublisher/interfaces/archivesigningkey.py'
102--- lib/lp/archivepublisher/interfaces/archivesigningkey.py 2016-06-17 21:07:22 +0000
103+++ lib/lp/archivepublisher/interfaces/archivesigningkey.py 2017-04-29 15:42:19 +0000
104@@ -1,4 +1,4 @@
105-# Copyright 2009 Canonical Ltd. This software is licensed under the
106+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
107 # GNU Affero General Public License version 3 (see the file LICENSE).
108
109 """ArchiveSigningKey interface."""
110@@ -70,9 +70,12 @@
111 upload to the keyserver.
112 """
113
114- def setSigningKey(key_path):
115+ def setSigningKey(key_path, async_keyserver=False):
116 """Set a given secret key export as the context archive signing key.
117
118+ :param key_path: full path to the secret key.
119+ :param async_keyserver: true if the keyserver is running
120+ asynchronously in the current thread.
121 :raises AssertionError: if the context archive already has a
122 `signing_key`.
123 :raises AssertionError: if the given 'key_path' does not exist.
124
125=== modified file 'lib/lp/archivepublisher/tests/test_archivesigningkey.py'
126--- lib/lp/archivepublisher/tests/test_archivesigningkey.py 2016-06-17 21:07:22 +0000
127+++ lib/lp/archivepublisher/tests/test_archivesigningkey.py 2017-04-29 15:42:19 +0000
128@@ -1,4 +1,4 @@
129-# Copyright 2016 Canonical Ltd. This software is licensed under the
130+# Copyright 2016-2017 Canonical Ltd. This software is licensed under the
131 # GNU Affero General Public License version 3 (see the file LICENSE).
132
133 """Test ArchiveSigningKey."""
134@@ -7,6 +7,8 @@
135
136 import os
137
138+from testtools.deferredruntest import AsynchronousDeferredRunTest
139+from twisted.internet import defer
140 from zope.component import getUtility
141
142 from lp.archivepublisher.config import getPubConfig
143@@ -18,14 +20,16 @@
144 from lp.soyuz.enums import ArchivePurpose
145 from lp.testing import TestCaseWithFactory
146 from lp.testing.gpgkeys import gpgkeysdir
147-from lp.testing.keyserver import KeyServerTac
148+from lp.testing.keyserver import InProcessKeyServerFixture
149 from lp.testing.layers import ZopelessDatabaseLayer
150
151
152 class TestArchiveSigningKey(TestCaseWithFactory):
153
154 layer = ZopelessDatabaseLayer
155+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
156
157+ @defer.inlineCallbacks
158 def setUp(self):
159 super(TestArchiveSigningKey, self).setUp()
160 self.temp_dir = self.makeTemporaryDirectory()
161@@ -38,9 +42,11 @@
162 self.archive_root = getPubConfig(self.archive).archiveroot
163 self.suite = "distroseries"
164
165- with KeyServerTac():
166+ with InProcessKeyServerFixture() as keyserver:
167+ yield keyserver.start()
168 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
169- IArchiveSigningKey(self.archive).setSigningKey(key_path)
170+ yield IArchiveSigningKey(self.archive).setSigningKey(
171+ key_path, async_keyserver=True)
172
173 def test_signfile_absolute_within_archive(self):
174 filename = os.path.join(self.archive_root, "signme")
175
176=== modified file 'lib/lp/archivepublisher/tests/test_publishdistro.py'
177--- lib/lp/archivepublisher/tests/test_publishdistro.py 2016-11-07 16:42:23 +0000
178+++ lib/lp/archivepublisher/tests/test_publishdistro.py 2017-04-29 15:42:19 +0000
179@@ -1,4 +1,4 @@
180-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
181+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
182 # GNU Affero General Public License version 3 (see the file LICENSE).
183
184 """Functional tests for publish-distro.py script."""
185@@ -11,10 +11,12 @@
186 import subprocess
187 import sys
188
189+from testtools.deferredruntest import AsynchronousDeferredRunTest
190 from testtools.matchers import (
191 Not,
192 PathExists,
193 )
194+from twisted.internet import defer
195 from zope.component import getUtility
196 from zope.security.proxy import removeSecurityProxy
197
198@@ -47,13 +49,15 @@
199 from lp.testing.fakemethod import FakeMethod
200 from lp.testing.faketransaction import FakeTransaction
201 from lp.testing.gpgkeys import gpgkeysdir
202-from lp.testing.keyserver import KeyServerTac
203+from lp.testing.keyserver import InProcessKeyServerFixture
204 from lp.testing.layers import ZopelessDatabaseLayer
205
206
207 class TestPublishDistro(TestNativePublishingBase):
208 """Test the publish-distro.py script works properly."""
209
210+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
211+
212 def runPublishDistro(self, extra_args=None, distribution="ubuntutest"):
213 """Run publish-distro without invoking the script.
214
215@@ -222,6 +226,7 @@
216 pub_source.sync()
217 self.assertEqual(PackagePublishingStatus.PENDING, pub_source.status)
218
219+ @defer.inlineCallbacks
220 def testForPPA(self):
221 """Try to run publish-distro in PPA mode.
222
223@@ -247,11 +252,10 @@
224 naked_archive.distribution = self.ubuntutest
225
226 self.setUpRequireSigningKeys()
227- tac = KeyServerTac()
228- tac.setUp()
229- self.addCleanup(tac.tearDown)
230+ yield self.useFixture(InProcessKeyServerFixture()).start()
231 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
232- IArchiveSigningKey(cprov.archive).setSigningKey(key_path)
233+ yield IArchiveSigningKey(cprov.archive).setSigningKey(
234+ key_path, async_keyserver=True)
235 name16.archive.signing_key_owner = cprov.archive.signing_key_owner
236 name16.archive.signing_key_fingerprint = (
237 cprov.archive.signing_key_fingerprint)
238@@ -282,6 +286,7 @@
239 'ppa/ubuntutest/pool/main/b/bar/bar_666.dsc')
240 self.assertEqual('bar', open(bar_path).read().strip())
241
242+ @defer.inlineCallbacks
243 def testForPrivatePPA(self):
244 """Run publish-distro in private PPA mode.
245
246@@ -299,11 +304,10 @@
247 self.layer.txn.commit()
248
249 self.setUpRequireSigningKeys()
250- tac = KeyServerTac()
251- tac.setUp()
252- self.addCleanup(tac.tearDown)
253+ yield self.useFixture(InProcessKeyServerFixture()).start()
254 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
255- IArchiveSigningKey(private_ppa).setSigningKey(key_path)
256+ yield IArchiveSigningKey(private_ppa).setSigningKey(
257+ key_path, async_keyserver=True)
258
259 # Try a plain PPA run, to ensure the private one is NOT published.
260 self.runPublishDistro(['--ppa'])
261@@ -398,17 +402,17 @@
262 self.config.distsroot)
263 self.assertNotExists(index_path)
264
265+ @defer.inlineCallbacks
266 def testCarefulRelease(self):
267 """publish-distro can be asked to just rewrite Release files."""
268 archive = self.factory.makeArchive(distribution=self.ubuntutest)
269 pub_source = self.getPubSource(filecontent='foo', archive=archive)
270
271 self.setUpRequireSigningKeys()
272- tac = KeyServerTac()
273- tac.setUp()
274- self.addCleanup(tac.tearDown)
275+ yield self.useFixture(InProcessKeyServerFixture()).start()
276 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
277- IArchiveSigningKey(archive).setSigningKey(key_path)
278+ yield IArchiveSigningKey(archive).setSigningKey(
279+ key_path, async_keyserver=True)
280
281 self.layer.txn.commit()
282
283
284=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
285--- lib/lp/archivepublisher/tests/test_publisher.py 2016-11-07 16:42:23 +0000
286+++ lib/lp/archivepublisher/tests/test_publisher.py 2017-04-29 15:42:19 +0000
287@@ -1,4 +1,4 @@
288-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
289+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
290 # GNU Affero General Public License version 3 (see the file LICENSE).
291
292 """Tests for publisher class."""
293@@ -32,6 +32,7 @@
294 except ImportError:
295 from backports import lzma
296 import pytz
297+from testtools.deferredruntest import AsynchronousDeferredRunTest
298 from testtools.matchers import (
299 ContainsAll,
300 DirContains,
301@@ -49,6 +50,7 @@
302 SamePath,
303 )
304 import transaction
305+from twisted.internet import defer
306 from zope.component import getUtility
307 from zope.security.proxy import removeSecurityProxy
308
309@@ -101,7 +103,7 @@
310 from lp.testing import TestCaseWithFactory
311 from lp.testing.fakemethod import FakeMethod
312 from lp.testing.gpgkeys import gpgkeysdir
313-from lp.testing.keyserver import KeyServerTac
314+from lp.testing.keyserver import InProcessKeyServerFixture
315 from lp.testing.layers import (
316 LaunchpadZopelessLayer,
317 ZopelessDatabaseLayer,
318@@ -2927,6 +2929,8 @@
319 class TestPublisherRepositorySignatures(TestPublisherBase):
320 """Testing `Publisher` signature behaviour."""
321
322+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
323+
324 archive_publisher = None
325
326 def tearDown(self):
327@@ -3005,6 +3009,7 @@
328 self.assertNotIn('Release.gpg', sync_args[1])
329 self.assertNotIn('InRelease', sync_args[1])
330
331+ @defer.inlineCallbacks
332 def testRepositorySignatureWithSigningKey(self):
333 """Check publisher behaviour when signing repositories.
334
335@@ -3016,12 +3021,12 @@
336 self.assertTrue(cprov.archive.signing_key is None)
337
338 # Start the test keyserver, so the signing_key can be uploaded.
339- tac = KeyServerTac()
340- tac.setUp()
341+ yield self.useFixture(InProcessKeyServerFixture()).start()
342
343 # Set a signing key for Celso's PPA.
344 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
345- IArchiveSigningKey(cprov.archive).setSigningKey(key_path)
346+ yield IArchiveSigningKey(cprov.archive).setSigningKey(
347+ key_path, async_keyserver=True)
348 self.assertTrue(cprov.archive.signing_key is not None)
349
350 self.setupPublisher(cprov.archive)
351@@ -3061,9 +3066,6 @@
352 self.assertThat(
353 sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease']))
354
355- # All done, turn test-keyserver off.
356- tac.tearDown()
357-
358
359 class TestPublisherLite(TestCaseWithFactory):
360 """Lightweight unit tests for the publisher."""
361@@ -3299,7 +3301,9 @@
362 """Unit tests for DirectoryHash object, signing functionality."""
363
364 layer = ZopelessDatabaseLayer
365+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
366
367+ @defer.inlineCallbacks
368 def setUp(self):
369 super(TestDirectoryHashSigning, self).setUp()
370 self.temp_dir = self.makeTemporaryDirectory()
371@@ -3313,13 +3317,11 @@
372 self.suite = "distroseries"
373
374 # Setup a keyserver so we can install the archive key.
375- tac = KeyServerTac()
376- tac.setUp()
377-
378- key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
379- IArchiveSigningKey(self.archive).setSigningKey(key_path)
380-
381- tac.tearDown()
382+ with InProcessKeyServerFixture() as keyserver:
383+ yield keyserver.start()
384+ key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
385+ yield IArchiveSigningKey(self.archive).setSigningKey(
386+ key_path, async_keyserver=True)
387
388 def test_basic_directory_add_signed(self):
389 tmpdir = unicode(self.makeTemporaryDirectory())
390
391=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
392--- lib/lp/archivepublisher/tests/test_signing.py 2016-06-22 08:54:11 +0000
393+++ lib/lp/archivepublisher/tests/test_signing.py 2017-04-29 15:42:19 +0000
394@@ -1,4 +1,4 @@
395-# Copyright 2012-2016 Canonical Ltd. This software is licensed under the
396+# Copyright 2012-2017 Canonical Ltd. This software is licensed under the
397 # GNU Affero General Public License version 3 (see the file LICENSE).
398
399 """Test UEFI custom uploads."""
400@@ -10,6 +10,8 @@
401 import tarfile
402
403 from fixtures import MonkeyPatch
404+from testtools.deferredruntest import AsynchronousDeferredRunTest
405+from twisted.internet import defer
406 from zope.component import getUtility
407
408 from lp.archivepublisher.config import getPubConfig
409@@ -31,7 +33,7 @@
410 from lp.testing import TestCaseWithFactory
411 from lp.testing.fakemethod import FakeMethod
412 from lp.testing.gpgkeys import gpgkeysdir
413-from lp.testing.keyserver import KeyServerTac
414+from lp.testing.keyserver import InProcessKeyServerFixture
415 from lp.testing.layers import ZopelessDatabaseLayer
416
417
418@@ -87,6 +89,7 @@
419 class TestSigningHelpers(TestCaseWithFactory):
420
421 layer = ZopelessDatabaseLayer
422+ run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10)
423
424 def setUp(self):
425 super(TestSigningHelpers, self).setUp()
426@@ -122,10 +125,13 @@
427 if not os.path.exists(pubconf.temproot):
428 os.makedirs(pubconf.temproot)
429
430+ @defer.inlineCallbacks
431 def setUpArchiveKey(self):
432- with KeyServerTac():
433+ with InProcessKeyServerFixture() as keyserver:
434+ yield keyserver.start()
435 key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec')
436- IArchiveSigningKey(self.archive).setSigningKey(key_path)
437+ yield IArchiveSigningKey(self.archive).setSigningKey(
438+ key_path, async_keyserver=True)
439
440 def setUpUefiKeys(self, create=True):
441 self.key = os.path.join(self.signing_dir, "uefi.key")
442@@ -648,11 +654,12 @@
443 "1.0", "SHA256SUMS")
444 self.assertTrue(os.path.exists(sha256file))
445
446+ @defer.inlineCallbacks
447 def test_checksumming_tree_signed(self):
448 # Specifying no options should leave us with an open tree,
449 # confirm it is checksummed. Supply an archive signing key
450 # which should trigger signing of the checksum file.
451- self.setUpArchiveKey()
452+ yield self.setUpArchiveKey()
453 self.setUpUefiKeys()
454 self.setUpKmodKeys()
455 self.openArchive("test", "1.0", "amd64")
456
457=== modified file 'lib/lp/testing/keyserver/__init__.py'
458--- lib/lp/testing/keyserver/__init__.py 2013-01-07 02:40:55 +0000
459+++ lib/lp/testing/keyserver/__init__.py 2017-04-29 15:42:19 +0000
460@@ -1,8 +1,10 @@
461-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the GNU
462+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the GNU
463 # Affero General Public License version 3 (see the file LICENSE).
464
465 __all__ = [
466+ 'InProcessKeyServerFixture',
467 'KeyServerTac',
468 ]
469
470 from lp.testing.keyserver.harness import KeyServerTac
471+from lp.testing.keyserver.inprocess import InProcessKeyServerFixture
472
473=== added file 'lib/lp/testing/keyserver/inprocess.py'
474--- lib/lp/testing/keyserver/inprocess.py 1970-01-01 00:00:00 +0000
475+++ lib/lp/testing/keyserver/inprocess.py 2017-04-29 15:42:19 +0000
476@@ -0,0 +1,69 @@
477+# Copyright 2017 Canonical Ltd. This software is licensed under the
478+# GNU Affero General Public License version 3 (see the file LICENSE).
479+
480+"""In-process keyserver fixture."""
481+
482+from __future__ import absolute_import, print_function, unicode_literals
483+
484+__metaclass__ = type
485+__all__ = [
486+ 'InProcessKeyServerFixture',
487+ ]
488+
489+from textwrap import dedent
490+
491+from fixtures import (
492+ Fixture,
493+ TempDir,
494+ )
495+from twisted.internet import (
496+ defer,
497+ endpoints,
498+ reactor,
499+ )
500+from twisted.python.compat import nativeString
501+from twisted.web import server
502+
503+from lp.services.config import config
504+from lp.testing.keyserver.web import KeyServerResource
505+
506+
507+class InProcessKeyServerFixture(Fixture):
508+ """A fixture that runs an in-process key server.
509+
510+ This is much faster than the out-of-process `KeyServerTac`, but it can
511+ only be used if all the tests relying on it are asynchronous.
512+
513+ Users of this fixture must call the `start` method, which returns a
514+ `Deferred`, and arrange for that to get back to the reactor. This is
515+ necessary because the basic fixture API does not allow `setUp` to return
516+ anything. For example:
517+
518+ class TestSomething(TestCase):
519+
520+ run_tests_with = AsynchronousDeferredRunTest.make_factory(
521+ timeout=10)
522+
523+ @defer.inlineCallbacks
524+ def setUp(self):
525+ super(TestSomething, self).setUp()
526+ yield self.useFixture(InProcessKeyServerFixture()).start()
527+ """
528+
529+ @defer.inlineCallbacks
530+ def start(self):
531+ resource = KeyServerResource(self.useFixture(TempDir()).path)
532+ endpoint = endpoints.serverFromString(reactor, nativeString("tcp:0"))
533+ port = yield endpoint.listen(server.Site(resource))
534+ self.addCleanup(port.stopListening)
535+ config.push("in-process-key-server-fixture", dedent("""
536+ [gpghandler]
537+ port: %s
538+ """) % port.getHost().port)
539+ self.addCleanup(config.pop, "in-process-key-server-fixture")
540+
541+ @property
542+ def url(self):
543+ """The URL that the web server will be running on."""
544+ return ("http://%s:%d" % (
545+ config.gpghandler.host, config.gpghandler.port)).encode("UTF-8")
546
547=== added file 'lib/lp/testing/keyserver/tests/test_inprocess.py'
548--- lib/lp/testing/keyserver/tests/test_inprocess.py 1970-01-01 00:00:00 +0000
549+++ lib/lp/testing/keyserver/tests/test_inprocess.py 2017-04-29 15:42:19 +0000
550@@ -0,0 +1,44 @@
551+# Copyright 2017 Canonical Ltd. This software is licensed under the
552+# GNU Affero General Public License version 3 (see the file LICENSE).
553+
554+"""In-process keyserver fixture tests."""
555+
556+from __future__ import absolute_import, print_function, unicode_literals
557+
558+__metaclass__ = type
559+
560+from testtools.deferredruntest import (
561+ AsynchronousDeferredRunTestForBrokenTwisted,
562+ )
563+from twisted.internet import defer
564+from twisted.web.client import getPage
565+
566+from lp.services.config import config
567+from lp.testing import TestCase
568+from lp.testing.keyserver import InProcessKeyServerFixture
569+from lp.testing.keyserver.web import GREETING
570+
571+
572+class TestInProcessKeyServerFixture(TestCase):
573+
574+ run_tests_with = AsynchronousDeferredRunTestForBrokenTwisted.make_factory(
575+ timeout=10)
576+
577+ @defer.inlineCallbacks
578+ def test_url(self):
579+ # The url is the one that gpghandler is configured to hit.
580+ fixture = self.useFixture(InProcessKeyServerFixture())
581+ yield fixture.start()
582+ self.assertEqual(
583+ ("http://%s:%d" % (
584+ config.gpghandler.host,
585+ config.gpghandler.port)).encode("UTF-8"),
586+ fixture.url)
587+
588+ @defer.inlineCallbacks
589+ def test_starts_properly(self):
590+ # The fixture starts properly and we can load the page.
591+ fixture = self.useFixture(InProcessKeyServerFixture())
592+ yield fixture.start()
593+ content = yield getPage(fixture.url)
594+ self.assertEqual(GREETING, content)