Source

bloodhound-mq / t115 / t115_r1431447_product_envs_bep3_p1.diff

The branch 't115_bep3_product_env' does not exist.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# HG changeset patch
# Parent 9b04a5a324bee6bf4f6b6d044488f9f7868f9e43
BH Multiproduct #115 : Product environments. Compliance with BEP 3 - part 1

diff -r 9b04a5a324be bloodhound_multiproduct/multiproduct/env.py
--- a/bloodhound_multiproduct/multiproduct/env.py	Mon Jan 14 14:05:28 2013 +0000
+++ b/bloodhound_multiproduct/multiproduct/env.py	Tue Jan 15 09:25:35 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,13 @@
         :param product: product prefix or an instance of
                         multiproduct.model.Product
         """
+        if not isinstance(env, trac.env.Environment):
+            cls = self.__class__
+            raise TypeError("Initializer must be called with " \
+                "trac.env.Environment instance as first argument " \
+                "(got %s instance instead)" % 
+                         (cls.__module__ + '.' + cls.__name__, ))
+
         ComponentManager.__init__(self)
 
         if isinstance(product, Product):
@@ -221,9 +228,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 +238,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 +267,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 +285,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 +296,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 +331,7 @@
           `db_transaction`).
         """
         BloodhoundIterableCursor.set_env(self)
-        return QueryContextManager(self.env)
+        return QueryContextManager(self.parent)
 
     @property
     def db_transaction(self):
@@ -364,7 +367,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 +388,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 +424,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 9b04a5a324be bloodhound_multiproduct/tests/env.py
--- a/bloodhound_multiproduct/tests/env.py	Mon Jan 14 14:05:28 2013 +0000
+++ b/bloodhound_multiproduct/tests/env.py	Tue Jan 15 09:25:35 2013 -0500
@@ -18,11 +18,22 @@
 
 """Tests for Apache(TM) Bloodhound's product environments"""
 
+from inspect import stack
 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
+    from unittest2.case import _AssertRaisesContext
+else:
+    import unittest
+    from unittest.case import _AssertRaisesContext
+
+from trac.config import Option
+from trac.env import Environment
 from trac.test import EnvironmentStub
 from trac.tests.env import EnvironmentTestCase
 
@@ -39,6 +50,55 @@
     to create product-specific subclasses.
     """
 
+    # unittest2 extensions
+
+    exceptFailureMessage = None
+
+    class _AssertRaisesLoggingContext(_AssertRaisesContext):
+        """Add logging capabilities to assertRaises
+        """
+        def __init__(self, expected, test_case, expected_regexp=None):
+            _AssertRaisesContext.__init__(
+                    self, expected, test_case, expected_regexp)
+            self.test_case = test_case
+
+        @staticmethod
+        def _tb_locals(tb):
+            if tb is None:
+                # Inspect interpreter stack two levels up
+                ns = stack()[2][0].f_locals.copy()
+            else:
+                # Traceback already in context
+                ns = tb.tb_frame.f_locals.copy()
+            ns.pop('__builtins__', None)
+            return ns
+
+        def __exit__(self, exc_type, exc_value, tb):
+            try:
+                return _AssertRaisesContext.__exit__(self, 
+                    exc_type, exc_value, tb)
+            except self.failureException, exc:
+                msg = self.test_case.exceptFailureMessage 
+                if msg is not None:
+                    standardMsg = str(exc)
+                    msg = msg % self._tb_locals(tb)
+                    msg = self.test_case._formatMessage(msg, standardMsg)
+                    raise self.failureException(msg)
+                else:
+                    raise
+            finally:
+                # Clear message placeholder
+                self.test_case.exceptFailureMessage = None
+
+    def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
+        """Adds logging capabilities on top of unittest2 implementation.
+        """
+        if callableObj is None:
+            return self._AssertRaisesLoggingContext(excClass, self)
+        else:
+            return unittest.TestCase.assertRaises(
+                    self, excClass, callableObj=None, *args, **kwargs)
+
     # Product data
 
     default_product = 'tp1'
@@ -100,6 +160,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 +199,99 @@
 
         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))
+
+                    self.exceptFailureMessage = 'Property %(attrnm)s'
+                    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 9b04a5a324be bloodhound_theme/setup.py
--- a/bloodhound_theme/setup.py	Mon Jan 14 14:05:28 2013 +0000
+++ b/bloodhound_theme/setup.py	Tue Jan 15 09:25:35 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',
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.