You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by as...@apache.org on 2013/05/20 13:47:24 UTC

svn commit: r1484438 - in /bloodhound/trunk: bloodhound_relations/bhrelations/tests/api.py bloodhound_relations/bhrelations/validation.py installer/bloodhound_setup.py

Author: astaric
Date: Mon May 20 11:47:24 2013
New Revision: 1484438

URL: http://svn.apache.org/r1484438
Log:
Cycle detection for blocker relations. Closes #528.

Modified:
    bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py
    bloodhound/trunk/bloodhound_relations/bhrelations/validation.py
    bloodhound/trunk/installer/bloodhound_setup.py

Modified: bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py
URL: http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py?rev=1484438&r1=1484437&r2=1484438&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py (original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/tests/api.py Mon May 20 11:47:24 2013
@@ -46,12 +46,13 @@ class BaseApiApiTestCase(MultiproductTes
             enable=['trac.*', 'multiproduct.*', 'bhrelations.*']
         )
         env.config.set('bhrelations', 'global_validators',
-                       'NoSelfReferenceValidator,ExclusiveValidator')
+                       'NoSelfReferenceValidator,ExclusiveValidator,'
+                       'BlockerValidator')
         config_name = RelationsSystem.RELATIONS_CONFIG_NAME
         env.config.set(config_name, 'dependency', 'dependson,dependent')
         env.config.set(config_name, 'dependency.validators',
                        'NoCycles,SingleProduct')
-        env.config.set(config_name, 'dependent.blocks', 'true')
+        env.config.set(config_name, 'dependson.blocks', 'true')
         env.config.set(config_name, 'parent_children', 'parent,children')
         env.config.set(config_name, 'parent_children.validators',
                        'OneToMany,SingleProduct,NoCycles')
