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',