def test_cardinality_on_projected_role(self): """ Test writing cardinality constraint on a role projected from ternary fact type. This type of constraint is not permitted in Norma, so we create it dynamically in the test. """ model = NormaLoader(TestData.path("canonical_example.orm")).model make_all_independent(model) # So that IDMC doesn't apply # Remove all constraints to_delete = [c for c in model.constraints] for cons in to_delete: model.constraints.remove(cons) # Create cardinality constraint ranges = [CardinalityRange(2, 4)] role = model.get("ObjectTypes.B").roles[0] model.add(CardinalityConstraint(ranges, name="CC1", covers=[role])) tempdir = os.path.join(self.tempdir, "test_cardinality_on_projected_role") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // CC1\n", " CC1_projection(x) <- model:predicates:AAndBLikeC(_, x, _).\n", " CC1_cardinality[] = n <- agg<< n = count() >> CC1_projection(_).\n", " CC1_cardinality[] = n -> (2 <= n, n <= 4).\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_adding_invalid_element(self): """ Confirm exception is raised if an invalid object is passed to the _add() method.""" loader = NormaLoader(self.data_dir + "empty_model.orm") with self.assertRaises(Exception) as ex: loader._add(ModelElement(uid="123", name="ABC")) self.assertEqual(ex.exception.message, "Unexpected model element type")
def test_unsat_euc_strengthening(self): """ Test case where EUC Strengthening makes model unsat. """ fname = os.path.join(self.data_dir, "euc_strengthening_unsat.orm") model = NormaLoader(fname).model self.assertIsNotNone( ORMMinusModel(model=model, experimental=False).solution) model = NormaLoader(fname).model self.assertIsNone( ORMMinusModel(model=model, experimental=True).solution)
def test_unsat_subset(self): """ Test a model that is unsatisfiable due to a subset constraint. """ fname = TestDataLocator.path("subset_unsat.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=100, experimental=True) self.assertIsNone(ormminus.solution) model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=100, experimental=False) self.assertIsNotNone(ormminus.solution)
def test_unsat_overlapping_iuc(self): """ Test case where OverlappingIFCTransform makes model unsat. """ fname = os.path.join(self.data_dir, "overlapping_iuc_unsat_if_strengthened.orm") model = NormaLoader(fname).model self.assertIsNotNone( ORMMinusModel(model=model, experimental=False).solution) model = NormaLoader(fname).model self.assertIsNone( ORMMinusModel(model=model, experimental=True).solution)
def test_join_subset_2(self): """ Test writing join subset constraints. """ model = NormaLoader(TestData.path("join_subset_simple_2.orm")).model make_all_independent(model) # So that IDMC doesn't apply # Remove all but SubsetConstraints to_delete = [ c for c in model.constraints if not c.name.startswith("SUB") ] for cons in to_delete: model.constraints.remove(cons) tempdir = os.path.join(self.tempdir, "test_join_subset_2") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // SUB\n", (" JoinFact_SUB_subset(AHasB_A, BHasC_C, AHasB_B, BHasC_B) <- " "model:predicates:AHasB(AHasB_A, AHasB_B), " "model:predicates:BHasC(BHasC_B, BHasC_C), " "AHasB_B = BHasC_B.\n"), (" JoinFact_SUB_superset(AHasE_A, EHasD_D, AHasE_E, EHasD_E) <- " "model:predicates:AHasE(AHasE_A, AHasE_E), " "model:predicates:EHasD(EHasD_E, EHasD_D), " "AHasE_E = EHasD_E.\n"), " JoinFact_SUB_subset(_0, _1, _, _) -> JoinFact_SUB_superset(_0, _1, _, _).\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_subset_2(self): """ Test writing subset constraints. """ model = NormaLoader(TestData.path("subset_constraint_2.orm"), deontic=True).model make_all_independent(model) # So that IDMC doesn't apply # Remove all but SubsetConstraints to_delete = [ c for c in model.constraints if not c.name.startswith("SubsetConstraint") ] for cons in to_delete: model.constraints.remove(cons) tempdir = os.path.join(self.tempdir, "test_subset_2") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // SubsetConstraint1\n", " model:predicates:AAndBAndC(_0, _, _1) -> model:predicates:CAndBAndA(_1, _, _0).\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_bad_root_element(self): """ Confirm that exception is raised when the root element of the XML is not <ormRoot:ORM2>. """ with self.assertRaises(Exception) as ex: NormaLoader(self.data_dir + "bad_root_element.orm") self.assertEqual(ex.exception.message, "Root of input file must be <ormRoot:ORM2>.")
def test_bad_filename_extension(self): """ Confirm that exception is raised when input file has .txt extension rather than .orm extension. """ with self.assertRaises(Exception) as ex: NormaLoader("test.txt") self.assertEqual(ex.exception.message, "Input filename must have .orm extension.")
def test_load_equality_constraint(self): """ Test loading of equality constraint. """ fname = TestDataLocator.path("equality_four_role.orm") loader = NormaLoader(fname) model = loader.model self.assertItemsEqual(loader.omissions, []) cons_list = model.constraints.of_type(Constraint.EqualityConstraint) self.assertEquals(3, len(cons_list)) eq1 = model.constraints.get("EQ") eq2 = model.constraints.get("EQ2") eq3 = model.constraints.get("EQ3") self.assertTrue(isinstance(eq1, Constraint.EqualityConstraint)) self.assertTrue(isinstance(eq2, Constraint.EqualityConstraint)) self.assertTrue(isinstance(eq3, Constraint.EqualityConstraint)) obj = model.object_types.get("A") self.assertEquals(eq1.superset, [obj.roles[0]]) self.assertEquals(eq1.subset, [obj.roles[1]]) self.assertEquals(eq1.covers, eq1.subset + eq1.superset) self.assertEquals(eq2.superset, [obj.roles[0]]) self.assertEquals(eq2.subset, [obj.roles[2]]) self.assertEquals(eq2.covers, eq2.subset + eq2.superset) self.assertEquals(eq3.superset, [obj.roles[0]]) self.assertEquals(eq3.subset, [obj.roles[3]]) self.assertEquals(eq3.covers, eq3.subset + eq3.superset)
def test_config_file(self): """ Test contents of config.py file. """ model = NormaLoader(TestData.path("empty_model.orm")).model pop = Population(ORMMinusModel(model)) tempdir = os.path.join(self.tempdir, "test_config_file") logiql = LogiQL(model, pop, tempdir, make=False) config_file = os.path.join(tempdir, "config.py") self.assertTrue(os.path.exists(config_file)) with open(config_file, "r") as infile: actual = infile.readlines() expected = [ "from lbconfig.api import *\n", "lbconfig_package('test_config_file', version='0.1', default_targets=['lb-libraries'])\n", "depends_on(logicblox_dep)\n", "lb_library(name='test_config_file', srcdir='.')\n", "check_lb_workspace(name='test_config_file', libraries=['test_config_file'])\n" ] self.assertItemsEqual(actual, expected)
def test_unsat_join_equality(self): """ Test a model that is unsatisfiable due to a join equality constraint. """ fname = TestDataLocator.path("join_equality_unsat.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=100, experimental=True) self.assertEquals(ormminus.ignored, []) self.assertIsNone(ormminus.solution) model = NormaLoader(fname).model eq = model.constraints.get("EQ") ormminus = ORMMinusModel(model, ubound=100, experimental=False) self.assertEquals(ormminus.ignored, [eq]) self.assertIsNotNone(ormminus.solution)
def test_unsat_subtype_with_value_constraint(self): """ Test a model that is unsatisfiable because the intersection of the value constraints for the root type and subtype is empty. """ fname = TestDataLocator.path("unsat_subtype_with_value_constraint.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=30) self.assertIsNone(ormminus.solution)
def test_unsat_role_and_value_type_value_constraint(self): """ Test unsatisfiable combination of role and value type value constraint. """ fname = os.path.join( self.data_dir, "role_value_constraint_and_type_value_constraint.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=10) self.assertItemsEqual(ormminus.ignored, []) a_id = model.object_types.get("A_id") cc1 = model.constraints.get("CC1") rvc1 = model.constraints.get("RVC1") vc1 = model.constraints.get("VC1") value_cons = model.constraints.of_type(Constraint.ValueConstraint) self.assertEquals(len(value_cons), 2) self.assertItemsEqual(value_cons, [rvc1, vc1]) self.assertEquals(rvc1.covers, [a_id]) self.assertEquals(vc1.covers, [a_id]) self.assertItemsEqual(a_id.covered_by, [rvc1, vc1, cc1]) # Key test 1: domain of a_id is intersection of two value constraints self.assertItemsEqual(a_id.domain.draw(10), [1, 2]) # Key test 2: model is unsat due to cardinality constraint self.assertIsNone(ormminus.solution)
def test_objectification_inequalities(self): """ Test a model with objectifications. """ fname = os.path.join(self.data_dir, "objectification.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model, ubound=10) # Confirm NORMA doesn't copy the cardinality constraint to the fact type self.assertEquals( len(model.constraints.of_type(Constraint.CardinalityConstraint)), 1) # Check for existence of expected inequalities actual = set([ineq.tostring() for ineq in ormminus._ineqsys]) self.assertIn("ObjectTypes.AHasB <= FactTypes.AHasB", actual) self.assertIn("FactTypes.AHasB <= ObjectTypes.AHasB", actual) self.assertIn("ObjectTypes.ALikesB <= FactTypes.ALikesB", actual) self.assertIn("FactTypes.ALikesB <= ObjectTypes.ALikesB", actual) self.assertIn("ObjectTypes.AEnjoysB <= FactTypes.AEnjoysB", actual) self.assertIn("FactTypes.AEnjoysB <= ObjectTypes.AEnjoysB", actual) # Check that solution has expected relations self.assertEquals(ormminus.solution["ObjectTypes.AHasB"], ormminus.solution["FactTypes.AHasB"]) self.assertEquals(ormminus.solution["ObjectTypes.AHasB"], 10) self.assertEquals(ormminus.solution["ObjectTypes.ALikesB"], ormminus.solution["FactTypes.ALikesB"]) self.assertEquals(ormminus.solution["ObjectTypes.ALikesB"], 5) self.assertEquals(ormminus.solution["ObjectTypes.AEnjoysB"], ormminus.solution["FactTypes.AEnjoysB"]) self.assertEquals(ormminus.solution["ObjectTypes.AEnjoysB"], 3)
def test_fact_type_parts(self): """ Test that fact type parts (e.g. roles vs role sequences) are correctly identified. """ fname = os.path.join(self.data_dir, "fact_type_parts.orm") model = NormaLoader(fname).model ormminus = ORMMinusModel(model=model, ubound=5) fact_type = model.fact_types.get("V1HasV2HasV3") role1, role2, role3 = fact_type.roles self.assertItemsEqual(ormminus.get_parts(fact_type), [role1, role2, role3]) fact_type = model.fact_types.get("V4HasV5") role1, role2 = fact_type.roles cons = model.constraints.get("IUC4") self.assertItemsEqual(ormminus.get_parts(fact_type), [cons, role2]) fact_type = model.fact_types.get("V6HasV7") cons = model.constraints.get("IUC5") self.assertItemsEqual(ormminus.get_parts(fact_type), [cons]) fact_type = model.fact_types.get("V8HasV9") cons = model.constraints.get("IFC1") self.assertItemsEqual(ormminus.get_parts(fact_type), [cons]) fact_type = model.fact_types.get("Seven_ary") roles = fact_type.roles iuc = model.constraints.get("IUC11") ifc = model.constraints.get("IFC2") self.assertItemsEqual(ormminus.get_parts(fact_type), [iuc, ifc, roles[3], roles[6]])
def test_import_types(self): """ Test writing type import logic. """ model = NormaLoader(TestData.path("object_type_tests_2.orm")).model pop = Population(ORMMinusModel(model)) tempdir = os.path.join(self.tempdir, "test_import_types") logiql = LogiQL(model, pop, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "import.logic")) AFile = os.path.join(tempdir, "import", "ObjectTypes.A.csv") BFile = os.path.join(tempdir, "import", "ObjectTypes.B.csv") expected = [ "// Import code for A\n", "_import_types_A[offset] = v -> int(offset), string(v).\n", 'lang:physical:filePath[`_import_types_A] = "{0}".\n'.format( AFile), 'lang:physical:fileMode[`_import_types_A] = "import".\n', "+model:types:A(x), +model:types:A_constructor(x: v) <- _import_types_A[_] = v.\n", # Note here the constructor is A_constructor because B is a subtype of A "// Import code for B\n", "_import_types_B[offset] = v -> int(offset), string(v).\n", 'lang:physical:filePath[`_import_types_B] = "{0}".\n'.format( BFile), 'lang:physical:fileMode[`_import_types_B] = "import".\n', "+model:types:B(x), +model:types:A_constructor(x: v) <- _import_types_B[_] = v.\n" ] self.assertItemsEqual(actual, expected)
def test_types_without_subtype(self): """ Test writing out of types without any subtypes present. """ model = NormaLoader(TestData.path("objectification.orm")).model pop = Population(ORMMinusModel(model)) tempdir = os.path.join(self.tempdir, "test_types_without_subtype") logiql = LogiQL(model, pop, tempdir, make=False) types_file = os.path.join(tempdir, "model", "types.logic") with open(types_file, 'r') as infile: actual = infile.readlines() expected = [ "block(`types) {\n", " export(`{\n", " A(x), A_constructor(x: v) -> string(v).\n", "\n", " B(x), B_constructor(x: v) -> string(v).\n", "\n", " AHasB(x), AHasB_constructor(x: v) -> string(v).\n", "\n", " ALikesB(x), ALikesB_constructor(x: v) -> string(v).\n", "\n", " AEnjoysB(x), AEnjoysB_constructor(x: v) -> string(v).\n", "\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_data_type_load(self): """ Confirm data types are loaded properly for Value Types. """ model = NormaLoader(self.data_dir + "data_types.orm").model ot = model.object_types self.assertIsInstance(ot.get("A").domain, Domain.StringDomain) # data type undefined self.assertIsInstance(ot.get("B").domain, Domain.BoolDomain) # True or false self.assertIsInstance(ot.get("C").domain, Domain.BoolDomain) # Yes or no self.assertIsInstance(ot.get("D").domain, Domain.IntegerDomain) # auto counter self.assertIsInstance(ot.get("E").domain, Domain.FloatDomain) # float self.assertIsInstance(ot.get("Special").domain, Domain.FloatDomain) # float self.assertIsInstance(ot.get("F").domain, Domain.FloatDomain) # money self.assertIsInstance(ot.get("G").domain, Domain.IntegerDomain) # big int self.assertIsInstance(ot.get("H").domain, Domain.DateTimeDomain) # timestamp self.assertIsInstance(ot.get("I").domain, Domain.DateDomain) # date self.assertIsInstance(ot.get("J").domain, Domain.TimeDomain) # time self.assertIsInstance(ot.get("K").domain, Domain.StringDomain) # text # Confirm for A and K that prefix is 'A' and 'K' obj = ot.get("A") actual = obj.domain.draw(2) expect = ['A0', 'A1'] self.assertItemsEqual(actual, expect) obj = ot.get("K") actual = obj.domain.draw(2) expect = ['K0', 'K1'] self.assertItemsEqual(actual, expect)
def test_subtypes(self): """ Test writing out of subtypes. """ model = NormaLoader(TestData.path("object_type_tests.orm")).model pop = Population(ORMMinusModel(model)) tempdir = os.path.join(self.tempdir, "test_subtypes") logiql = LogiQL(model, pop, tempdir, make=False) types_file = os.path.join(tempdir, "model", "types.logic") with open(types_file, 'r') as infile: actual = [line for line in infile.readlines() if line != "\n"] expected = [ "block(`types) {\n", " export(`{\n", " A(x), A_constructor(x: v) -> string(v).\n", " A_id(x), A_id_constructor(x: v) -> string(v).\n", " B(x) -> A(x).\n", " C(x) -> B(x).\n", " CHasV1(x), CHasV1_constructor(x: v) -> string(v).\n", " V1(x), V1_constructor(x: v) -> string(v).\n", " V2(x), V2_constructor(x: v) -> string(v).\n", " V1HasV2(x), V1HasV2_constructor(x: v) -> string(v).\n", " D(x), D_constructor(x: v) -> string(v).\n", " D_id(x), D_id_constructor(x: v) -> string(v).\n", " }),\n", " clauses(`{\n", " lang:entity(`B).\n", " lang:entity(`C).\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_deprecated_join(self): """ Confirm exception fires for models containing deprecated join constraint. """ with self.assertRaises(Exception) as ex: loader = NormaLoader(self.data_dir + "deprecated_join.orm") self.assertEquals( ex.exception.message, "Subset constraint SubsetConstraint1 has deprecated join rule.")
def test_unsupported_constraint(self): """ Test handling of unsupported constraint. """ self.log.beforeTest(None) model = NormaLoader(TestData.path("empty_model.orm")).model model.constraints.add(UnsupportedConstraint(name="Dummy")) tempdir = os.path.join(self.tempdir, "test_unsupported_constraint") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // Not implemented: Dummy\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected) expected = [ "WARNING: Constraint not implemented in LogiQL output: Dummy" ] self.assertItemsEqual(self.log.formatLogRecords(), expected) self.log.afterTest(None)
def test_bad_role_sequence_node(self): """ Confirm exception fires for invalid node in RoleSequence. """ with self.assertRaises(Exception) as ex: loader = NormaLoader(self.data_dir + "bad_role_sequence.orm") self.assertEquals( ex.exception.message, "Uniqueness constraint " + "ExternalUniquenessConstraint1 has unexpected role sequence.")
def test_internal_uniq_constraint(self): """ Test writing out of internal uniqueness constraints. """ model = NormaLoader( TestData.path("uniqueness_constraints_2.orm")).model make_all_independent(model) # So that IDMC doesn't apply tempdir = os.path.join(self.tempdir, "test_internal_uniq_constraint") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // InternalUniquenessConstraint1\n", " model:predicates:AHasB(A_, B_), model:predicates:AHasB(A_, B_2) -> B_ = B_2.\n", " // InternalUniquenessConstraint6\n", " model:predicates:BHasC(B_, C_), model:predicates:BHasC(B_, C_) -> .\n", " // InternalUniquenessConstraint5\n", " model:predicates:AHasBHasC(A_, B_, C_), model:predicates:AHasBHasC(A_, B_2, C_) -> B_ = B_2.\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_valid_complex_branching_join_path(self): """ Test valid complex branching join path. """ fname = TestDataLocator.path( "join_rule_valid_complex_branching_path.orm") loader = NormaLoader(fname) model = loader.model cons = model.constraints.get("EUC1") self.assertIsNotNone(cons) self.assertIsNotNone(cons.covers.join_path) self.assertEquals(loader.omissions, []) path = cons.covers.join_path DHasE = model.fact_types.get("DHasE") EHasB = model.fact_types.get("EHasB") BHasC = model.fact_types.get("BHasC") FHasG = model.fact_types.get("FHasG") GHasB = model.fact_types.get("GHasB") HHasG = model.fact_types.get("HHasG") self.assertEquals(path.fact_types, [DHasE, EHasB, GHasB, FHasG, HHasG, BHasC]) self.assertEquals(path.joins, [(DHasE.roles[1], EHasB.roles[0]), (EHasB.roles[1], GHasB.roles[1]), (GHasB.roles[0], FHasG.roles[1]), (GHasB.roles[0], HHasG.roles[1]), (EHasB.roles[1], BHasC.roles[0])])
def test_equality(self): """ Test writing equality constraints. """ model = NormaLoader(TestData.path("equality_tuple.orm")).model make_all_independent(model) # So that IDMC doesn't apply # Remove all but SubsetConstraints to_delete = [ c for c in model.constraints if not c.name.startswith("EQ") ] for cons in to_delete: model.constraints.remove(cons) tempdir = os.path.join(self.tempdir, "test_equality") logiql = LogiQL(model, None, tempdir, make=False) actual = file_lines(os.path.join(tempdir, "model", "constraints.logic")) expected = [ "block(`constraints) {\n", " clauses(`{\n", " // Dummy constraint\n", " string(x) -> string(x).\n", " // EQ\n", " model:predicates:AHasBCD(_0, _1, _, _2) -> model:predicates:AHasBD(_0, _1, _2).\n", " model:predicates:AHasBD(_0, _1, _2) -> model:predicates:AHasBCD(_0, _1, _, _2).\n", " })\n", "} <-- .\n" ] self.assertItemsEqual(actual, expected)
def test_deontic_constraints(self): """ Test that deontic constraints are correctly ignored. """ fname = TestDataLocator.path("deontic_constraints.orm") loader = NormaLoader(fname, deontic=False) model = loader.model self.assertEquals(model.constraints.count(), 2) self.assertIsNotNone(model.constraints.get("IUC1")) self.assertIsNotNone(model.constraints.get("IUC_unary")) self.assertEquals(loader.omissions, []) loader = NormaLoader(fname, deontic=True) model = loader.model self.assertEquals(model.constraints.count(), 3) self.assertIsNotNone(model.constraints.get("IUC2d")) self.assertItemsEqual(loader.omissions, ["Exclusion constraint EXC1"])
def test_bad_cardinality_constraint_3(self): """ Test loading of file with badly named ranges node. """ loader = NormaLoader(self.data_dir + "bad_cardinality_constraint_3.orm") model = loader.model cons = model.constraints.get("C1") self.assertEquals(cons.ranges, [])
def test_card_and_value_constraint_on_implicit_type(self): """ Confirm that cardinality and value constraints on implicit types are ignored. This model contains one of each on an implicit boolean object type. """ loader = NormaLoader(self.data_dir + "constraint_on_implicit_type.orm") model = loader.model self.assertEquals(model.constraints.count(), 1) self.assertIsNotNone(model.constraints.get("IUC1"))
def test_bad_cardinality_constraint_1(self): """ Test loading of file with two cardinality constraints in one node. """ with self.assertRaises(ValueError) as ex: NormaLoader(self.data_dir + "bad_cardinality_constraint_1.orm") self.assertEquals(ex.exception.message, "Unexpected cardinality constraint format")