@@ -65,6 +66,8 @@ class BaseApiApiTestCase(MultiproductTes
         env.config.set(config_name, 'duplicate.validators', 'ReferencesOlder')
         env.config.set(config_name, 'duplicateof.label', 'Duplicate of')
         env.config.set(config_name, 'duplicatedby.label', 'Duplicated by')
+        env.config.set(config_name, 'blocker', 'blockedby,blocks')
+        env.config.set(config_name, 'blockedby.blocks', 'true')
 
         self.global_env = env
         self._upgrade_mp(self.global_env)
@@ -351,7 +354,7 @@ class ApiTestCase(BaseApiApiTestCase):
         #arrange
         ticket1 = self._insert_and_load_ticket("A1")
         ticket2 = self._insert_and_load_ticket("A2")
-        self.relations_system.add(ticket1, ticket2, "dependent")
+        self.relations_system.add(ticket1, ticket2, "dependson")
         #act
         self.req.args["action"] = 'resolve'
         warnings = TicketRelationsSpecifics(self.env).validate_ticket(
@@ -363,7 +366,7 @@ class ApiTestCase(BaseApiApiTestCase):
         #arrange
         ticket1 = self._insert_and_load_ticket("A1")
         ticket2 = self._insert_and_load_ticket("A2", status="closed")
-        self.relations_system.add(ticket1, ticket2, "dependent")
+        self.relations_system.add(ticket1, ticket2, "dependson")
         #act
         self.req.args["action"] = 'resolve'
         warnings = TicketRelationsSpecifics(self.env).validate_ticket(
@@ -547,6 +550,30 @@ class ApiTestCase(BaseApiApiTestCase):
         )
         self.relations_system.add(t2, t1, "duplicateof")
 
+    def test_detects_blocker_cycles(self):
+        t1, t2, t3, t4, t5 = map(self._insert_and_load_ticket, range(5))
+        self.relations_system.add(t1, t2, "blocks")
+        self.relations_system.add(t3, t2, "dependson")
+        self.relations_system.add(t4, t3, "blockedby")
+        self.relations_system.add(t4, t5, "dependent")
+
+        self.assertRaises(ValidationError,
+                          self.relations_system.add, t2, t1, "blocks")
+        self.assertRaises(ValidationError,
+                          self.relations_system.add, t3, t1, "dependent")
+        self.assertRaises(ValidationError,
+                          self.relations_system.add, t1, t2, "blockedby")
+        self.assertRaises(ValidationError,
+                          self.relations_system.add, t1, t5, "dependson")
+
+        self.relations_system.add(t1, t2, "dependent")
+        self.relations_system.add(t2, t3, "blocks")
+        self.relations_system.add(t4, t3, "dependson")
+        self.relations_system.add(t5, t4, "blockedby")
+
+        self.relations_system.add(t1, t2, "refersto")
+        self.relations_system.add(t2, t1, "refersto")
+
 
 class RelationChangingListenerTestCase(BaseApiApiTestCase):
     def test_can_sent_adding_event(self):

Modified: bloodhound/trunk/bloodhound_relations/bhrelations/validation.py
URL: http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_relations/bhrelations/validation.py?rev=1484438&r1=1484437&r2=1484438&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_relations/bhrelations/validation.py (original)
+++ bloodhound/trunk/bloodhound_relations/bhrelations/validation.py Mon May 20 11:47:24 2013
@@ -63,14 +63,16 @@ class Validator(Component):
             else:
                 relation = 'destination, source'
                 origin = 'destination'
+            relation_types = \
+                ','.join("'%s'" % r for r in relation_type.split(','))
             query = """
                 SELECT %(relation)s
                   FROM bloodhound_relations
-                 WHERE type = '%(relation_type)s'
+                 WHERE type IN (%(relation_type)s)
                    AND %(origin)s IN (%(new_nodes)s)
             """ % dict(
                 relation=relation,
-                relation_type=relation_type,
+                relation_type=relation_types,
                 new_nodes=', '.join("'%s'" % n for n in new_nodes),
                 origin=origin)
             new_nodes = set()
@@ -202,6 +204,33 @@ class ReferencesOlderValidator(Validator
                 )
 
 
+class BlockerValidator(Validator):
+    def validate(self, relation):
+        """If a path exists from relation's destination to its source,
+         adding the relation will create a cycle.
+         """
+        rls = RelationsSystem(self.env)
+        if not rls.is_blocker(relation.type):
+            relation = rls.get_reverted_relation(relation)
+        if not relation or not rls.is_blocker(relation.type):
+            return
+
+        blockers = ','.join(b for b, is_blocker in rls._blockers.items()
+                            if is_blocker)
+
+        path = self._find_path(relation.source,
+                               relation.destination,
+                               blockers)
+        if path:
+            cycle_str = map(self.get_resource_name, path)
+            error = 'Cycle in ''%s'': %s' % (
+                self.render_relation_type(relation.type),
+                ' -> '.join(cycle_str))
+            error = ValidationError(error)
+            error.failed_ids = path
+            raise error
+
+
 class ValidationError(TracError):
     def __init__(self, message, title=None, show_traceback=False):
         super(ValidationError, self).__init__(

Modified: bloodhound/trunk/installer/bloodhound_setup.py
URL: http://svn.apache.org/viewvc/bloodhound/trunk/installer/bloodhound_setup.py?rev=1484438&r1=1484437&r2=1484438&view=diff
==============================================================================
--- bloodhound/trunk/installer/bloodhound_setup.py (original)
+++ bloodhound/trunk/installer/bloodhound_setup.py Mon May 20 11:47:24 2013
@@ -91,13 +91,14 @@ BASE_CONFIG = {'components': {'bhtheme.*
                'bhsearch': {'is_default': 'true', 'enable_redirect': 'true'},
                'bhrelations': {
                    'global_validators':
-                       'NoSelfReferenceValidator,ExclusiveValidator',
+                       'NoSelfReferenceValidator,ExclusiveValidator,'
+                       'BlockerValidator',
                },
                'bhrelations_links': {
                     'children.label': 'Child',
                     'dependency': 'dependson,dependent',
                     'dependency.validators': 'NoCycles,SingleProduct',
-                    'dependent.blocks': 'true',
+                    'dependson.blocks': 'true',
                     'dependson.label': 'Depends on',
                     'dependent.label': 'Dependent',
                     'oneway': 'refersto',