Commits

Olemis Lang committed e8a5e51 Merge

BH Multiproduct #350 : Merge 6e275060f0d0389d7ff21372 patch for compatibility with BEP 3

Comments (0)

Files changed (3)

 #t333/t333_r1427886_schema_autoinc.diff
 #t333/t333_r1427886_mp_gen_schema.diff
-t115/t115_r1429886_product_envs.diff
-t115/t115_r1429886_product_config.diff
-t115/t115_r1429886_product_envs_testing.diff
+#t115/t115_r1429886_product_envs.diff
+#t115/t115_r1429886_product_config.diff
+#t115/t115_r1429886_product_envs_testing.diff
+t115/t115_r1431447_product_envs_bep3_p1.diff
 t350/t350_r1432088_product_component_enable.diff

t115/t115_r1431447_product_envs_bep3_p1.diff

+# HG changeset patch
+# Parent 00a5e8114cb43b9d28de1ae1ce8030fbe5e7b2b8
+BH Multiproduct #115 : Product environments. Compliance with BEP 3 - part 1
+
+diff -r 00a5e8114cb4 bloodhound_multiproduct/multiproduct/env.py
+--- a/bloodhound_multiproduct/multiproduct/env.py	Sun Jan 13 10:19:19 2013 +0000
++++ b/bloodhound_multiproduct/multiproduct/env.py	Mon Jan 14 01:49:36 2013 -0500
+@@ -46,7 +46,7 @@
+     def __init__(self, path, create=False, options=[]):
+         super(Environment, self).__init__(path, create=create, options=options)
+         # global environment w/o parent
+-        self.env = None
++        self.parent = None
+         self.product = None
+ 
+     @property
+@@ -82,11 +82,23 @@
+ 
+     implements(trac.env.ISystemInfoProvider)
+ 
+-    @property
+-    def system_info_providers(self):
+-        r"""System info will still be determined by the global environment.
++    def __getattr__(self, attrnm):
++        """Forward attribute access request to parent environment.
++
++        Initially this will affect the following members of
++        `trac.env.Environment` class:
++
++        system_info_providers, secure_cookies, project_admin_trac_url,
++        get_system_info, get_version, get_templates_dir, get_templates_dir,
++        get_log_dir, backup
+         """
+-        return self.env.system_info_providers
++        try:
++            if attrnm == 'parent':
++                raise AttributeError
++            return getattr(self.parent, attrnm)
++        except AttributeError:
++            raise AttributeError("'%s' object has no attribute '%s'" %
++                    (self.__class__.__name__, attrnm))
+ 
+     @property
+     def setup_participants(self):
+@@ -116,12 +128,6 @@
+     base_url_for_redirect = ''
+ 
+     @property
+-    def secure_cookies(self):
+-        """Restrict cookies to HTTPS connections.
+-        """
+-        return self.env.secure_cookies
+-
+-    @property
+     def project_name(self):
+         """Name of the product.
+         """
+@@ -139,24 +145,18 @@
+         which the `base_url` resides. This is used in notification
+         e-mails.
+         """
+-        return self.env.project_url
++        # FIXME: Should products have different values i.e. config option ?
++        return self.parent.project_url
+ 
+     project_admin = Option('project', 'admin', '',
+         """E-Mail address of the product's leader / administrator.""")
+ 
+     @property
+-    def project_admin_trac_url(self):
+-        """Base URL of a Trac instance where errors in this Trac
+-        should be reported.
+-        """
+-        return self.env.project_admin_trac_url
+-
+-    # FIXME: Should products have different values i.e. config option ?
+-    @property
+     def project_footer(self):
+         """Page footer text (right-aligned).
+         """
+-        return self.env.project_footer
++        # FIXME: Should products have different values i.e. config option ?
++        return self.parent.project_footer
+ 
+     project_icon = Option('project', 'icon', 'common/trac.ico',
+         """URL of the icon of the product.""")
+@@ -207,6 +207,12 @@
+         :param product: product prefix or an instance of
+                         multiproduct.model.Product
+         """
++        if not isinstance(env, trac.env.Environment):
++            raise TypeError("Initializer must be called with " \
++                "trac.env.Environment instance as first argument " \
++                "(got %s instance instead)" % 
++                        (self._component_name(env.__class__),) )
++
+         ComponentManager.__init__(self)
+ 
+         if isinstance(product, Product):
+@@ -221,9 +227,9 @@
+                         product, products)
+                 raise LookupError("Missing product %s" % (product,))
+ 
+-        self.env = env
++        self.parent = env
+         self.product = product
+-        self.path = self.env.path
++        self.path = self.parent.path
+         self.systeminfo = []
+         self._href = self._abs_href = None
+ 
+@@ -231,16 +237,12 @@
+ 
+     # ISystemInfoProvider methods
+ 
+-    def get_system_info(self):
+-        return self.env.get_system_info()
+-
+     # Same as parent environment's . Avoid duplicated code
+     component_activated = trac.env.Environment.component_activated.im_func
+     _component_name = trac.env.Environment._component_name.im_func
+     _component_rules = trac.env.Environment._component_rules
+     enable_component = trac.env.Environment.enable_component.im_func
+     get_known_users = trac.env.Environment.get_known_users.im_func
+-    get_systeminfo = trac.env.Environment.get_system_info.im_func
+     get_repository = trac.env.Environment.get_repository.im_func
+     is_component_enabled = trac.env.Environment.is_component_enabled.im_func
+ 
+@@ -264,7 +266,7 @@
+                ...
+         """
+         # share connection pool with global environment
+-        return self.env.get_db_cnx()
++        return self.parent.get_db_cnx()
+ 
+     @lazy
+     def db_exc(self):
+@@ -282,7 +284,7 @@
+                 ...
+         """
+         # exception types same as in global environment
+-        return self.env.db_exc()
++        return self.parent.db_exc()
+ 
+     def with_transaction(self, db=None):
+         """Decorator for transaction functions :deprecated:"""
+@@ -293,7 +295,7 @@
+ 
+         See `trac.db.api.get_read_db` for detailed documentation."""
+         # database connection is shared with global environment
+-        return self.env.get_read_db()
++        return self.parent.get_read_db()
+ 
+     @property
+     def db_query(self):
+@@ -328,7 +330,7 @@
+           `db_transaction`).
+         """
+         BloodhoundIterableCursor.set_env(self)
+-        return QueryContextManager(self.env)
++        return QueryContextManager(self.parent)
+ 
+     @property
+     def db_transaction(self):
+@@ -364,7 +366,7 @@
+           (`db_query` or `db_transaction`).
+         """
+         BloodhoundIterableCursor.set_env(self)
+-        return TransactionContextManager(self.env)
++        return TransactionContextManager(self.parent)
+ 
+     def shutdown(self, tid=None):
+         """Close the environment."""
+@@ -385,58 +387,30 @@
+         """
+         # TODO: Handle options args
+ 
+-    def get_version(self, db=None, initial=False):
+-        """Return the current version of the database.  If the
+-        optional argument `initial` is set to `True`, the version of
+-        the database used at the time of creation will be returned.
+-
+-        In practice, for database created before 0.11, this will
+-        return `False` which is "older" than any db version number.
+-
+-        :since: 0.11
+-
+-        :since 1.0: deprecation warning: the `db` parameter is no
+-                    longer used and will be removed in version 1.1.1
+-        """
+-        return self.env.get_version(db, initial)
+-
+     def setup_config(self):
+         """Load the configuration object.
+         """
+         # FIXME: Install product-specific configuration object
+-        self.config = self.env.config
++        self.config = self.parent.config
+         self.setup_log()
+ 
+-    def get_templates_dir(self):
+-        """Return absolute path to the templates directory.
+-        """
+-        return self.env.get_templates_dir()
+-
+-    def get_htdocs_dir(self):
+-        """Return absolute path to the htdocs directory."""
+-        return self.env.get_htdocs_dir()
+-
+-    def get_log_dir(self):
+-        """Return absolute path to the log directory."""
+-        return self.env.get_log_dir()
+-
+     def setup_log(self):
+         """Initialize the logging sub-system."""
+         from trac.log import logger_handler_factory
+         logtype = self.log_type
+-        self.env.log.debug("Log type '%s' for product '%s'", 
++        self.parent.log.debug("Log type '%s' for product '%s'", 
+                 logtype, self.product.prefix)
+         if logtype == 'inherit':
+-            logtype = self.env.log_type
+-            logfile = self.env.log_file
+-            format = self.env.log_format
++            logtype = self.parent.log_type
++            logfile = self.parent.log_file
++            format = self.parent.log_format
+         else:
+             logfile = self.log_file
+             format = self.log_format
+         if logtype == 'file' and not os.path.isabs(logfile):
+             logfile = os.path.join(self.get_log_dir(), logfile)
+         logid = 'Trac.%s.%s' % \
+-                (sha1(self.env.path).hexdigest(), self.product.prefix)
++                (sha1(self.parent.path).hexdigest(), self.product.prefix)
+         if format:
+             format = format.replace('$(', '%(') \
+                      .replace('%(path)s', self.path) \
+@@ -449,25 +423,8 @@
+         self.log.info('-' * 32 + ' environment startup [Trac %s] ' + '-' * 32,
+                       get_pkginfo(core).get('version', VERSION))
+ 
+-    def backup(self, dest=None):
+-        """Create a backup of the database.
+-
+-        :param dest: Destination file; if not specified, the backup is
+-                     stored in a file called db_name.trac_version.bak
+-        """
+-        return self.env.backup(dest)
+-
+     def needs_upgrade(self):
+         """Return whether the environment needs to be upgraded."""
+-        #for participant in self.setup_participants:
+-        #    with self.db_query as db:
+-        #        if participant.environment_needs_upgrade(db):
+-        #            self.log.warn("Component %s requires environment upgrade",
+-        #                          participant)
+-        #            return True
+-
+-        # FIXME: For the time being no need to upgrade the environment
+-        # TODO: Determine the role of product environments at upgrade time
+         return False
+ 
+     def upgrade(self, backup=False, backup_dest=None):
+diff -r 00a5e8114cb4 bloodhound_multiproduct/tests/env.py
+--- a/bloodhound_multiproduct/tests/env.py	Sun Jan 13 10:19:19 2013 +0000
++++ b/bloodhound_multiproduct/tests/env.py	Mon Jan 14 01:49:36 2013 -0500
+@@ -20,9 +20,17 @@
+ 
+ import os.path
+ import shutil
++import sys
+ import tempfile
+-import unittest
++from types import MethodType
+ 
++if sys.version_info < (2, 7):
++    import unittest2 as unittest
++else:
++    import unittest
++
++from trac.config import Option
++from trac.env import Environment
+ from trac.test import EnvironmentStub
+ from trac.tests.env import EnvironmentTestCase
+ 
+@@ -100,6 +108,14 @@
+             # table remains but database version is deleted
+             pass
+ 
++    def _mp_setup(self):
++        """Shortcut for quick product-aware environment setup.
++        """
++        self.env = self._setup_test_env()
++        self._upgrade_mp(self.env)
++        self._setup_test_log(self.env)
++        self._load_product_from_data(self.env, self.default_product)
++
+ class ProductEnvTestCase(EnvironmentTestCase, MultiproductTestCase):
+     r"""Test cases for Trac environments rewritten for product environments
+     """
+@@ -131,9 +147,98 @@
+ 
+         EnvironmentTestCase.tearDown(self)
+ 
+-def suite():
+-    return unittest.makeSuite(ProductEnvTestCase,'test')
++class ProductEnvApiTestCase(MultiproductTestCase):
++    """Assertions for Apache(TM) Bloodhound product-specific extensions in
++    [https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003 BEP 3]
++    """
++    def setUp(self):
++        self._mp_setup()
++        self.product_env = ProductEnvironment(self.env, self.default_product)
++
++    def test_attr_forward_parent(self):
++        class EnvironmentAttrSandbox(EnvironmentStub):
++            """Limit the impact of class edits so as to avoid race conditions
++            """
++
++        self.longMessage = True
++
++        class AttrSuccess(Exception):
++            """Exception raised when target method / property is actually
++            invoked.
++            """
++
++        def property_mock(attrnm, expected_self):
++            def assertAttrFwd(instance):
++                self.assertIs(instance, expected_self, 
++                        "Mismatch in property '%s'" % (attrnm,))
++                raise AttrSuccess
++            return property(assertAttrFwd)
++
++        self.env.__class__ = EnvironmentAttrSandbox
++        try:
++            for attrnm in 'system_info_providers secure_cookies ' \
++                    'project_admin_trac_url get_system_info get_version ' \
++                    'get_templates_dir get_templates_dir get_log_dir ' \
++                    'backup'.split(): 
++                original = getattr(Environment, attrnm)
++                if isinstance(original, MethodType):
++                    translation = getattr(self.product_env, attrnm)
++                    self.assertIs(translation.im_self, self.env,
++                            "'%s' not bound to global env in product env" % 
++                                    (attrnm,))
++                    self.assertIs(translation.im_func, original.im_func,
++                            "'%s' function differs in product env" % (attrnm,))
++                elif isinstance(original, (property, Option)):
++                    # Intercept property access e.g. properties, Option, ...
++                    setattr(self.env.__class__, attrnm, 
++                        property_mock(attrnm, self.env))
++
++                    with self.assertRaises(AttrSuccess) as cm_test_attr:
++                        getattr(self.product_env, attrnm)
++                else:
++                    self.fail("Environment member %s has unexpected type" % 
++                            (repr(original),))
++
++        finally:
++            self.env.__class__ = EnvironmentStub
++
++        for attrnm in 'component_activated _component_rules ' \
++                'enable_component get_known_users get_repository ' \
++                'is_component_enabled _component_name'.split():
++            original = getattr(Environment, attrnm)
++            if isinstance(original, MethodType):
++                translation = getattr(self.product_env, attrnm)
++                self.assertIs(translation.im_self, self.product_env,
++                        "'%s' not bound to product env" % (attrnm,))
++                self.assertIs(translation.im_func, original.im_func,
++                        "'%s' function differs in product env" % (attrnm,))
++            elif isinstance(original, property):
++                translation = getattr(ProductEnvironment, attrnm)
++                self.assertIs(original, translation,
++                        "'%s' property differs in product env" % (attrnm,))
++
++    def test_typecheck(self):
++        self._load_product_from_data(self.env, 'tp2')
++        with self.assertRaises(TypeError) as cm_test:
++            new_env = ProductEnvironment(self.product_env, 'tp2')
++
++        #msg = str(cm_test.exception)
++        #expected_msg = "Initializer must be called with " \
++        #        "trac.env.Environment instance as first argument " \
++        #        "(got multiproduct.env.ProductEnvironment instance instead)"
++        #self.assertEqual(msg, expected_msg)
++
++    def tearDown(self):
++        # Release reference to transient environment mock object
++        self.env = None
++        self.product_env = None
++
++def test_suite():
++    return unittest.TestSuite([
++            unittest.makeSuite(ProductEnvTestCase,'test'),
++            unittest.makeSuite(ProductEnvApiTestCase, 'test')
++        ])
+ 
+ if __name__ == '__main__':
+-    unittest.main(defaultTest='suite')
++    unittest.main(defaultTest='test_suite')
+ 
+diff -r 00a5e8114cb4 bloodhound_theme/setup.py
+--- a/bloodhound_theme/setup.py	Sun Jan 13 10:19:19 2013 +0000
++++ b/bloodhound_theme/setup.py	Mon Jan 14 01:49:36 2013 -0500
+@@ -19,6 +19,7 @@
+ #  under the License.
+ 
+ from setuptools import setup
++import sys
+ 
+ setup(
+   name = 'BloodhoundTheme',
+@@ -34,6 +35,7 @@
+       'Framework :: Trac',
+     ],
+   install_requires = ['BloodhoundDashboardPlugin', 'TracThemeEngine', 'Trac'],
++  tests_require = ['unittest2'] if sys.version_info < (2, 7) else [],
+   entry_points = {
+       'trac.plugins': [
+             'bhtheme.theme = bhtheme.theme',

t350/t350_r1432088_product_component_enable.diff

 # HG changeset patch
-# Parent 3fc853d53fd5cd35add505162d5da3ac849caa3b
+# Parent 0ce445d6efc5f22823d02def8b5988e90d145246
 BH Multiproduct #350 : Product-specific component rules (enable / disable)
 
-diff -r 3fc853d53fd5 bloodhound_multiproduct/tests/env.py
---- a/bloodhound_multiproduct/tests/env.py	Fri Jan 11 15:17:56 2013 +0000
-+++ b/bloodhound_multiproduct/tests/env.py	Sun Jan 13 00:57:47 2013 -0500
-@@ -20,9 +20,15 @@
+diff -r 0ce445d6efc5 bloodhound_multiproduct/tests/env.py
+--- a/bloodhound_multiproduct/tests/env.py	Mon Jan 14 19:07:09 2013 -0500
++++ b/bloodhound_multiproduct/tests/env.py	Mon Jan 14 19:19:04 2013 -0500
+@@ -228,6 +228,28 @@
+         #        "(got multiproduct.env.ProductEnvironment instance instead)"
+         #self.assertEqual(msg, expected_msg)
  
- import os.path
- import shutil
-+from sys import version_info
- import tempfile
--import unittest
- 
-+if version_info < (2, 7):
-+    import unittest2 as unittest
-+else:
-+    import unittest
-+
-+from trac.core import Component
- from trac.test import EnvironmentStub
- from trac.tests.env import EnvironmentTestCase
- 
-@@ -100,6 +106,14 @@
-             # table remains but database version is deleted
-             pass
- 
-+    def _mp_setup(self):
-+        """Shortcut for quick product-aware environment setup.
-+        """
-+        self.env = self._setup_test_env()
-+        self._upgrade_mp(self.env)
-+        self._setup_test_log(self.env)
-+        self._load_product_from_data(self.env, self.default_product)
-+
- class ProductEnvTestCase(EnvironmentTestCase, MultiproductTestCase):
-     r"""Test cases for Trac environments rewritten for product environments
-     """
-@@ -131,8 +145,40 @@
- 
-         EnvironmentTestCase.tearDown(self)
- 
-+class ProductEnvApiTestCase(MultiproductTestCase):
-+    """Assertions for Apache(TM) Bloodhound product-specific extensions in
-+    [https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003 BEP 3]
-+    """
-+    def setUp(self):
-+        self._mp_setup()
-+
 +    def test_component_enable(self):
 +        class C(Component):
 +            pass
 +        self.assertIsNot(self.env[C], None)
 +        self.assertIsNot(product_env[C], None)
 +
- def suite():
--    return unittest.makeSuite(ProductEnvTestCase,'test')
-+    return unittest.TestSuite([
-+            unittest.makeSuite(ProductEnvTestCase,'test'),
-+            unittest.makeSuite(ProductEnvApiTestCase, 'test')
-+        ])
- 
- if __name__ == '__main__':
-     unittest.main(defaultTest='suite')
-diff -r 3fc853d53fd5 bloodhound_theme/setup.py
---- a/bloodhound_theme/setup.py	Fri Jan 11 15:17:56 2013 +0000
-+++ b/bloodhound_theme/setup.py	Sun Jan 13 00:57:47 2013 -0500
-@@ -19,6 +19,7 @@
- #  under the License.
- 
- from setuptools import setup
-+from sys import version_info
- 
- setup(
-   name = 'BloodhoundTheme',
-@@ -33,7 +34,9 @@
-   classifiers = [
-       'Framework :: Trac',
-     ],
--  install_requires = ['BloodhoundDashboardPlugin', 'TracThemeEngine', 'Trac'],
-+  install_requires = ['BloodhoundDashboardPlugin', 'TracThemeEngine', 'Trac',
-+          'sqlparse'],
-+  tests_require = ['unittest2'] if version_info < (2, 7) else [],
-   entry_points = {
-       'trac.plugins': [
-             'bhtheme.theme = bhtheme.theme',
+     def tearDown(self):
+         # Release reference to transient environment mock object
+         self.env = None