← Back to team overview

launchpad-reviewers team mailing list archive

[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