def test_select(self, mock_get_answer): (context, t1) = self.init_one_theory('l(1,2). l(3,4).') expr = self.mk_z3_result(context) mock_get_answer.return_value = expr query = ast.parse('l(x,y)')[0] result = context.select(t1, query, True) self.assertEqual(ast.parse('l(1,2). l(3,4).'), result)
def check_equal(self, actual_code, correct_code, msg=None, equal=None): def minus(iter1, iter2, invert=False): extra = [] for i1 in iter1: found = False for i2 in iter2: # for asymmetric equality checks if invert: test_result = equal(i2, i1) else: test_result = equal(i1, i2) if test_result: found = True break if not found: extra.append(i1) return extra if equal is None: equal = lambda x, y: x == y LOG.debug("** Checking equality: %s **", msg) actual = compile.parse(actual_code) correct = compile.parse(correct_code) extra = minus(actual, correct) # in case EQUAL is asymmetric, always supply actual as the first arg missing = minus(correct, actual, invert=True) self.output_diffs(extra, missing, msg) LOG.debug("** Finished equality: %s **", msg)
def test_event_facts(self): # insert event = compile.parse('insert[p(1) :- true]') self.assertEqual(len(event), 1) event = event[0] fact = compile.parse1('p(1) :- true') self.assertEqual(event.formula, fact) self.assertEqual(event.insert, True) self.assertIsNone(event.target) # delete event = compile.parse('delete[p(1) :- true]') self.assertEqual(len(event), 1) event = event[0] fact = compile.parse1('p(1) :- true') self.assertEqual(event.formula, fact) self.assertEqual(event.insert, False) self.assertIsNone(event.target) # insert with policy event = compile.parse('insert[p(1) :- true; "policy"]') self.assertEqual(len(event), 1) event = event[0] fact = compile.parse1('p(1) :- true') self.assertEqual(event.formula, fact) self.assertEqual(event.insert, True) self.assertEqual(event.target, "policy")
def check_err(code, errmsg, msg): try: compile.parse(code) self.fail("Error should have been thrown but was not: " + msg) except exception.PolicyException as e: emsg = "Err message '{}' should include '{}'".format( str(e), errmsg) self.assertTrue(errmsg in str(e), msg + ": " + emsg)
def create_unify(self, atom_string1, atom_string2, msg, change_num, unifier1=None, unifier2=None, recursive_str=False): """Create unification and check basic results.""" def str_uni(u): if recursive_str: return u.recur_str() else: return str(u) def print_unifiers(changes=None): LOG.debug("unifier1: %s", str_uni(unifier1)) LOG.debug("unifier2: %s", str_uni(unifier2)) if changes is not None: LOG.debug("changes: %s", ";".join([str(x) for x in changes])) if msg is not None: self.open(msg) if unifier1 is None: # LOG.debug("Generating new unifier1") unifier1 = topdown.TopDownTheory.new_bi_unifier() if unifier2 is None: # LOG.debug("Generating new unifier2") unifier2 = topdown.TopDownTheory.new_bi_unifier() p1 = compile.parse(atom_string1)[0] p2 = compile.parse(atom_string2)[0] changes = unify.bi_unify_atoms(p1, unifier1, p2, unifier2) self.assertIsNotNone(changes) print_unifiers(changes) p1p = p1.plug(unifier1) p2p = p2.plug(unifier2) print_unifiers(changes) if not p1p == p2p: LOG.debug("Failure: bi-unify(%s, %s) produced %s and %s", p1, p2, str_uni(unifier1), str_uni(unifier2)) LOG.debug("plug(%s, %s) = %s", p1, str_uni(unifier1), p1p) LOG.debug("plug(%s, %s) = %s", p2, str_uni(unifier2), p2p) self.fail() if change_num is not None and len(changes) != change_num: LOG.debug("Failure: bi-unify(%s, %s) produced %s and %s", p1, p2, str_uni(unifier1), str_uni(unifier2)) LOG.debug("plug(%s, %s) = %s", p1, str_uni(unifier1), p1p) LOG.debug("plug(%s, %s) = %s", p2, str_uni(unifier2), p2p) LOG.debug("Expected %s changes; computed %s changes", change_num, len(changes)) self.fail() LOG.debug("unifier1: %s", str_uni(unifier1)) LOG.debug("unifier2: %s", str_uni(unifier2)) if msg is not None: self.open(msg) return (p1, unifier1, p2, unifier2, changes)
def create_unify(self, atom_string1, atom_string2, msg, change_num, unifier1=None, unifier2=None, recursive_str=False): """Create unification and check basic results.""" def str_uni(u): if recursive_str: return u.recur_str() else: return str(u) def print_unifiers(changes=None): LOG.debug("unifier1: %s", str_uni(unifier1)) LOG.debug("unifier2: %s", str_uni(unifier2)) if changes is not None: LOG.debug("changes: %s", ";".join([str(x) for x in changes])) if msg is not None: self.open(msg) if unifier1 is None: # LOG.debug("Generating new unifier1") unifier1 = TopDownTheory.new_bi_unifier() if unifier2 is None: # LOG.debug("Generating new unifier2") unifier2 = TopDownTheory.new_bi_unifier() p1 = compile.parse(atom_string1)[0] p2 = compile.parse(atom_string2)[0] changes = unify.bi_unify_atoms(p1, unifier1, p2, unifier2) self.assertTrue(changes is not None) print_unifiers(changes) p1p = p1.plug(unifier1) p2p = p2.plug(unifier2) print_unifiers(changes) if not p1p == p2p: LOG.debug("Failure: bi-unify(%s, %s) produced %s and %s", p1, p2, str_uni(unifier1), str_uni(unifier2)) LOG.debug("plug(%s, %s) = %s", p1, str_uni(unifier1), p1p) LOG.debug("plug(%s, %s) = %s", p2, str_uni(unifier2), p2p) self.fail() if change_num is not None and len(changes) != change_num: LOG.debug("Failure: bi-unify(%s, %s) produced %s and %s", p1, p2, str_uni(unifier1), str_uni(unifier2)) LOG.debug("plug(%s, %s) = %s", p1, str_uni(unifier1), p1p) LOG.debug("plug(%s, %s) = %s", p2, str_uni(unifier2), p2p) LOG.debug("Expected %s changes; computed %s changes", change_num, len(changes)) self.fail() LOG.debug("unifier1: %s", str_uni(unifier1)) LOG.debug("unifier2: %s", str_uni(unifier2)) if msg is not None: self.open(msg) return (p1, unifier1, p2, unifier2, changes)
def test_old_new_correctness(self): obj = self.MyObject() run = agnostic.Runtime() run.create_policy('test') run.insert('p(x) :- q(x)') run.insert('q(x) :- r(x), not s(x)') run.insert('r(1) r(2) r(3)') run.insert('s(2)') oldp = set(compile.parse('p(1) p(3)')) newp = set(compile.parse('p(1) p(2)')) run.register_trigger('p', lambda old, new: obj.equal(oldp, newp, old, new)) run.update([compile.Event(compile.parse1('s(3)')), compile.Event(compile.parse1('s(2)'), insert=False)]) self.assertEqual(obj.equals, True)
def check_unify_fail(self, atom_string1, atom_string2, msg): """Check that the bi-unification fails.""" self.open(msg) unifier1 = topdown.TopDownTheory.new_bi_unifier() unifier2 = topdown.TopDownTheory.new_bi_unifier() p1 = compile.parse(atom_string1)[0] p2 = compile.parse(atom_string2)[0] changes = unify.bi_unify_atoms(p1, unifier1, p2, unifier2) if changes is not None: LOG.debug("Failure failure: bi-unify(%s, %s) produced %s and %s", p1, p2, unifier1, unifier2) LOG.debug("plug(%s, %s) = %s", p1, unifier1, p1.plug(unifier1)) LOG.debug("plug(%s, %s) = %s", p2, unifier2, p2.plug(unifier2)) self.fail() self.close(msg)
def test_rule_recursion(self): rules = compile.parse('p(x) :- q(x), r(x) q(x) :- r(x) r(x) :- t(x)') self.assertFalse(compile.is_recursive(rules)) rules = compile.parse('p(x) :- p(x)') self.assertTrue(compile.is_recursive(rules)) rules = compile.parse('p(x) :- q(x) q(x) :- r(x) r(x) :- p(x)') self.assertTrue(compile.is_recursive(rules)) rules = compile.parse('p(x) :- q(x) q(x) :- not p(x)') self.assertTrue(compile.is_recursive(rules)) rules = compile.parse('p(x) :- q(x), s(x) q(x) :- t(x) s(x) :- p(x)') self.assertTrue(compile.is_recursive(rules))
def check_unify_fail(self, atom_string1, atom_string2, msg): """Check that the bi-unification fails.""" self.open(msg) unifier1 = TopDownTheory.new_bi_unifier() unifier2 = TopDownTheory.new_bi_unifier() p1 = compile.parse(atom_string1)[0] p2 = compile.parse(atom_string2)[0] changes = unify.bi_unify_atoms(p1, unifier1, p2, unifier2) if changes is not None: LOG.debug("Failure failure: bi-unify(%s, %s) produced %s and %s", p1, p2, unifier1, unifier2) LOG.debug("plug(%s, %s) = %s", p1, unifier1, p1.plug(unifier1)) LOG.debug("plug(%s, %s) = %s", p2, unifier2, p2.plug(unifier2)) self.fail() self.close(msg)
def test_inject(self): theory = mock.MagicMock(spec=topdown.TopDownTheory) world = {'t': theory} theory.name = 't' theory.schema = ast.Schema() theory.schema.map['l'] = [mkc('Int'), mkc('Int')] theory.select.return_value = ast.parse('l(1,2). l(3,4). l(5,6)') context = z3theory.Z3Context() # An external theory world context.theories = world # inject the declaration of external relation without rules param_types = [ context.type_registry.get_type(typ) for typ in ['Int', 'Int', 'Bool'] ] relation = z3.Function('t:l', *param_types) context.context.register_relation(relation) context.relations['t:l'] = relation # the test context.inject('t', 'l') rules = context.context._rules # pylint: disable=E1101 self.assertIs(True, all(r.decl().name() == 't:l' for r in rules)) self.assertEqual([[1, 2], [3, 4], [5, 6]], sorted([[c.as_long() for c in r.children()] for r in rules]))
def datalog_equal(actual_code, correct_code, msg=None, equal=None, theories=None, output_diff=True): """Check equality. Check if the strings given by actual_code and CORRECT_CODE represent the same datalog. """ def minus(iter1, iter2, invert=False): extra = [] for i1 in iter1: found = False for i2 in iter2: # for asymmetric equality checks if invert: test_result = equal(i2, i1) else: test_result = equal(i1, i2) if test_result: found = True break if not found: extra.append(i1) return extra if equal is None: equal = lambda x, y: x == y LOG.debug("** Checking equality: %s **", msg) actual = compile.parse(actual_code, theories=theories) correct = compile.parse(correct_code, theories=theories) extra = minus(actual, correct) # in case EQUAL is asymmetric, always supply actual as the first arg # and set INVERT to true missing = minus(correct, actual, invert=True) if output_diff: output_diffs(extra, missing, msg) LOG.debug("** Finished equality: %s **", msg) is_equal = len(extra) == 0 and len(missing) == 0 if not is_equal: LOG.debug('datalog_equal failed, extras: %s, missing: %s', extra, missing) return is_equal
def setUp(self): self.world = {} t1 = MinTheory('t1', self.world) t2 = MinTheory('t2', self.world) self.world['t1'] = t1 self.world['t2'] = t2 self.rules = ast.parse( 'l(2). l(3). p(x) :- l(x). q(x,x) :- m(x). ' 'm("a"). k(x) :- t2:f(x). r(y) :- q(x,y). ' 's(x) :- l(y),builtin:plus(y, 2, x).') for rule in self.rules: t1.insert(rule) for rule in ast.parse("f(3)."): t2.insert(rule) self.t1 = t1 self.t2 = t2 super(TestTypeChecker, self).setUp()
def test_old_new_correctness(self): obj = self.MyObject() run = agnostic.Runtime() run.create_policy('test') run.insert('p(x) :- q(x)') run.insert('q(x) :- r(x), not s(x)') run.insert('r(1) r(2) r(3)') run.insert('s(2)') oldp = set(compile.parse('p(1) p(3)')) newp = set(compile.parse('p(1) p(2)')) run.register_trigger('p', lambda old, new: obj.equal(oldp, newp, old, new)) run.update([ compile.Event(compile.parse1('s(3)')), compile.Event(compile.parse1('s(2)'), insert=False) ]) self.assertEqual(obj.equals, True)
def test_modal_execute(self): # modal rule rule = compile.parse('execute[p(x)] :- q(x)') self.assertEqual(len(rule), 1) rule = rule[0] self.assertEqual(rule.head.table.modal, 'execute') # modal rule with namespace rule = compile.parse('execute[nova:disconnectNetwork(x)] :- q(x)') self.assertEqual(len(rule), 1) rule = rule[0] self.assertEqual(rule.head.table.modal, 'execute') # modal query rule = compile.parse('execute[p(x)]') self.assertEqual(len(rule), 1) rule = rule[0] self.assertEqual(rule.table.modal, 'execute')
def init_one_theory(self, prog): context = z3theory.Z3Context() world = {} t1 = z3theory.Z3Theory('t1', theories=world) world['t1'] = t1 context.register(t1) for rule in ast.parse(prog): t1.insert(rule) return (context, t1)
def test_eval(self, mock_get_answer): (context, t1) = self.init_one_theory('l(1,2). l(3,4).') expr = self.mk_z3_result(context) mock_get_answer.return_value = expr query = ast.parse('l(x,y)')[0] result = context.eval(t1, query) self.assertEqual(2, len(result[1])) self.assertEqual(2, len(result[2])) self.assertIs(True, all(len(row) == 2 for row in result[0]))
def init_one_builtin(self, body, typ, arity): context = z3theory.Z3Context() world = {} t1 = z3theory.Z3Theory('t1', theories=world) world['t1'] = t1 context.register(t1) rule = ast.parse('p(x) :- ' + body + '.')[0] t1.schema.map['p'] = [mkc(typ)] context.declare_tables() return (context, t1, rule, {0: [mkc(typ)] * arity})
def init_one_rule(self): context = z3theory.Z3Context() world = {} t1 = z3theory.Z3Theory('t1', theories=world) world['t1'] = t1 context.register(t1) rule = ast.parse('p(x) :- l(x,y), l(3,x).')[0] t1.schema.map['l'] = [mkc('Int'), mkc('Int')] t1.schema.map['p'] = [mkc('Int')] context.declare_tables() return (context, t1, rule)
def init_three_theories(self): context = z3theory.Z3Context() world = {} for name in ['t1', 't2', 't3']: world[name] = z3theory.Z3Theory(name, theories=world) t1, t2, t3 = world['t1'], world['t2'], world['t3'] context.register(t1) context.register(t2) # t3 is kept external # Declare rules for rule in ast.parse('p(x) :- t2:r(x), t3:s(x). q(x) :- p(x). p(4).'): t1.insert(rule) for rule in ast.parse('r(x) :- t1:p(x).'): t2.insert(rule) # typechecker t1.schema.map['p'] = [mkc('Int')] t1.schema.map['q'] = [mkc('Int')] t2.schema.map['r'] = [mkc('Int')] t3.schema.map['s'] = [mkc('Int')] t3.schema.map['t'] = [mkc('Int')] return context
def check(self, rule, data, correct, possibilities=None): rule = compile.parse1(rule, use_modules=False) data = compile.parse(data, use_modules=False) possibilities = possibilities or '' possibilities = compile.parse(possibilities, use_modules=False) possibilities = [compile.Rule(x, []) for x in possibilities] poss = {} for rule_lit in possibilities: if rule_lit.head.tablename() not in poss: poss[rule_lit.head.tablename()] = set([rule_lit]) else: poss[rule_lit.head.tablename()].add(rule_lit) th = MultiModuleNonrecursiveRuleTheory() th.debug_mode() for lit in data: th.insert(lit) result = th.instances(rule, poss) actual = " ".join(str(x) for x in result) e = helper.datalog_equal(actual, correct) self.assertTrue(e)
def check(self, rule, data, correct, possibilities=None): rule = compile.parse1(rule, use_modules=False) data = compile.parse(data, use_modules=False) possibilities = possibilities or '' possibilities = compile.parse(possibilities, use_modules=False) possibilities = [compile.Rule(x, []) for x in possibilities] poss = {} for rule_lit in possibilities: if rule_lit.head.tablename() not in poss: poss[rule_lit.head.tablename()] = set([rule_lit]) else: poss[rule_lit.head.tablename()].add(rule_lit) th = nonrecursive.MultiModuleNonrecursiveRuleTheory() th.debug_mode() for lit in data: th.insert(lit) result = th.instances(rule, poss) actual = " ".join(str(x) for x in result) e = helper.datalog_equal(actual, correct) self.assertTrue(e)
def test_event_rules(self): """Test modal operators.""" # a rule we use a few times pqrule = compile.parse1('p(x) :- q(x)') # rule-level modal (with insert) event = compile.parse('insert[p(x) :- q(x)]') self.assertEqual(len(event), 1) event = event[0] self.assertEqual(event.formula, pqrule) self.assertEqual(event.insert, True) self.assertIsNone(event.target) # rule-level modal with delete event = compile.parse('delete[p(x) :- q(x)]') self.assertEqual(len(event), 1) event = event[0] self.assertEqual(event.formula, pqrule) self.assertEqual(event.insert, False) self.assertIsNone(event.target) # embedded modals event = compile.parse('insert[execute[p(x)] :- q(x)]') self.assertEqual(len(event), 1) event = event[0] rule = compile.parse1('execute[p(x)] :- q(x)') self.assertEqual(event.formula, rule) self.assertEqual(event.insert, True) self.assertIsNone(event.target) # rule-level modal with policy name event = compile.parse('insert[p(x) :- q(x); "policy"]') self.assertEqual(len(event), 1) event = event[0] self.assertEqual(event.formula, pqrule) self.assertEqual(event.insert, True) self.assertEqual(event.target, "policy")
def init_two_theories(prog): context = z3theory.Z3Context() world = {} for name in ['t1', 't2']: world[name] = z3theory.Z3Theory(name, theories=world) t1, t2 = world['t1'], world['t2'] context.register(t1) # t2 is kept external # Declare rules for rule in ast.parse(prog): t1.insert(rule) # typechecker t2.schema.map['p'] = [mkc('Small')] t2.schema.map['q'] = [mkc('Str')] return context
def test_compile_facts(self): context = z3theory.Z3Context() world = {} t1 = z3theory.Z3Theory('t1', theories=world) world['t1'] = t1 context.register(t1) for rule in ast.parse('l(1,2). l(3,4). l(5,6).'): t1.insert(rule) t1.schema.map['l'] = [mkc('Int'), mkc('Int')] context.declare_tables() context.compile_facts(t1) rules = context.context.get_rules() self.assertEqual(3, len(rules)) self.assertIs(True, all(r.decl().name() == 't1:l' for r in rules)) self.assertEqual([[1, 2], [3, 4], [5, 6]], [[c.as_long() for c in r.children()] for r in rules])
def test_compile_facts(self): context = z3theory.Z3Context() world = {} t1 = z3theory.Z3Theory('t1', theories=world) world['t1'] = t1 context.register(t1) for rule in ast.parse('l(1,2). l(3,4). l(5,6).'): t1.insert(rule) t1.schema.map['l'] = [mkc('Int'), mkc('Int')] context.declare_tables() context.compile_facts(t1) rules = context.context.get_rules() self.assertEqual(3, len(rules)) self.assertIs(True, all(r.decl().name() == 't1:l' for r in rules)) self.assertEqual( [[1, 2], [3, 4], [5, 6]], [[c.as_long() for c in r.children()] for r in rules])
def test_inject(self): theory = mock.MagicMock(spec=topdown.TopDownTheory) world = {'t': theory} theory.name = 't' theory.schema = ast.Schema() theory.schema.map['l'] = [mkc('Int'), mkc('Int')] theory.select.return_value = ast.parse('l(1,2). l(3,4). l(5,6)') context = z3theory.Z3Context() # An external theory world context.theories = world # inject the declaration of external relation without rules param_types = [ context.type_registry.get_type(typ) for typ in ['Int', 'Int', 'Bool']] relation = z3.Function('t:l', *param_types) context.context.register_relation(relation) context.relations['t:l'] = relation # the test context.inject('t', 'l') rules = context.context._rules # pylint: disable=E1101 self.assertIs(True, all(r.decl().name() == 't:l' for r in rules)) self.assertEqual( [[1, 2], [3, 4], [5, 6]], sorted([[c.as_long() for c in r.children()] for r in rules]))
def str2pol(policy_string, theories=None): return compile.parse(policy_string, theories=theories)
def test_rule_stratification(self): rules = compile.parse('p(x) :- not q(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- p(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x) q(x) :- p(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x) q(x) :- not r(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- not q(x) q(x) :- not r(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- not q(x) ' 'q(x) :- not r(x) ' 'r(x) :- not s(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x), r(x) ' 'q(x) :- not t(x) ' 'r(x) :- not s(x)') self.assertTrue(compile.is_stratified(rules)) rules = compile.parse('p(x) :- not p(x)') self.assertFalse(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x) q(x) :- not p(x)') self.assertFalse(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x),r(x) r(x) :- not p(x)') self.assertFalse(compile.is_stratified(rules)) rules = compile.parse('p(x) :- q(x), r(x) ' 'q(x) :- not t(x) ' 'r(x) :- not s(x) ' 't(x) :- p(x)') self.assertFalse(compile.is_stratified(rules))
def parse(self, policy): return compile.parse(policy, use_modules=False)
def str2pol(policy_string): return compile.parse(policy_string)