launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01646
[Merge] lp:~lifeless/launchpad/librarian into lp:launchpad/devel
Robert Collins has proposed merging lp:~lifeless/launchpad/librarian into lp:launchpad/devel with lp:~lifeless/launchpad/databasefixture as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#661009 rebooting buildbot slave environment doesn't remove test librarian pid
https://bugs.launchpad.net/bugs/661009
#663165 librarian logs should be attached to tests
https://bugs.launchpad.net/bugs/663165
Prepare the librarian for paralleltests, or at least approximate that.
Highlights:
- newer fixtures, to get the useFixture helper there
- allocate ports in the librarian, and use a passed-via-environment root, if LP_TEST_INSTANCE is set
- use the exposed LibrarianServerFixture.service_config to push a config (and its pushed onto disk, so that other helpers will pick it up).
- snarf the log file and put it in tests (a bit crude, we don't get 'log for the test', we get 'current log')
--
https://code.launchpad.net/~lifeless/launchpad/librarian/+merge/39013
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/librarian into lp:launchpad/devel.
=== modified file 'Makefile'
--- Makefile 2010-10-21 03:22:06 +0000
+++ Makefile 2010-10-21 04:51:25 +0000
@@ -355,7 +355,7 @@
/var/tmp/bzrsync \
/var/tmp/codehosting.test \
/var/tmp/codeimport \
- /var/tmp/fatsam.appserver \
+ /var/tmp/fatsam.test \
/var/tmp/lperr \
/var/tmp/lperr.test \
/var/tmp/mailman \
=== modified file 'daemons/librarian.tac'
--- daemons/librarian.tac 2010-10-20 18:43:29 +0000
+++ daemons/librarian.tac 2010-10-21 04:51:25 +0000
@@ -4,6 +4,7 @@
# Twisted Application Configuration file.
# Use with "twistd2.4 -y <file.tac>", e.g. "twistd -noy server.tac"
+import os
import signal
from meliae import scanner
@@ -25,7 +26,12 @@
dbconfig.setConfigSection('librarian')
execute_zcml_for_scripts()
-path = config.librarian_server.root
+if os.environ.get('LP_TEST_INSTANCE'):
+ # Running in ephemeral mode: get the root dir from the environment and
+ # dynamically allocate ports.
+ path = os.environ['LP_LIBRARIAN_ROOT']
+else:
+ path = config.librarian_server.root
if config.librarian_server.upstream_host:
upstreamHost = config.librarian_server.upstream_host
upstreamPort = config.librarian_server.upstream_port
@@ -61,15 +67,19 @@
site.displayTracebacks = False
strports.service(str(webPort), site).setServiceParent(librarianService)
-# Set up the public librarian.
-uploadPort = config.librarian.upload_port
-webPort = config.librarian.download_port
-setUpListener(uploadPort, webPort, restricted=False)
-
-# Set up the restricted librarian.
-webPort = config.librarian.restricted_download_port
-uploadPort = config.librarian.restricted_upload_port
-setUpListener(uploadPort, webPort, restricted=True)
+if os.environ.get('LP_TEST_INSTANCE'):
+ # Running in ephemeral mode: allocate ports on demand.
+ setUpListener(0, 0, restricted=False)
+ setUpListener(0, 0, restricted=True)
+else:
+ # Set up the public librarian.
+ uploadPort = config.librarian.upload_port
+ webPort = config.librarian.download_port
+ setUpListener(uploadPort, webPort, restricted=False)
+ # Set up the restricted librarian.
+ webPort = config.librarian.restricted_download_port
+ uploadPort = config.librarian.restricted_upload_port
+ setUpListener(uploadPort, webPort, restricted=True)
# Log OOPS reports
set_up_oops_reporting('librarian', 'librarian')
=== modified file 'lib/canonical/launchpad/doc/old-testing.txt'
--- lib/canonical/launchpad/doc/old-testing.txt 2010-10-17 18:31:43 +0000
+++ lib/canonical/launchpad/doc/old-testing.txt 2010-10-21 04:51:25 +0000
@@ -138,22 +138,22 @@
>>> lpsetup.tearDown()
-LibrarianTestSetup
-------------------
+LibrarianServerFixture
+----------------------
Code that needs to access the Librarian can do so easily. Note that
-LibrarianTestSetup requires the Launchpad database to be available, and
+LibrarianServerFixture requires the Launchpad database to be available, and
thus requires LaunchpadTestSetup or similar to be used in tandam.
You probably really want LaunchpadFunctionalLayer so you can access
the Librarian as a Utility.
->>> from canonical.librarian.testing.server import LibrarianTestSetup
+>>> from canonical.librarian.testing.server import LibrarianServerFixture
>>> from canonical.launchpad.ftests import login, ANONYMOUS
>>> from zope.app import zapi
>>> from canonical.librarian.interfaces import ILibrarianClient
>>> from StringIO import StringIO
->>> librarian = LibrarianTestSetup()
+>>> librarian = LibrarianServerFixture()
>>> librarian.setUp()
>>> login(ANONYMOUS)
=== modified file 'lib/canonical/librarian/testing/server.py'
--- lib/canonical/librarian/testing/server.py 2010-09-27 02:08:32 +0000
+++ lib/canonical/librarian/testing/server.py 2010-10-21 04:51:25 +0000
@@ -5,18 +5,20 @@
__metaclass__ = type
__all__ = [
- 'cleanupLibrarianFiles',
'fillLibrarianFile',
'LibrarianServerFixture',
- 'LibrarianTestSetup',
]
import os
import shutil
import tempfile
+from textwrap import dedent
import warnings
-from fixtures import Fixture
+from fixtures import (
+ Fixture,
+ FunctionFixture,
+ )
import canonical
from canonical.config import config
@@ -32,74 +34,14 @@
class LibrarianServerFixture(TacTestSetup):
"""Librarian server fixture.
- >>> from urllib import urlopen
- >>> from canonical.config import config
-
- >>> librarian_url = "http://%s:%d" % (
- ... config.librarian.download_host,
- ... config.librarian.download_port)
- >>> restricted_librarian_url = "http://%s:%d" % (
- ... config.librarian.restricted_download_host,
- ... config.librarian.restricted_download_port)
-
- >>> fixture = LibrarianServerFixture()
- >>> fixture.setUp()
-
- Set a socket timeout, so that this test cannot hang indefinitely.
-
- >>> import socket
- >>> print socket.getdefaulttimeout()
- None
- >>> socket.setdefaulttimeout(1)
-
- After setUp() is called, two librarian ports are available:
- The regular one:
-
- >>> 'Copyright' in urlopen(librarian_url).read()
- True
-
- And the restricted one:
-
- >>> 'Copyright' in urlopen(restricted_librarian_url).read()
- True
-
- The librarian root is also available.
-
- >>> import os
- >>> os.path.isdir(config.librarian_server.root)
- True
-
- After tearDown() is called, both ports are closed:
-
- >>> fixture.tearDown()
-
- >>> urlopen(librarian_url).read()
- Traceback (most recent call last):
- ...
- IOError: ...
-
- >>> urlopen(restricted_librarian_url).read()
- Traceback (most recent call last):
- ...
- IOError: ...
-
- The root directory was removed:
-
- >>> os.path.exists(config.librarian_server.root)
- False
-
- That fixture can be started and stopped multiple time in succession:
-
- >>> fixture.setUp()
- >>> 'Copyright' in urlopen(librarian_url).read()
- True
-
- Tidy up.
-
- >>> fixture.tearDown()
- >>> socket.setdefaulttimeout(None)
-
- :ivar: pid pid of the external process.
+ :ivar service_config: A config fragment with the variables for this
+ service.
+ :ivar root: the root of the server storage area.
+ :ivar upload_port: the port to upload on.
+ :ivar download_port: the port to download from.
+ :ivar restricted_upload_port: the port to upload restricted files on.
+ :ivar restricted_download_port: the port to upload restricted files from.
+ :ivar pid: pid of the external process.
"""
def __init__(self):
@@ -140,9 +82,8 @@
def clear(self):
"""Clear all files from the Librarian"""
# Make this smarter if our tests create huge numbers of files
- root = config.librarian_server.root
- if os.path.isdir(os.path.join(root, '00')):
- shutil.rmtree(os.path.join(root, '00'))
+ if os.path.isdir(os.path.join(self.root, '00')):
+ shutil.rmtree(os.path.join(self.root, '00'))
@property
def pid(self):
@@ -155,22 +96,87 @@
def _read_pid(self):
return get_pid_from_file(self.pidfile)
+ def _dynamic_config(self):
+ """Is a dynamic config to be used?
+
+ True if LP_TEST_INSTANCE is set in the environment.
+ """
+ return 'LP_TEST_INSTANCE' in os.environ
+
def _persistent_servers(self):
return os.environ.get('LP_PERSISTENT_TEST_SERVICES') is not None
@property
def root(self):
"""The root directory for the librarian file repository."""
- return config.librarian_server.root
+ if self._dynamic_config():
+ return self._root
+ else:
+ return config.librarian_server.root
def setUpRoot(self):
"""Create the librarian root archive."""
- # This should not happen in normal usage, but might if someone
- # interrupts the test suite.
- if os.path.exists(self.root):
- self.tearDownRoot()
- os.makedirs(self.root, 0700)
- self.addCleanup(self.tearDownRoot)
+ if self._dynamic_config():
+ root_fixture = FunctionFixture(tempfile.mkdtemp, shutil.rmtree)
+ self.useFixture(root_fixture)
+ self._root = root_fixture.fn_result
+ os.chmod(self.root, 0700)
+ # Give the root to the new librarian.
+ os.environ['LP_LIBRARIAN_ROOT'] = self._root
+ else:
+ # This should not happen in normal usage, but might if someone
+ # interrupts the test suite.
+ if os.path.exists(self.root):
+ self.tearDownRoot()
+ self.addCleanup(self.tearDownRoot)
+ os.makedirs(self.root, 0700)
+
+ def _waitForDaemonStartup(self):
+ super(LibrarianServerFixture, self)._waitForDaemonStartup()
+ # Expose the dynamically allocated ports, if we used them.
+ if not self._dynamic_config():
+ self.download_port = config.librarian.download_port
+ self.upload_port = config.librarian.upload_port
+ self.restricted_download_port = \
+ config.librarian.restricted_download_port
+ self.restricted_upload_port = \
+ config.librarian.restricted_upload_port
+ return
+ chunks = self.logChunks()
+ # A typical startup: upload, download, restricted up, restricted down.
+ #2010-10-20 14:28:21+0530 [-] Log opened.
+ #2010-10-20 14:28:21+0530 [-] twistd 10.1.0 (/usr/bin/python 2.6.5) starting up.
+ #2010-10-20 14:28:21+0530 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
+ #2010-10-20 14:28:21+0530 [-] canonical.librarian.libraryprotocol.FileUploadFactory starting on 59090
+ #2010-10-20 14:28:21+0530 [-] Starting factory <canonical.librarian.libraryprotocol.FileUploadFactory instance at 0x6f8ff38>
+ #2010-10-20 14:28:21+0530 [-] twisted.web.server.Site starting on 58000
+ #2010-10-20 14:28:21+0530 [-] Starting factory <twisted.web.server.Site instance at 0x6fb2638>
+ #2010-10-20 14:28:21+0530 [-] canonical.librarian.libraryprotocol.FileUploadFactory starting on 59095
+ #2010-10-20 14:28:21+0530 [-] Starting factory <canonical.librarian.libraryprotocol.FileUploadFactory instance at 0x6fb25f0>
+ #2010-10-20 14:28:21+0530 [-] twisted.web.server.Site starting on 58005
+ self.upload_port = int(chunks[3].split()[-1])
+ self.download_port = int(chunks[5].split()[-1])
+ self.restricted_upload_port = int(chunks[7].split()[-1])
+ self.restricted_download_port = int(chunks[9].split()[-1])
+ self.service_config = dedent("""\
+ [librarian_server]
+ root: %s
+ [librarian]
+ download_port: %s
+ upload_port: %s
+ download_url: http://launchpad.dev:%s/
+ restricted_download_port: %s
+ restricted_upload_port: %s
+ restricted_download_url: http://launchpad_dev:%s/
+ """) % (
+ self.root,
+ self.download_port,
+ self.upload_port,
+ self.download_port,
+ self.restricted_download_port,
+ self.restricted_upload_port,
+ self.restricted_download_port,
+ )
def tearDownRoot(self):
"""Remove the librarian root archive."""
@@ -186,21 +192,26 @@
@property
def pidfile(self):
- return os.path.join(self.root, 'librarian.pid')
+ try:
+ return os.path.join(self.root, 'librarian.pid')
+ except AttributeError:
+ # Not setup and using dynamic config: tachandler reads this early.
+ return '/tmp/unused/'
@property
def logfile(self):
# Store the log in the server root; if its wanted after a test, that
# test can use addDetail to grab the log and include it in its
# error.
- return os.path.join(self.root, 'librarian.log')
-
-
-_global_fixture = LibrarianServerFixture()
-
-def LibrarianTestSetup():
- """Support the stateless lie."""
- return _global_fixture
+ try:
+ return os.path.join(self.root, 'librarian.log')
+ except AttributeError:
+ # Not setup and using dynamic config: tachandler reads this early.
+ return '/tmp/unused/'
+
+ def logChunks(self):
+ """Get a list with the contents of the librarian log in it."""
+ return open(self.logfile, 'rb').readlines()
def fillLibrarianFile(fileid, content='Fake Content'):
@@ -214,8 +225,3 @@
libfile = open(filepath, 'wb')
libfile.write(content)
libfile.close()
-
-
-def cleanupLibrarianFiles():
- """Remove all librarian files present in disk."""
- _global_fixture.clear()
=== modified file 'lib/canonical/librarian/testing/tests/test_server_fixture.py'
--- lib/canonical/librarian/testing/tests/test_server_fixture.py 2010-09-26 21:22:04 +0000
+++ lib/canonical/librarian/testing/tests/test_server_fixture.py 2010-10-21 04:51:25 +0000
@@ -1,29 +1,123 @@
# Copyright 2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+from __future__ import with_statement
+
"""Test the LibrarianServerFixture."""
__metaclass__ = type
-import doctest
-import unittest
-
+import os
+from urllib import urlopen
+import socket
+from textwrap import dedent
+
+from testtools.content import Content
+from testtools.content_type import UTF8_TEXT
+
+from canonical.config import config
from canonical.librarian.testing.server import LibrarianServerFixture
+from canonical.testing.ftests.test_layers import (
+ BaseLayerIsolator,
+ LayerFixture,
+ )
+from canonical.testing.layers import BaseLayer, DatabaseLayer
from lp.testing import TestCase
-def test_suite():
- result = unittest.TestLoader().loadTestsFromName(__name__)
- result.addTest(doctest.DocTestSuite(
- 'canonical.librarian.testing.server',
- optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
- ))
- return result
-
class TestLibrarianServerFixture(TestCase):
- def test_on_init_no_pid(self):
- fixture = LibrarianServerFixture()
+ layer = DatabaseLayer
+
+ def skip_if_persistent(self, fixture):
if fixture._persistent_servers():
self.skip('persistent server running.')
+
+ def test_on_init_no_pid(self):
+ fixture = LibrarianServerFixture()
+ self.skip_if_persistent(fixture)
self.assertEqual(None, fixture.pid)
+
+ def test_setUp_allocates_resources(self):
+ fixture = LibrarianServerFixture()
+ self.skip_if_persistent(fixture)
+ with fixture:
+ try:
+ self.assertNotEqual(config.librarian_server.root, fixture.root)
+ self.assertNotEqual(
+ config.librarian.download_port,
+ fixture.download_port)
+ self.assertNotEqual(
+ config.librarian.upload_port,
+ fixture.upload_port)
+ self.assertNotEqual(
+ config.librarian.restricted_download_port,
+ fixture.restricted_download_port)
+ self.assertNotEqual(
+ config.librarian.restricted_upload_port,
+ fixture.restricted_upload_port)
+ # And it exposes a config fragment
+ # (Which is not activated by it.
+ expected_config = dedent("""\
+ [librarian_server]
+ root: %s
+ [librarian]
+ download_port: %s
+ upload_port: %s
+ download_url: http://launchpad.dev:%s/
+ restricted_download_port: %s
+ restricted_upload_port: %s
+ restricted_download_url: http://launchpad_dev:%s/
+ """) % (
+ fixture.root,
+ fixture.download_port,
+ fixture.upload_port,
+ fixture.download_port,
+ fixture.restricted_download_port,
+ fixture.restricted_upload_port,
+ fixture.restricted_download_port,
+ )
+ self.assertEqual(expected_config, fixture.service_config)
+ except:
+ self.attachLibrarianLog(fixture)
+ raise
+
+ def test_logChunks(self):
+ fixture = LibrarianServerFixture()
+ with fixture:
+ chunks = fixture.logChunks()
+ self.assertIsInstance(chunks, list)
+ found_started = False
+ for chunk in chunks:
+ if 'daemon ready' in chunk:
+ found_started = True
+ self.assertTrue(found_started)
+
+ def test_smoke_test(self):
+ # Avoid indefinite hangs:
+ import socket
+ self.addCleanup(socket.setdefaulttimeout, socket.getdefaulttimeout())
+ socket.setdefaulttimeout(1)
+ fixture = LibrarianServerFixture()
+ with fixture:
+ librarian_url = "http://%s:%d" % (
+ config.librarian.download_host,
+ fixture.download_port)
+ restricted_librarian_url = "http://%s:%d" % (
+ config.librarian.restricted_download_host,
+ fixture.restricted_download_port)
+ # Both download ports work:
+ self.assertTrue('Copyright' in urlopen(librarian_url).read())
+ self.assertTrue(
+ 'Copyright' in urlopen(restricted_librarian_url).read())
+ os.path.isdir(fixture.root)
+ # Ports are closed on cleanUp.
+ self.assertRaises(IOError, urlopen, librarian_url)
+ self.assertRaises(IOError, urlopen, restricted_librarian_url)
+ self.assertFalse(os.path.exists(fixture.root))
+ # We can use the fixture again (gets a new URL):
+ with fixture:
+ librarian_url = "http://%s:%d" % (
+ config.librarian.download_host,
+ fixture.download_port)
+ self.assertTrue('Copyright' in urlopen(librarian_url).read())
=== modified file 'lib/canonical/librarian/tests/test_sigdumpmem.py'
--- lib/canonical/librarian/tests/test_sigdumpmem.py 2010-09-27 01:03:03 +0000
+++ lib/canonical/librarian/tests/test_sigdumpmem.py 2010-10-21 04:51:25 +0000
@@ -9,12 +9,12 @@
import time
from canonical.librarian.interfaces import DUMP_FILE, SIGDUMPMEM
-from canonical.librarian.testing.server import LibrarianTestSetup
from canonical.testing.layers import LibrarianLayer
from lp.testing import TestCase
class SIGDUMPMEMTestCase(TestCase):
+
layer = LibrarianLayer
def test_sigdumpmem(self):
@@ -23,10 +23,8 @@
os.unlink(DUMP_FILE)
self.assertFalse(os.path.exists(DUMP_FILE))
- # Use the global instance used by the Layer machinery; it would
- # be nice to be able to access those without globals / magical
- # 'constructors'.
- pid = LibrarianTestSetup().pid
+ # Use the global instance used by the Layer machinery
+ pid = LibrarianLayer.librarian_fixture.pid
# Send the signal and ensure the dump file is created.
os.kill(pid, SIGDUMPMEM)
=== modified file 'lib/canonical/testing/ftests/test_layers.py'
--- lib/canonical/testing/ftests/test_layers.py 2010-10-21 04:51:19 +0000
+++ lib/canonical/testing/ftests/test_layers.py 2010-10-21 04:51:25 +0000
@@ -1,13 +1,17 @@
# Copyright 2009 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+from __future__ import with_statement
+
""" Test layers
Note that many tests are performed at run time in the layers themselves
to confirm that the environment hasn't been corrupted by tests
"""
+
__metaclass__ = type
+from contextlib import nested
from cStringIO import StringIO
import os
import signal
@@ -16,6 +20,7 @@
from urllib import urlopen
from fixtures import (
+ Fixture,
EnvironmentVariableFixture,
TestWithFixtures,
)
@@ -48,30 +53,70 @@
from lp.services.memcache.client import memcache_client_factory
+class BaseLayerIsolator(Fixture):
+ """A fixture for isolating BaseLayer.
+
+ This is useful to test interactions with LP_PERSISTENT_TEST_SERVICES
+ which makes tests within layers unable to test that easily.
+ """
+
+ def __init__(self, with_persistent=False):
+ """Create a BaseLayerIsolator.
+
+ :param with_persistent: If True LP_PERSISTENT_TEST_SERVICES will
+ be enabled during setUp.
+ """
+ super(BaseLayerIsolator, self).__init__()
+ self.with_persistent = with_persistent
+
+ def setUp(self):
+ super(BaseLayerIsolator, self).setUp()
+ if self.with_persistent:
+ env_value = ''
+ else:
+ env_value = None
+ self.useFixture(
+ EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES',
+ env_value))
+ self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
+
+
+class LayerFixture(Fixture):
+ """Adapt a layer to a fixture.
+
+ Note that the layer setup/teardown are called, not the base class ones.
+
+ :ivar layer: The adapted layer.
+ """
+
+ def __init__(self, layer):
+ """Create a LayerFixture.
+
+ :param layer: The layer to use.
+ """
+ super(LayerFixture, self).__init__()
+ self.layer = layer
+
+ def setUp(self):
+ super(LayerFixture, self).setUp()
+ self.layer.setUp()
+ self.addCleanup(self.layer.tearDown)
+
+
class TestBaseLayer(testtools.TestCase, TestWithFixtures):
def test_allocates_LP_TEST_INSTANCE(self):
- self.useFixture(
- EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES'))
- self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
- layer = BaseLayer
- layer.setUp()
- try:
- self.assertEqual(str(os.getpid()), os.environ.get('LP_TEST_INSTANCE'))
- finally:
- layer.tearDown()
+ self.useFixture(BaseLayerIsolator())
+ with LayerFixture(BaseLayer):
+ self.assertEqual(
+ str(os.getpid()),
+ os.environ.get('LP_TEST_INSTANCE'))
self.assertEqual(None, os.environ.get('LP_TEST_INSTANCE'))
def test_persist_test_services_disables_LP_TEST_INSTANCE(self):
- self.useFixture(
- EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES', ''))
- self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
- layer = BaseLayer
- layer.setUp()
- try:
+ self.useFixture(BaseLayerIsolator())
+ with LayerFixture(BaseLayer):
self.assertEqual(None, os.environ.get('LP_TEST_INSTANCE'))
- finally:
- layer.tearDown()
self.assertEqual(None, os.environ.get('LP_TEST_INSTANCE'))
def test_generates_unique_config(self):
@@ -81,14 +126,10 @@
EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES'))
self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
self.useFixture(EnvironmentVariableFixture('LPCONFIG'))
- layer = BaseLayer
- layer.setUp()
- try:
+ with LayerFixture(BaseLayer):
self.assertEqual(
'testrunner_%s' % os.environ['LP_TEST_INSTANCE'],
config.instance_name)
- finally:
- layer.tearDown()
self.assertEqual(orig_instance, config.instance_name)
def test_generates_unique_config_dirs(self):
@@ -96,9 +137,7 @@
EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES'))
self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
self.useFixture(EnvironmentVariableFixture('LPCONFIG'))
- layer = BaseLayer
- layer.setUp()
- try:
+ with LayerFixture(BaseLayer):
runner_root = 'configs/%s' % config.instance_name
runner_appserver_root = 'configs/testrunner-appserver_%s' % \
os.environ['LP_TEST_INSTANCE']
@@ -106,8 +145,6 @@
runner_root + '/launchpad-lazr.conf'))
self.assertTrue(os.path.isfile(
runner_appserver_root + '/launchpad-lazr.conf'))
- finally:
- layer.tearDown()
self.assertFalse(os.path.exists(runner_root))
self.assertFalse(os.path.exists(runner_appserver_root))
@@ -246,6 +283,35 @@
'foo.txt', len(data), StringIO(data), 'text/plain')
+class LibrarianLayerTest(testtools.TestCase, TestWithFixtures):
+
+ def test_makes_unique_instance(self):
+ # Capture the original settings
+ default_root = config.librarian_server.root
+ download_port = config.librarian.download_port
+ restricted_download_port = config.librarian.restricted_download_port
+ self.useFixture(BaseLayerIsolator())
+ with nested(
+ LayerFixture(BaseLayer),
+ LayerFixture(DatabaseLayer),
+ ):
+ with LayerFixture(LibrarianLayer):
+ active_root = config.librarian_server.root
+ # The config settings have changed:
+ self.assertNotEqual(default_root, active_root)
+ self.assertNotEqual(
+ download_port, config.librarian.download_port)
+ self.assertNotEqual(
+ restricted_download_port,
+ config.librarian.restricted_download_port)
+ self.assertTrue(os.path.exists(active_root))
+ # This needs more sophistication in the config system (it needs to
+ # affect configs on disk and other perhaps other processes
+ # self.assertEqual(default_root, config.librarian_server.root)
+ # The working dir has to have been deleted.
+ self.assertFalse(os.path.exists(active_root))
+
+
class LibrarianNoResetTestCase(testtools.TestCase):
"""Our page tests need to run multple tests without destroying
the librarian database in between.
=== modified file 'lib/canonical/testing/layers.py'
--- lib/canonical/testing/layers.py 2010-10-21 04:51:19 +0000
+++ lib/canonical/testing/layers.py 2010-10-21 04:51:25 +0000
@@ -124,7 +124,7 @@
from canonical.lazr.timeout import (
get_default_timeout_function, set_default_timeout_function)
from canonical.lp import initZopeless
-from canonical.librarian.testing.server import LibrarianTestSetup
+from canonical.librarian.testing.server import LibrarianServerFixture
from canonical.testing import reset_logging
from canonical.testing.profiled import profiled
from canonical.testing.smtpd import SMTPController
@@ -789,35 +789,43 @@
"""
_reset_between_tests = True
- _is_setup = False
+ librarian_fixture = None
@classmethod
@profiled
def setUp(cls):
- cls._is_setup = True
- if not LibrarianLayer._reset_between_tests:
+ if not cls._reset_between_tests:
raise LayerInvariantError(
"_reset_between_tests changed before LibrarianLayer "
"was actually used."
)
- the_librarian = LibrarianTestSetup()
- the_librarian.setUp()
- LibrarianLayer._check_and_reset()
- atexit.register(the_librarian.tearDown)
+ cls.librarian_fixture = LibrarianServerFixture()
+ cls.librarian_fixture.setUp()
+ BaseLayer.config.add_section(cls.librarian_fixture.service_config)
+ config.reloadConfig()
+ cls._check_and_reset()
+ atexit.register(cls.librarian_fixture.tearDown)
@classmethod
@profiled
def tearDown(cls):
- if not cls._is_setup:
+ # Permit multiple teardowns while we sort out the layering
+ # responsibilities : not desirable though.
+ if cls.librarian_fixture is None:
return
- cls._is_setup = False
- if not LibrarianLayer._reset_between_tests:
- raise LayerInvariantError(
- "_reset_between_tests not reset before LibrarianLayer "
- "shutdown"
- )
- LibrarianLayer._check_and_reset()
- LibrarianTestSetup().tearDown()
+ try:
+ cls._check_and_reset()
+ finally:
+ librarian = cls.librarian_fixture
+ cls.librarian_fixture = None
+ try:
+ if not cls._reset_between_tests:
+ raise LayerInvariantError(
+ "_reset_between_tests not reset before LibrarianLayer "
+ "shutdown"
+ )
+ finally:
+ librarian.cleanUp()
@classmethod
@profiled
@@ -836,20 +844,20 @@
"the Librarian is restarted if it absolutely must be "
"shutdown: " + str(e)
)
- if LibrarianLayer._reset_between_tests:
- LibrarianTestSetup().clear()
+ if cls._reset_between_tests:
+ cls.librarian_fixture.clear()
@classmethod
@profiled
def testSetUp(cls):
- LibrarianLayer._check_and_reset()
+ cls._check_and_reset()
@classmethod
@profiled
def testTearDown(cls):
- if LibrarianLayer._hidden:
- LibrarianLayer.reveal()
- LibrarianLayer._check_and_reset()
+ if cls._hidden:
+ cls.reveal()
+ cls._check_and_reset()
# Flag maintaining state of hide()/reveal() calls
_hidden = False
@@ -866,17 +874,17 @@
We do this by altering the configuration so the Librarian client
looks for the Librarian server on the wrong port.
"""
- LibrarianLayer._hidden = True
- if LibrarianLayer._fake_upload_socket is None:
+ cls._hidden = True
+ if cls._fake_upload_socket is None:
# Bind to a socket, but don't listen to it. This way we
# guarantee that connections to the given port will fail.
- LibrarianLayer._fake_upload_socket = socket.socket(
+ cls._fake_upload_socket = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
assert config.librarian.upload_host == 'localhost', (
'Can only hide librarian if it is running locally')
- LibrarianLayer._fake_upload_socket.bind(('127.0.0.1', 0))
+ cls._fake_upload_socket.bind(('127.0.0.1', 0))
- host, port = LibrarianLayer._fake_upload_socket.getsockname()
+ host, port = cls._fake_upload_socket.getsockname()
librarian_data = dedent("""
[librarian]
upload_port: %s
=== modified file 'lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt'
--- lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/soyuz/browser/tests/distroseriesqueue-views.txt 2010-10-21 04:51:25 +0000
@@ -294,5 +294,5 @@
We created librarian files that need cleaning up before leaving the test.
- >>> from canonical.librarian.testing.server import cleanupLibrarianFiles
- >>> cleanupLibrarianFiles()
+ >>> from canonical.testing.layers import LibrarianLayer
+ >>> LibrarianLayer.librarian_fixture.clear()
=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-notify.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-notify.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-notify.txt 2010-10-21 04:51:25 +0000
@@ -259,5 +259,5 @@
Clean up, otherwise stuff is left lying around in /var/tmp.
- >>> from canonical.librarian.testing.server import cleanupLibrarianFiles
- >>> cleanupLibrarianFiles()
+ >>> from canonical.testing.layers import LibrarianLayer
+ >>> LibrarianLayer.librarian_fixture.clear()
=== modified file 'lib/lp/soyuz/doc/distroseriesqueue.txt'
--- lib/lp/soyuz/doc/distroseriesqueue.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue.txt 2010-10-21 04:51:25 +0000
@@ -1025,8 +1025,8 @@
Clean up the librarian files:
- >>> from canonical.librarian.testing.server import cleanupLibrarianFiles
- >>> cleanupLibrarianFiles()
+ >>> from canonical.testing.layers import LibrarianLayer
+ >>> LibrarianLayer.librarian_fixture.clear()
== Delayed copies ==
=== modified file 'lib/lp/soyuz/scripts/tests/test_queue.py'
--- lib/lp/soyuz/scripts/tests/test_queue.py 2010-10-04 20:46:55 +0000
+++ lib/lp/soyuz/scripts/tests/test_queue.py 2010-10-21 04:51:25 +0000
@@ -33,13 +33,13 @@
)
from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
from canonical.librarian.testing.server import (
- cleanupLibrarianFiles,
fillLibrarianFile,
)
from canonical.librarian.utils import filechunks
from canonical.testing.layers import (
DatabaseFunctionalLayer,
LaunchpadZopelessLayer,
+ LibrarianLayer,
)
from lp.archiveuploader.nascentupload import NascentUpload
from lp.archiveuploader.tests import (
@@ -152,7 +152,7 @@
def tearDown(self):
"""Remove test contents from disk."""
- cleanupLibrarianFiles()
+ LibrarianLayer.librarian_fixture.clear()
def uploadPackage(self,
changesfile="suite/bar_1.0-1/bar_1.0-1_source.changes"):
@@ -998,7 +998,7 @@
directory used as jail.
"""
os.chdir(self._home)
- cleanupLibrarianFiles()
+ LibrarianLayer.librarian_fixture.clear()
shutil.rmtree(self._jail)
def _listfiles(self):
=== modified file 'lib/lp/soyuz/scripts/tests/test_sync_source.py'
--- lib/lp/soyuz/scripts/tests/test_sync_source.py 2010-10-06 11:46:51 +0000
+++ lib/lp/soyuz/scripts/tests/test_sync_source.py 2010-10-21 04:51:25 +0000
@@ -25,10 +25,12 @@
from canonical.config import config
from canonical.launchpad.scripts import BufferLogger
from canonical.librarian.testing.server import (
- cleanupLibrarianFiles,
fillLibrarianFile,
)
-from canonical.testing.layers import LaunchpadZopelessLayer
+from canonical.testing.layers import (
+ LaunchpadZopelessLayer,
+ LibrarianLayer,
+ )
from lp.archiveuploader.tagfiles import parse_tagfile
from lp.registry.interfaces.distribution import IDistributionSet
from lp.soyuz.scripts.ftpmaster import (
@@ -69,7 +71,7 @@
"""
super(TestSyncSource, self).tearDown()
os.chdir(self._home)
- cleanupLibrarianFiles()
+ LibrarianLayer.librarian_fixture.clear()
shutil.rmtree(self._jail)
def _listFiles(self):
=== modified file 'lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt'
--- lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt 2010-10-21 04:51:25 +0000
@@ -574,6 +574,6 @@
== Clean up ==
- >>> from canonical.librarian.testing.server import cleanupLibrarianFiles
- >>> cleanupLibrarianFiles()
+ >>> from canonical.testing.layers import LibrarianLayer
+ >>> LibrarianLayer.librarian_fixture.clear()
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2010-10-19 23:04:18 +0000
+++ lib/lp/testing/__init__.py 2010-10-21 04:51:25 +0000
@@ -490,14 +490,27 @@
content = Content(UTF8_TEXT, oops.get_chunks)
self.addDetail("oops-%d" % i, content)
+ def attachLibrarianLog(self, fixture):
+ """Include the logChunks from fixture in the test details."""
+ # Evaluate the log when called, not later, to permit the librarian to
+ # be shutdown before the detail is rendered.
+ chunks = fixture.logChunks()
+ content = Content(UTF8_TEXT, lambda:chunks)
+ self.addDetail('librarian-log', content)
+
def setUp(self):
testtools.TestCase.setUp(self)
from lp.testing.factory import ObjectFactory
+ from canonical.testing.layers import LibrarianLayer
self.factory = ObjectFactory()
# Record the oopses generated during the test run.
self.oopses = []
self.useFixture(ZopeEventHandlerFixture(self._recordOops))
self.addCleanup(self.attachOopses)
+ if LibrarianLayer.librarian_fixture is not None:
+ self.addCleanup(
+ self.attachLibrarianLog,
+ LibrarianLayer.librarian_fixture)
@adapter(ErrorReportEvent)
def _recordOops(self, event):
=== modified file 'versions.cfg'
--- versions.cfg 2010-10-21 04:51:19 +0000
+++ versions.cfg 2010-10-21 04:51:25 +0000
@@ -19,7 +19,7 @@
epydoc = 3.0.1
FeedParser = 4.1
feedvalidator = 0.0.0DEV-r1049
-fixtures = 0.3.2
+fixtures = 0.3.3
functest = 0.8.7
funkload = 1.10.0
grokcore.component = 1.6