Author: jure
Date: Tue Jan 15 09:30:22 2013
New Revision: 1433320
URL: http://svn.apache.org/viewvc?rev=1433320&view=rev
Log:
Towards #115, patch t115_r1431447_product_envs_bep3_p1.diff from olemis
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py?rev=1433320&r1=1433319&r2=1433320&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
Tue Jan 15 09:30:22 2013
@@ -46,7 +46,7 @@ class Environment(trac.env.Environment):
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 @@ class ProductEnvironment(Component, Comp
implements(trac.env.ISystemInfoProvider)
- @property
- def system_info_providers(self):
- r"""System info will still be determined by the global environment.
- """
- return self.env.system_info_providers
+ 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
+ """
+ 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 @@ class ProductEnvironment(Component, Comp
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 @@ class ProductEnvironment(Component, Comp
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 @@ class ProductEnvironment(Component, Comp
: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 @@ class ProductEnvironment(Component, Comp
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 @@ class ProductEnvironment(Component, Comp
# 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 @@ class ProductEnvironment(Component, Comp
...
"""
# 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 @@ class ProductEnvironment(Component, Comp
...
"""
# 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 @@ class ProductEnvironment(Component, Comp
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 @@ class ProductEnvironment(Component, Comp
`db_transaction`).
"""
BloodhoundIterableCursor.set_env(self)
- return QueryContextManager(self.env)
+ return QueryContextManager(self.parent)
@property
def db_transaction(self):
@@ -364,7 +366,7 @@ class ProductEnvironment(Component, Comp
(`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 @@ class ProductEnvironment(Component, Comp
"""
# 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 @@ class ProductEnvironment(Component, Comp
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):
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py?rev=1433320&r1=1433319&r2=1433320&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/env.py
Tue Jan 15 09:30:22 2013
@@ -20,9 +20,20 @@
import os.path
import shutil
+import sys
import tempfile
-import unittest
+from sqlite3 import OperationalError
+
+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 +111,14 @@ class MultiproductTestCase(unittest.Test
# 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 +150,98 @@ class ProductEnvTestCase(EnvironmentTest
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')
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py?rev=1433320&r1=1433319&r2=1433320&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py
Tue Jan 15 09:30:22 2013
@@ -19,6 +19,7 @@
# under the License.
from setuptools import setup
+import sys
setup(
name = 'BloodhoundTheme',
@@ -34,6 +35,7 @@ setup(
'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',