launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01585
[Merge] lp:~lifeless/launchpad/uniqueconfig into lp:launchpad/devel
Robert Collins has proposed merging lp:~lifeless/launchpad/uniqueconfig into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Generate unique LP config directories during test time. Another step towards parallel testing.
--
https://code.launchpad.net/~lifeless/launchpad/uniqueconfig/+merge/38689
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/uniqueconfig into lp:launchpad/devel.
=== modified file 'configs/README.txt'
--- configs/README.txt 2009-04-29 19:10:17 +0000
+++ configs/README.txt 2010-10-18 04:06:48 +0000
@@ -281,9 +281,13 @@
| |
| + authserver-lazr.conf
| |
+ | + testrunner_\d+/launchpad-lazr.conf
+ | |
| + testrunner-appserver/launchpad-lazr.conf
| |
| + authserver-lazr.conf
+ | |
+ | + testrunner-appserver_\d+/launchpad-lazr.conf
|
+ staging-lazr.conf
| |
=== added file 'lib/canonical/config/fixture.py'
--- lib/canonical/config/fixture.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/config/fixture.py 2010-10-18 04:06:48 +0000
@@ -0,0 +1,63 @@
+# 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
+"""Fixtures related to configs."""
+
+__metaclass__ = type
+
+__all__ = [
+ 'ConfigFixture',
+ 'ConfigUseFixture',
+ ]
+
+import os.path
+import shutil
+
+from fixtures import Fixture
+
+from canonical.config import config
+
+
+class ConfigFixture(Fixture):
+ """Create a unique launchpad config."""
+
+ def __init__(self, instance_name, copy_from_instance):
+ """Create a ConfigFixture.
+
+ :param instance_name: The name of the instance to create.
+ :param copy_from_instance: An existing instance to clone.
+ """
+ self.instance_name = instance_name
+ self.copy_from_instance = copy_from_instance
+
+ def setUp(self):
+ super(ConfigFixture, self).setUp()
+ root = 'configs/' + self.instance_name
+ os.mkdir(root)
+ absroot = os.path.abspath(root)
+ self.addCleanup(shutil.rmtree, absroot)
+ source = 'configs/' + self.copy_from_instance
+ for basename in os.listdir(source):
+ if basename == 'launchpad-lazr.conf':
+ with open(root + '/launchpad-lazr.conf', 'wb') as out:
+ out.write("""[meta]
+extends: ../%s/launchpad-lazr.conf
+
+""" % self.copy_from_instance)
+ continue
+ with open(source + '/' + basename, 'rb') as input:
+ with open(root + '/' + basename, 'wb') as out:
+ out.write(input.read())
+
+
+class ConfigUseFixture(Fixture):
+ """Use a config and restore the current config after."""
+
+ def __init__(self, instance_name):
+ self.instance_name = instance_name
+
+ def setUp(self):
+ super(ConfigUseFixture, self).setUp()
+ self.addCleanup(config.setInstance, config.instance_name)
+ config.setInstance(self.instance_name)
=== added file 'lib/canonical/config/tests/test_fixture.py'
--- lib/canonical/config/tests/test_fixture.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/config/tests/test_fixture.py 2010-10-18 04:06:48 +0000
@@ -0,0 +1,56 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests of the config fixtures."""
+
+__metaclass__ = type
+
+import os
+
+from testtools import TestCase
+
+from canonical.config import config
+from canonical.config.fixture import (
+ ConfigFixture,
+ ConfigUseFixture,
+ )
+
+
+class TestConfigUseFixture(TestCase):
+
+ def test_sets_restores_instance(self):
+ fixture = ConfigUseFixture('foo')
+ orig_instance = config.instance_name
+ fixture.setUp()
+ try:
+ self.assertEqual('foo', config.instance_name)
+ finally:
+ fixture.cleanUp()
+ self.assertEqual(orig_instance, config.instance_name)
+
+
+class TestConfigFixture(TestCase):
+
+ def test_copies_and_derives(self):
+ fixture = ConfigFixture('testtestconfig', 'testrunner')
+ to_copy = [
+ 'apidoc-configure-normal.zcml',
+ 'launchpad.conf',
+ 'test-process-lazr.conf',
+ ]
+ fixture.setUp()
+ try:
+ for base in to_copy:
+ path = 'configs/testtestconfig/' + base
+ source = 'configs/testrunner/' + base
+ old = open(source, 'rb').read()
+ new = open(path, 'rb').read()
+ self.assertEqual(old, new)
+ confpath = 'configs/testtestconfig/launchpad-lazr.conf'
+ lazr_config = open(confpath, 'rb').read()
+ self.assertEqual("""[meta]
+extends: ../testrunner/launchpad-lazr.conf
+
+""", lazr_config)
+ finally:
+ fixture.cleanUp()
=== modified file 'lib/canonical/ftests/pgsql.py'
--- lib/canonical/ftests/pgsql.py 2010-10-17 05:30:43 +0000
+++ lib/canonical/ftests/pgsql.py 2010-10-18 04:06:48 +0000
@@ -169,7 +169,12 @@
if template is not None:
self.template = template
if dbname is PgTestSetup.dynamic:
- self.dbname = self.__class__.dbname + "_" + str(os.getpid())
+ if os.environ.get('LP_TEST_INSTANCE'):
+ self.dbname = "%s_%s" % (
+ self.__class__.dbname, os.environ.get('LP_TEST_INSTANCE'))
+ else:
+ # Fallback to the class name.
+ self.dbname = self.__class__.dbname
elif dbname is not None:
self.dbname = dbname
else:
@@ -269,7 +274,6 @@
ConnectionWrapper.dirty = False
if PgTestSetup._reset_db:
self.dropDb()
- PgTestSetup._reset_db = True
#uninstallFakeConnect()
def connect(self):
@@ -329,6 +333,8 @@
cur.execute('VACUUM pg_catalog.pg_shdepend')
finally:
con.close()
+ # Any further setUp's must make a new DB.
+ PgTestSetup._reset_db = True
def force_dirty_database(self):
"""flag the database as being dirty
=== modified file 'lib/canonical/ftests/test_pgsql.py'
--- lib/canonical/ftests/test_pgsql.py 2010-10-17 06:26:54 +0000
+++ lib/canonical/ftests/test_pgsql.py 2010-10-18 04:06:48 +0000
@@ -3,6 +3,10 @@
import os
+from fixtures import (
+ EnvironmentVariableFixture,
+ TestWithFixtures,
+ )
import testtools
from canonical.ftests.pgsql import (
@@ -11,11 +15,10 @@
)
-class TestPgTestSetup(testtools.TestCase):
+class TestPgTestSetup(testtools.TestCase, TestWithFixtures):
- def test_db_naming(self):
- fixture = PgTestSetup(dbname=PgTestSetup.dynamic)
- expected_name = "%s_%s" % (PgTestSetup.dbname, os.getpid())
+ def assertDBName(self, expected_name, fixture):
+ """Check that fixture uses expected_name as its dbname."""
self.assertEqual(expected_name, fixture.dbname)
fixture.setUp()
self.addCleanup(fixture.dropDb)
@@ -25,6 +28,19 @@
where = cur.fetchone()[0]
self.assertEqual(expected_name, where)
+ def test_db_naming_LP_TEST_INSTANCE_set(self):
+ # when LP_TEST_INSTANCE is set, it is used for dynamic db naming.
+ self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE', 'xx'))
+ fixture = PgTestSetup(dbname=PgTestSetup.dynamic)
+ expected_name = "%s_xx" % (PgTestSetup.dbname,)
+ self.assertDBName(expected_name, fixture)
+
+ def test_db_naming_without_LP_TEST_INSTANCE_is_static(self):
+ self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
+ fixture = PgTestSetup(dbname=PgTestSetup.dynamic)
+ expected_name = PgTestSetup.dbname
+ self.assertDBName(expected_name, fixture)
+
def testOptimization(self):
# Test to ensure that the database is destroyed only when necessary
=== modified file 'lib/canonical/launchpad/scripts/__init__.py'
--- lib/canonical/launchpad/scripts/__init__.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/scripts/__init__.py 2010-10-18 04:06:48 +0000
@@ -71,7 +71,8 @@
Instead, your test should use the Zopeless layer.
"""
- if config.instance_name == 'testrunner':
+ if (config.instance_name == 'testrunner' or
+ config.instance_name.startswith('testrunner_')):
scriptzcmlfilename = 'script-testing.zcml'
else:
scriptzcmlfilename = 'script.zcml'
=== modified file 'lib/canonical/launchpad/scripts/logger.py'
--- lib/canonical/launchpad/scripts/logger.py 2010-09-27 08:46:26 +0000
+++ lib/canonical/launchpad/scripts/logger.py 2010-10-18 04:06:48 +0000
@@ -172,7 +172,8 @@
def __init__(self, fmt=None, datefmt=None):
if fmt is None:
- if config.instance_name == 'testrunner':
+ if (config.instance_name == 'testrunner' or
+ config.instance_name.startswith('testrunner_')):
# Don't output timestamps in the test environment
fmt = '%(levelname)-7s %(message)s'
else:
=== modified file 'lib/canonical/testing/ftests/test_layers.py'
--- lib/canonical/testing/ftests/test_layers.py 2010-10-17 19:08:32 +0000
+++ lib/canonical/testing/ftests/test_layers.py 2010-10-18 04:06:48 +0000
@@ -8,14 +8,18 @@
"""
__metaclass__ = type
+from cStringIO import StringIO
import os
import signal
import smtplib
-import unittest
-
-from cStringIO import StringIO
from urllib import urlopen
+
+from fixtures import (
+ EnvironmentVariableFixture,
+ TestWithFixtures,
+ )
import psycopg2
+import testtools
from zope.component import getUtility, ComponentLookupError
@@ -43,7 +47,72 @@
)
from lp.services.memcache.client import memcache_client_factory
-class BaseTestCase(unittest.TestCase):
+
+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.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.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):
+ config.setInstance('testrunner')
+ orig_instance = config.instance_name
+ self.useFixture(
+ EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES'))
+ self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
+ self.useFixture(EnvironmentVariableFixture('LPCONFIG'))
+ layer = BaseLayer
+ layer.setUp()
+ try:
+ 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):
+ self.useFixture(
+ EnvironmentVariableFixture('LP_PERSISTENT_TEST_SERVICES'))
+ self.useFixture(EnvironmentVariableFixture('LP_TEST_INSTANCE'))
+ self.useFixture(EnvironmentVariableFixture('LPCONFIG'))
+ layer = BaseLayer
+ layer.setUp()
+ try:
+ runner_root = 'configs/%s' % config.instance_name
+ runner_appserver_root = 'configs/testrunner-appserver_%s' % \
+ os.environ['LP_TEST_INSTANCE']
+ self.assertTrue(os.path.isfile(
+ 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))
+
+
+class BaseTestCase(testtools.TestCase):
"""Both the Base layer tests, as well as the base Test Case
for all the other Layer tests.
"""
@@ -178,7 +247,7 @@
)
-class LibrarianNoResetTestCase(unittest.TestCase):
+class LibrarianNoResetTestCase(testtools.TestCase):
"""Our page tests need to run multple tests without destroying
the librarian database in between.
"""
@@ -221,7 +290,7 @@
self.failIfEqual(data, self.sample_data)
-class LibrarianHideTestCase(unittest.TestCase):
+class LibrarianHideTestCase(testtools.TestCase):
layer = LaunchpadLayer
def testHideLibrarian(self):
@@ -389,7 +458,7 @@
LayerProcessController.startSMTPServer)
-class LayerProcessControllerTestCase(unittest.TestCase):
+class LayerProcessControllerTestCase(testtools.TestCase):
"""Tests for the `LayerProcessController`."""
# We need the database to be set up, no more.
layer = DatabaseLayer
@@ -432,14 +501,10 @@
self.assertEquals(True, LaunchpadTestSetup()._reset_db)
-class TestNameTestCase(unittest.TestCase):
+class TestNameTestCase(testtools.TestCase):
layer = BaseLayer
def testTestName(self):
self.failUnlessEqual(
BaseLayer.test_name,
"testTestName "
"(canonical.testing.ftests.test_layers.TestNameTestCase)")
-
-
-def test_suite():
- return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'lib/canonical/testing/layers.py'
--- lib/canonical/testing/layers.py 2010-10-17 05:02:20 +0000
+++ lib/canonical/testing/layers.py 2010-10-18 04:06:48 +0000
@@ -57,6 +57,7 @@
import gc
import logging
import os
+import shutil
import signal
import socket
import subprocess
@@ -64,12 +65,12 @@
import tempfile
import threading
import time
-
from cProfile import Profile
from textwrap import dedent
from unittest import TestCase, TestResult
from urllib import urlopen
+from fixtures import Fixture
import psycopg2
from storm.zope.interfaces import IZStorm
import transaction
@@ -97,6 +98,10 @@
from canonical.launchpad.webapp.vhosts import allvhosts
from canonical.lazr import pidfile
from canonical.config import CanonicalConfig, config, dbconfig
+from canonical.config.fixture import (
+ ConfigFixture,
+ ConfigUseFixture,
+ )
from canonical.database.revision import (
confirm_dbrevision, confirm_dbrevision_on_startup)
from canonical.database.sqlbase import (
@@ -253,18 +258,55 @@
# LP_PERSISTENT_TEST_SERVICES environment variable.
persist_test_services = False
+ # Things we need to cleanup.
+ fixture = None
+
+ # The config names that are generated for this layer
+ config_name = None
+ appserver_config_name = None
+
+ @classmethod
+ def make_config(cls, config_name, clone_from):
+ """Create a temporary config and link it into the layer cleanup."""
+ cfg_fixture = ConfigFixture(config_name, clone_from)
+ cls.fixture.addCleanup(cfg_fixture.cleanUp)
+ cfg_fixture.setUp()
+
@classmethod
@profiled
def setUp(cls):
BaseLayer.isSetUp = True
+ cls.fixture = Fixture()
+ cls.fixture.setUp()
+ cls.fixture.addCleanup(setattr, cls, 'fixture', None)
BaseLayer.persist_test_services = (
os.environ.get('LP_PERSISTENT_TEST_SERVICES') is not None)
- # Kill any Memcached or Librarian left running from a previous
- # test run, or from the parent test process if the current
- # layer is being run in a subprocess. No need to be polite
- # about killing memcached - just do it quickly.
+ # We can only do unique test allocation and parallelisation if
+ # LP_PERSISTENT_TEST_SERVICES is off.
if not BaseLayer.persist_test_services:
- kill_by_pidfile(MemcachedLayer.getPidFile(), num_polls=0)
+ test_instance = str(os.getpid())
+ os.environ['LP_TEST_INSTANCE'] = test_instance
+ cls.fixture.addCleanup(os.environ.pop, 'LP_TEST_INSTANCE', '')
+ # Kill any Memcached or Librarian left running from a previous
+ # test run, or from the parent test process if the current
+ # layer is being run in a subprocess. No need to be polite
+ # about killing memcached - just do it quickly.
+ if not BaseLayer.persist_test_services:
+ kill_by_pidfile(MemcachedLayer.getPidFile(), num_polls=0)
+ config_name = 'testrunner_%s' % test_instance
+ cls.make_config(config_name, 'testrunner')
+ app_config_name = 'testrunner-appserver_%s' % test_instance
+ cls.make_config(app_config_name, 'testrunner-appserver')
+ else:
+ config_name = 'testrunner'
+ app_config_name = 'testrunner-appserver'
+ cls.config_name = config_name
+ cls.fixture.addCleanup(setattr, cls, 'config_name', None)
+ cls.appserver_config_name = app_config_name
+ cls.fixture.addCleanup(setattr, cls, 'appserver_config_name', None)
+ use_fixture = ConfigUseFixture(config_name)
+ cls.fixture.addCleanup(use_fixture.cleanUp)
+ use_fixture.setUp()
# Kill any database left lying around from a previous test run.
db_fixture = LaunchpadTestSetup()
try:
@@ -278,6 +320,7 @@
@classmethod
@profiled
def tearDown(cls):
+ cls.fixture.cleanUp()
BaseLayer.isSetUp = False
@classmethod
=== modified file 'lib/canonical/testing/tests/test_layers.py'
--- lib/canonical/testing/tests/test_layers.py 2010-09-29 05:53:47 +0000
+++ lib/canonical/testing/tests/test_layers.py 2010-10-18 04:06:48 +0000
@@ -42,7 +42,3 @@
def test_disabled_thread_check(self):
# Confirm the BaseLayer.disable_thread_check code path works.
BaseLayer.disable_thread_check = True
-
-
-def test_suite():
- return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'versions.cfg'
--- versions.cfg 2010-10-15 04:53:38 +0000
+++ versions.cfg 2010-10-18 04:06:48 +0000
@@ -19,7 +19,7 @@
epydoc = 3.0.1
FeedParser = 4.1
feedvalidator = 0.0.0DEV-r1049
-fixtures = 0.3.1
+fixtures = 0.3.2
functest = 0.8.7
funkload = 1.10.0
grokcore.component = 1.6
Follow ups