def test_commit_and_rollback_value(self): """ Test commit and rollback of value constraints. """ d0 = Domain.IntegerDomain() role = Role(name="R1") obj = ObjectType(name="O1", data_type=d0) d1 = Constraint.ValueDomain() d1.add_range("Dog") rvc = Constraint.ValueConstraint(name="RVC", covers=[role], domain=d1) self.assertEquals(role.covered_by, []) rvc.commit() self.assertEquals(role.covered_by, [rvc]) rvc.rollback() self.assertEquals(role.covered_by, []) vc = Constraint.ValueConstraint(name="VTVC", covers=[obj], domain=d1) self.assertEquals(obj.covered_by, []) self.assertEquals(obj.domain, obj.data_type) self.assertEquals(obj.domain, d0) vc.commit() self.assertEquals(obj.covered_by, [vc]) self.assertEquals(obj.domain, d1) self.assertEquals(obj.data_type, d0) vc.rollback() self.assertEquals(obj.covered_by, []) self.assertEquals(obj.domain, obj.data_type) self.assertEquals(obj.domain, d0)
def test_vc_add_enum(self): """ Test the addition of enumerated items to a value constraint.""" domain = Constraint.ValueDomain() domain.add_range("Dog", "Dog") domain.add_range("Cat") domain.add_range(1.35) domain.add_range(9) cons = Constraint.ValueConstraint(domain, name="VC1") self.assertItemsEqual(cons.domain.draw(4), ["Dog", "Cat", 1.35, 9]) self.assertEquals(cons.size, 4)
def test_vc_too_large(self): """ Test a value constraint range that is too large. """ cons = Constraint.ValueConstraint() with self.assertRaises(Constraint.ValueConstraintError) as ex: cons.domain.add_range(0, Constraint.ValueDomain.MAX_SIZE) self.assertEquals(ex.exception.message, "The range of the value constraint is too large") cons = Constraint.ValueConstraint() with self.assertRaises(Constraint.ValueConstraintError) as ex: cons.domain.add_range(75, float('inf')) self.assertEquals(ex.exception.message, "Value constraints only support integer ranges")
def test_mandatory_is_simple(self): """ Test simple property on MandatoryConstraint. """ role1 = Role(uid="R1", name="R1") role2 = Role(uid="R2", name="R2") cons1 = Constraint.MandatoryConstraint(uid="C1", name="C1", covers=[role1]) cons2 = Constraint.MandatoryConstraint(uid="C2", name="C2", covers=[role1, role2]) self.assertTrue(cons1.simple) self.assertFalse(cons2.simple)
def test_of_type_without_hit(self): """ Test of_type method with empty result. """ cons_set = Constraint.ConstraintSet() cons1 = Constraint.SubsetConstraint(uid="1", name="S1") cons2 = Constraint.ValueConstraint(uid="2", name="V1") cons3 = Constraint.SubsetConstraint(uid="3", name="S2") cons_set.add(cons1) cons_set.add(cons2) cons_set.add(cons3) actual = cons_set.of_type(Constraint.MandatoryConstraint) expect = [] self.assertItemsEqual(actual, expect)
def test_vc_bad_ranges(self): """ Test a value constraint with a range of non-integers. """ domain = Constraint.ValueDomain() with self.assertRaises(Constraint.ValueConstraintError) as ex: domain.add_range("1.45", "5.6") self.assertEquals(ex.exception.message, "Value constraints only support integer ranges")
def test_commit_and_rollback_subtype(self): """ Test commit and rollback of a subtype constraint. """ obj1 = ObjectType(name="O1") obj2 = ObjectType(name="O2") cons = Constraint.SubtypeConstraint(subtype=obj1, supertype=obj2) self.assertEquals(obj1.covered_by, []) self.assertEquals(obj1.direct_subtypes, []) self.assertEquals(obj1.direct_supertypes, []) self.assertEquals(obj2.covered_by, []) self.assertEquals(obj2.direct_subtypes, []) self.assertEquals(obj2.direct_supertypes, []) cons.commit() self.assertEquals(obj1.covered_by, [cons]) self.assertEquals(obj1.direct_subtypes, []) self.assertEquals(obj1.direct_supertypes, [obj2]) self.assertEquals(obj2.covered_by, [cons]) self.assertEquals(obj2.direct_subtypes, [obj1]) self.assertEquals(obj2.direct_supertypes, []) with self.assertRaises(NotImplementedError): cons.rollback() # The commented out code below was used before I decided to raise # NotImplementedError for rollback. See the comments in rollback(). """
def test_commit_rollback_ior(self): """ Test commit and rollback of inclusive-or constraint. """ role1 = Role(name="R1") role2 = Role(name="R2") cons = Constraint.MandatoryConstraint(name="M1", covers=[role1, role2]) self.assertFalse(cons.simple) self.assertEquals(role1.covered_by, []) self.assertEquals(role2.covered_by, []) self.assertFalse(role1.mandatory) self.assertFalse(role2.mandatory) cons.commit() self.assertEquals(role1.covered_by, [cons]) self.assertEquals(role2.covered_by, [cons]) self.assertFalse(role1.mandatory) # False because cons is not simple self.assertFalse(role2.mandatory) # False because cons is not simple cons.rollback() self.assertEquals(role1.covered_by, []) self.assertEquals(role2.covered_by, []) self.assertFalse(role1.mandatory) self.assertFalse(role2.mandatory)
def _load_subtype_fact(self, xml_node): """ Load a subtype fact, which indicates a subtype constraint. Note, we chose not to move this node under <Constraints>, because it must be loaded prior to any associated XOR/IOR constraints. """ attribs, name = get_basic_attribs(xml_node) # Get super and sub type XML nodes factroles = find(xml_node, "FactRoles") super_node = find(factroles, "SupertypeMetaRole") sub_node = find(factroles, "SubtypeMetaRole") supertype_node = find(super_node, "RolePlayer") subtype_node = find(sub_node, "RolePlayer") # Look-up the corresponding object types try: supertype = self._elements[supertype_node.get("ref")] subtype = self._elements[subtype_node.get("ref")] except KeyError: raise Exception("Cannot load subtype constraint.") # Does this subtype constraint provide a path to the preferred ID? path = (xml_node.get("PreferredIdentificationPath") == "true") # Create constraint cons = Constraint.SubtypeConstraint(subtype, supertype, path, **attribs) # If there are additional constraints on the subtype (e.g. XOR or IOR), # their role sequence will consist of the subtype fact's roles. We will # redirect the id for those roles to this constraint, so that the covers # attribute is a list of SubtypeConstraints for constraints on subtypes. self._elements[super_node.get("id")] = cons self._elements[sub_node.get("id")] = cons self._add(cons)
def _load_cardinality_constraint(self, node): """ Load cardinality constraint. """ attribs, name = get_basic_attribs(node) attribs['covers'] = self._get_covered_element(node) attribs['ranges'] = self._load_cardinality_ranges(node) return Constraint.CardinalityConstraint(**attribs)
def test_vc_int_ranges(self): """ Test the addition of value ranges to a value constraint with default inclusion values. """ cons = Constraint.ValueConstraint(name="VC1") cons.domain.add_range("1", "4") self.assertItemsEqual(cons.domain.draw(10), [1, 2, 3, 4]) self.assertEquals(cons.size, 4) cons.domain.add_range("3", "5") self.assertItemsEqual(cons.domain.draw(10), [1, 2, 3, 4, 5])
def test_vc_int_ranges_open(self): """ Test the addition of value ranges to a value constraint with explicit inclusion values. """ cons = Constraint.ValueConstraint(uid="1", name="VC1") cons.domain.add_range("1", "2", min_open=True) cons.domain.add_range("5", "10", max_open=True) cons.domain.add_range("11", "13", min_open=True, max_open=True) self.assertItemsEqual(cons.domain.draw(10), [2, 5, 6, 7, 8, 9, 12]) self.assertEquals(cons.size, 7)
def test_commit_and_rollback(self): """ Test committing and rolling back constraints on a model. """ model = Model() obj1 = ObjectType(name="O1") obj2 = ObjectType(name="O2") fact = FactType(name="F1") role1 = fact.add_role(player=obj1, name="R1") role2 = fact.add_role(player=obj2, name="R2") cons1 = Constraint.MandatoryConstraint(name="M1", covers=[role1]) cons2 = Constraint.UniquenessConstraint(name="U1", covers=[role1, role2]) cons3 = Constraint.ValueConstraint(name="V1", covers=[obj1]) for element in [obj1, obj2, fact, cons1, cons2, cons3]: model.add(element) self.assertEquals(model.constraints.get("M1").covers, [role1]) self.assertEquals(model.constraints.get("U1").covers, [role1, role2]) self.assertEquals(model.constraints.get("V1").covers, [obj1]) self.assertEquals(role1.covered_by, [cons1, cons2]) self.assertEquals(role2.covered_by, [cons2]) self.assertEquals(obj1.covered_by, [cons3]) model.remove(cons2) model.remove(cons3) self.assertEquals(model.constraints.get("M1"), cons1) self.assertEquals(model.constraints.get("U1"), None) self.assertEquals(model.constraints.get("V1"), None) self.assertEquals(role1.covered_by, [cons1]) self.assertEquals(role2.covered_by, []) self.assertEquals(obj1.covered_by, []) # Test that additional rollback has no effect model.remove(cons3) self.assertEquals(model.constraints.get("M1"), cons1) self.assertEquals(model.constraints.get("V1"), None) self.assertEquals(obj1.covered_by, [])
def test_add_subset_roles(self): """ Test adding subset and superset roles to subset constraint. """ role1 = Role(name="R1") role2 = Role(name="R2") cons = Constraint.SubsetConstraint(name="S1", subset=[role1], superset=[role2]) self.assertItemsEqual(cons.covers, [role1, role2]) self.assertEquals(cons.subset, [role1]) self.assertEquals(cons.superset, [role2])
def _load_cardinality_ranges(self, parent_node): """ Load a list of cardinality ranges. """ ranges = [] isrange = lambda x: local_tag(x) == 'CardinalityRange' for node in filter(isrange, node_collection(parent_node, "Ranges")): lower = int(node.get("From")) # "From" attribute is mandatory upper = node.get("To") # "To" attribute is optional upper = int(upper) if upper else None ranges.append(Constraint.CardinalityRange(lower, upper)) return ranges
def test_commit_rollback_iuc(self): """ Test commit and rollback of internal uniqueness constraints.""" obj1 = EntityType(name="O1") obj2 = EntityType(name="V1") fact1 = FactType(name="F1") role1 = fact1.add_role(player=obj1) role2 = fact1.add_role(player=obj2) fact1.commit() fact2 = FactType(name="F2") role3 = fact2.add_role(player=obj1) fact2.commit() cons1 = Constraint.UniquenessConstraint(covers=[role1], identifier_for=None) cons2 = Constraint.UniquenessConstraint(covers=[role2], identifier_for=obj1) self.assertEquals(role1.covered_by, []) self.assertEquals(role2.covered_by, []) self.assertEquals(obj1.identifying_constraint, None) self.assertEquals(obj1.ref_roles, []) self.assertItemsEqual(obj1.roles, [role1, role3]) cons1.commit() cons2.commit() self.assertEquals(role1.covered_by, [cons1]) self.assertEquals(role2.covered_by, [cons2]) self.assertEquals(obj1.identifying_constraint, cons2) self.assertEquals(obj1.ref_roles, [role1]) cons1.rollback() cons2.rollback() self.assertEquals(role1.covered_by, []) self.assertEquals(role2.covered_by, []) self.assertEquals(obj1.identifying_constraint, None) self.assertEquals(obj1.ref_roles, [])
def test_vc_bad_enum(self): """ Test invalid enumerations in value constraint. """ domain = Constraint.ValueDomain() with self.assertRaises(Constraint.ValueConstraintError) as ex: domain.add_range("Dog", max_open=True) self.assertEquals(ex.exception.message, "Value constraints only support integer ranges") with self.assertRaises(Constraint.ValueConstraintError) as ex: domain.add_range("Dog", min_open=True) self.assertEquals(ex.exception.message, "Value constraints only support integer ranges")
def test_vc_invalid_range(self): """ Test a value constraint with an invalid integer range. """ cons = Constraint.ValueConstraint() with self.assertRaises(Constraint.ValueConstraintError) as ex: cons.domain.add_range("12", "13", min_open=True, max_open=True) self.assertEquals(ex.exception.message, "The range of the value constraint is invalid") with self.assertRaises(Constraint.ValueConstraintError) as ex: cons.domain.add_range("3", "2") self.assertEquals(ex.exception.message, "The range of the value constraint is invalid")
def test_commit_and_rollback_affect_on_role_unique(self): """ Test affect of commit and rollback on role.unique """ fact = FactType("AHasB") fact.add_role(ObjectType("A")) fact.add_role(ObjectType("B")) fact.commit() role = fact.roles[0] self.assertFalse(role.unique) # Unique after covered by a simple IUC uniq1 = Constraint.UniquenessConstraint(covers=[role]) uniq1.commit() self.assertTrue(role.unique) # No longer unique after rollback uniq1.rollback() self.assertFalse(role.unique) # Not unique after covered by spanning IUC uniq2 = Constraint.UniquenessConstraint(covers=[role, fact.roles[1]]) uniq2.commit() self.assertFalse(role.unique) # Unique again, covered by simple IUC uniq1.commit() self.assertTrue(role.unique) # Cover by a second simple IUC uniq3 = Constraint.UniquenessConstraint(covers=[role]) uniq3.commit() # Still unique after rollback, because it's still covered by a simple IUC uniq1.rollback() self.assertTrue(role.unique) # No longer unique: not covered by any simple IUCs. uniq3.rollback() self.assertFalse(role.unique)
def _load_value_constraint(self, node): """ Load value constraint. """ attribs, name = get_basic_attribs(node) attribs['covers'] = covers = self._get_covered_element(node) data_type = covers[0].data_type if covers else None try: domain = Constraint.ValueDomain() for value_range in node_collection(node, "ValueRanges"): domain.add_range( min_value=value_range.get("MinValue"), max_value=value_range.get("MaxValue"), min_open=(value_range.get("MinInclusion") == "Open"), max_open=(value_range.get("MaxInclusion") == "Open"), data_type=data_type) except Constraint.ValueConstraintError as ex: reason = ex.message.lower() mesg = "Value constraint {0} because {1}".format(name, reason) self.omissions.append(mesg) return None return Constraint.ValueConstraint(domain, **attribs)
def _load_frequency_constraint(self, xml_node): """ Load frequency constraint. """ attribs, name = get_basic_attribs(xml_node) name = "Frequency constraint " + name # Parse frequency attributes min_freq = int(xml_node.get("MinFrequency")) max_freq = int(xml_node.get("MaxFrequency")) # Build attribute dictionary attribs['min_freq'] = min_freq attribs['max_freq'] = max_freq if max_freq > 0 else float('inf') attribs['covers'] = self._load_role_sequence(xml_node, name) return Constraint.FrequencyConstraint(**attribs)
def _load_subset_constraint(self, xml_node): """ Load subset constraint. """ attribs, name = get_basic_attribs(xml_node) name = "Subset constraint " + name sequences_node = find(xml_node, "RoleSequences") if len(sequences_node) != 2: msg = "{0} does not have exactly two role sequences" raise Exception(msg.format(name)) # Load subset and superset role sequences attribs['subset'] = self._load_role_sequence(sequences_node[0], name) attribs['superset'] = self._load_role_sequence(sequences_node[1], name) return Constraint.SubsetConstraint(**attribs)
def test_commit_rollback_mandatory(self): """ Test commit and rollback of mandatory constraint. """ role = Role(name="R1") cons = Constraint.MandatoryConstraint(name="M1", covers=[role]) self.assertEquals(role.covered_by, []) self.assertFalse(role.mandatory) cons.commit() self.assertEquals(role.covered_by, [cons]) self.assertTrue(role.mandatory) cons.rollback() self.assertEquals(role.covered_by, []) self.assertFalse(role.mandatory)
def _load_uniqueness_constraint(self, xml_node): """ Load uniqueness constraint. """ attribs, name = get_basic_attribs(xml_node) name = "Uniqueness constraint " + name # Get object type that this constraint is a preferred id for pref_node = find(xml_node, "PreferredIdentifierFor") if pref_node is not None: uid = pref_node.get("ref") attribs['identifier_for'] = self._elements.get(uid) # Get sequence of covered roles covers = self._load_role_sequence(xml_node, name) if covers and isinstance(covers[0], Constraint.SubtypeConstraint): return None # Covers a role in an implicit subtype fact else: return Constraint.UniquenessConstraint(covers=covers, **attribs)
def _load_mandatory_constraint(self, xml_node): """ Load mandatory constraint. """ attribs, name = get_basic_attribs(xml_node) implied = (xml_node.get("IsImplied") == "true") covers = self._load_role_sequence(xml_node, "Mandatory constraint " + name) # Lambda function to decide if constraint covers a subtype subtype = lambda x: x and isinstance(x[0], Constraint.SubtypeConstraint ) if implied: return None elif subtype(covers): if len(covers) > 1: # If len == 1 its on the implicit subtype fact self.omissions.append("Subtype inclusive-or constraint " + name) return None else: return Constraint.MandatoryConstraint(covers=covers, **attribs)
def _load_equality_constraint(self, xml_node): """ Load equality constraint. """ attribs, name = get_basic_attribs(xml_node) name = "Equality constraint " + name sequences_node = find(xml_node, "RoleSequences") # If there are > 2 role sequences, we split the equality constraint # into multiple 2-role-sequence equality constraints. Each of them use # sequences_node[0] as the superset sequence and then one of the # subsequent role sequences as their subset sequence. cons_list = [] superset_seq = sequences_node[0] attribs['superset'] = self._load_role_sequence(superset_seq, name) sequences_node.remove(superset_seq) for sequence in sequences_node: attribs['subset'] = self._load_role_sequence(sequence, name) cons_list.append(Constraint.EqualityConstraint(**attribs)) return cons_list
def test_commit_rollback_euc(self): """ Test commit and rollback of external uniqueness constraint.""" obj1 = EntityType(name="O1") obj2 = EntityType(name="V1") obj3 = EntityType(name="V2") fact1 = FactType(name="F1") role11 = fact1.add_role(player=obj1) role12 = fact1.add_role(player=obj2) fact1.commit() fact2 = FactType(name="F2") role21 = fact2.add_role(player=obj1) role23 = fact2.add_role(player=obj3) fact2.commit() fact3 = FactType(name="F3") role31 = fact3.add_role(player=obj1) fact3.commit() cons = Constraint.UniquenessConstraint(covers=[role12, role23], identifier_for=obj1) self.assertEquals(obj1.identifying_constraint, None) self.assertEquals(obj1.ref_roles, []) self.assertItemsEqual(obj1.roles, [role11, role21, role31]) cons.commit() self.assertEquals(obj1.identifying_constraint, cons) self.assertItemsEqual(obj1.ref_roles, [role11, role21]) cons.rollback() self.assertEquals(obj1.identifying_constraint, None) self.assertEquals(obj1.ref_roles, [])
def test_add_object_type(self): """ Test the addition of an object type to a constraint. """ covers = [ObjectType(uid="2", name="O1")] cons = Constraint.Constraint(uid="1", name="C1", covers=covers) self.assertEquals(cons.covers[0].name, "O1")