def test_nested_matches(qeval, qvar, externals): assert qeval( "new MyClass(x: new MyClass(x: 1, y: 2), y: 2) matches MyClass{x: MyClass{x: 1}}" ) assert not qeval( "new MyClass(x: new MyClass(x: 1), y: 2) matches MyClass{x: MyClass{y: 2}}" )
def test_argument_patterns(tell, qeval, qvar, externals): tell("matchesFoo(name, foo: Foo) if name = foo.name") assert qeval('matchesFoo(sam, new Foo(name: "sam"))') assert qeval('matchesFoo(sam, new Bar(name: "sam"))') assert not qeval('matchesFoo("alex", new Foo(name: "sam"))') assert not qeval('matchesFoo("alex", new Bar(name: "sam"))') assert not qeval('matchesFoo("alex", new Qux())')
def test_rule_ordering(tell, qeval, externals): tell("f(_: Foo{}) if cut and 1 = 2;") tell('f(_: Foo{name: "test"});') assert qeval('f(new Foo( name: "test" )) ') assert qeval('x = new Foo( name: "test" ) and f(x) ') assert not qeval('f(new Foo( name: "nope" )) ') assert not qeval('x = new Foo( name: "nope" ) and f(x) ')
def test_groups(load_file, qeval, query): # Contains test queries. load_file(Path(__file__).parent / "policies/groups.pol") # Check that we can't instantiate groups. with pytest.raises(PolarRuntimeException): qeval("G{}") # Test rule ordering with groups. results = query("check_order(A{}, action)") expected = ["A", "G", "H"] assert expected == [result["action"] for result in results]
def test_comparisons(tell, qeval, qvar, query): assert qeval("3 == 3") assert qeval("3 != 2") assert qeval("2 <= 2") assert qeval("2 <= 3") assert qeval("2 < 3") assert qeval("3 >= 3") assert qeval("3 >= 2") assert qeval("3 > 2") assert qeval("x = 1 and x == 1")
def test_iterators(polar, qeval, qvar): class Foo: pass polar.register_class(Foo) with pytest.raises(exceptions.InvalidIteratorError) as e: qeval("x in new Foo()") class Bar(list): def sum(self): return sum(x for x in self) polar.register_class(Bar) assert qvar("x in new Bar([1, 2, 3])", "x") == [1, 2, 3] assert qvar("x = new Bar([1, 2, 3]).sum()", "x", one=True) == 6
def test_numbers_from_external_call(polar, qeval, qvar, query): class Numberer: def give_me_an_int(self): yield 1 def give_me_a_float(self): yield 1.234 polar.register_class(Numberer) result = qvar("new Numberer().give_me_an_int() = var", "var", one=True) assert result == 1 assert qeval("new Numberer().give_me_an_int() = 1") result = qvar("new Numberer().give_me_a_float() = var", "var", one=True) assert result == 1.234 assert qeval("new Numberer().give_me_a_float() = 1.234")
def test_inf_nan(polar, qeval, query): polar.register_constant("inf", inf) polar.register_constant("neg_inf", -inf) polar.register_constant("nan", nan) assert isnan(query("x = nan")[0]["x"]) assert not query("nan = nan") assert query("x = inf")[0]["x"] == inf assert qeval("inf = inf") assert query("x = neg_inf")[0]["x"] == -inf assert qeval("neg_inf = neg_inf") assert not query("inf = neg_inf") assert not query("inf < neg_inf") assert qeval("neg_inf < inf")
def test_bool_from_external_call(polar, qeval, qvar, query): class Booler: def whats_up(self): yield True polar.register_class(Booler) result = qvar("new Booler().whats_up() = var", "var", one=True) assert qeval("new Booler().whats_up()")
def test_external(polar, qvar, qeval): class Bar: def y(self): return "y" class Foo: def __init__(self, a="a"): self.a = a def b(self): yield "b" @classmethod def c(cls): assert issubclass(cls, Foo) return "c" def d(self, x): return x def bar(self): return Bar() def e(self): return [1, 2, 3] def f(self): yield [1, 2, 3] yield [4, 5, 6] yield 7 def g(self): return {"hello": "world"} def h(self): return True def capital_foo(): return Foo(a="A") polar.register_class(Foo, from_polar=capital_foo) assert qvar("new Foo().a = x", "x", one=True) == "A" with pytest.raises(RuntimeError, match="tried to call 'a' but it is not callable"): assert not qeval("new Foo().a() = x") assert not qvar("new Foo().b = x", "x", one=True) == "b" assert qvar("new Foo().b() = x", "x", one=True) == "b" assert not qvar("Foo.c = x", "x", one=True) == "c" assert qvar("Foo.c() = x", "x", one=True) == "c" assert qvar("new Foo() = f and f.a = x", "x", one=True) == "A" assert qvar("new Foo().bar().y() = x", "x", one=True) == "y" assert qvar("new Foo().e() = x", "x", one=True) == [1, 2, 3] assert qvar("new Foo().f() = x", "x") == [[1, 2, 3], [4, 5, 6], 7] assert qvar("new Foo().g().hello = x", "x", one=True) == "world" assert qvar("new Foo().h() = x", "x", one=True) is True
def test_class_definitions(tell, qeval, load_file): # Contains test queries. load_file(Path(__file__).parent / "policies/classes.pol") # Test instantiation errors. with pytest.raises(PolarRuntimeException): qeval("NotADefinedClassName{foo: 1}") with pytest.raises(PolarRuntimeException): qeval("Three{foo: One{}}") with pytest.raises(PolarRuntimeException): qeval("Three{unit: Two{}}") with pytest.raises(PolarRuntimeException): qeval("Three{unit: One{}, pair: One{}}")
def test_recursive_rule(tell, qeval, qvar): tell('derive("apple", "orange")') tell('derive("orange", "avacado")') tell('derive("avacado", "juniper_berry")') results = qvar('derive("apple", x)', "x") assert results == ["orange"] tell("derives(a, b) if derive(a, b);") tell("derives(a, b) if derive(a, z) and derives(z, b);") assert qeval('derives("apple", "juniper_berry")') results = qvar('derives("apple", x)', "x") assert results == ["orange", "avacado", "juniper_berry"]
def test_disjunctive_rule(tell, qeval): tell("or_eq(a, b) if 1 = 0 or a = b;") assert qeval("or_eq(1, 1)") tell("and_or_eq(a, b, c) if (a = b and b = c) or 1 = 0") assert not qeval("and_or_eq(1, 1, 0)") assert qeval("and_or_eq(1, 1, 1)") assert qeval("1=0 or (1=1 and 1=1)") assert not qeval("1=0 or (1=0 and 1=1)") # not sure if these test anything but :) assert qeval("1=0 or (1=0 or 1=1)") assert not qeval("1=0 or (1=0 or 1=0)") assert qeval("1=1 and (1=0 or 1=1)") assert not qeval("1=0 and (1=0 or 1=1)")
def test_keys_are_confusing(tell, qeval, qvar, externals): assert qeval("new MyClass(x: 1, y: 2) = new MyClass(y: 2, x: 1)") assert qeval("new MyClass(x: 1, y: 2) = new MyClass(x: 1, y: 2)") assert not qeval("new MyClass(x: 1, y: 2) = new MyClass(x: 2, y: 1)") assert not qeval("new MyClass(x: 1, y: 2) = new MyClass(y: 1, x: 2)") assert not qeval("new MyClass(x: 1) = new MyClass(x: 1, y: 2)") assert not qeval("new MyClass(x: 1, y: 2) = new MyClass(y: 2)")
def test_defining_things(tell, qeval): facts = [ 'apple("orange")', 'thing("with", "two")', 'thing("with", "a", "lot", "of", "arguments", 1, 2, 3, 4, 5)', 'thing(with("nested"), "stuff")', "dream(within(a(dream(within(a(dream(within(a(_dream)))))))))", 'embedded("strings")', ] for f in facts: tell(f) for f in facts: assert qeval(f)
def test_field_unification(qeval): # test dictionary field unification assert qeval("{} = {}") assert qeval("{x: 1} = {x: 1}") assert not qeval("{x: 1} = {x: 2}") assert not qeval("{x: 1} = {y: 1}") assert not qeval("{x: 1, y: 2} = {y: 1, x: 2}") assert qeval("{x: 1, y: 2} = {y: 2, x: 1}")
def test_unify(polar, qeval): class Foo: def __init__(self, foo): self.foo = foo def __eq__(self, other): if isinstance(other, Foo): return self.foo == other.foo return False polar.register_class(Foo) polar.load_str("foo() if new Foo(foo: 1) = new Foo(foo: 1);") assert qeval("foo()")
def test_dictionaries(tell, qeval, qvar): # basic dictionary lookup tell('dict({hello: "world", foo: "bar"})') assert qeval('dict(d) and d.hello = "world"') ### dictionary lookups with variable fields ### tell("attr(d, k, d.(k))") # k = "hello", {hello: "steve"}.(k) = "steve" assert qeval('attr({hello: "steve"}, "hello", "steve")') # k = "hello", {hello: "steve"}.(k) = value, value = "steve" assert qvar('attr({hello: "steve"}, "hello", value)', "value", one=True) == "steve" # k = key, {hello: "steve"}.(k) = "steve", key = "hello" assert qvar('attr({hello: "steve"}, key, "steve")', "key", one=True) == "hello" ### nested lookups ### assert qeval( 'attr({hello: {this: {is: "nested"}}}, "hello", {this: {is: "nested"}})' ) tell("deepget(d, d.hello.this.is)") assert qeval('deepget({hello: {this: {is: "nested"}}}, "nested")') tell("myget(d, d.get.inner)") assert qeval('myget({get: {inner: "nested"}}, "nested")') tell("x({a: {b:{c:123}}})") tell("x({a: {y:{c:456}}})") assert qvar("x(d) and d.a.(k).c = value", "value") == [123, 456] tell("lookup(dict, result) if result = dict.a.b.c;") assert qeval('lookup({a: {b: {c: "nested"}}}, "nested")') ### more basic lookup tests ### tell('user({name: "steve", job: "programmer", state: "NY"})') tell('user({name: "alex", job: "programmer", state: "CO"})') tell('user({name: "graham", job: "business", state: "NY"})') assert qeval('user(d) and d.name = "steve"') assert qvar('user({job: "programmer", name: name, state: state})', "name") == [ "steve", "alex", ]
def test_load_file(load_file, tell, qeval, qvar): load_file(Path(__file__).parent / "policies/test.polar") assert qeval('test("true")') tell('b("foo")') assert qvar("a(x)", "x", one=True) == "foo"
def test_matches(qeval, qvar, externals): assert qeval("{} matches {}") assert qeval("{x: 1} matches {}") assert qeval("{x: 1} matches {x: 1}") assert qeval("{x: 1, y: 2} matches {x: 1}") assert qeval("{x: 1, y: 2} matches {x: 1, y: 2}") assert qeval("{a: {x: 1, y: 2}} matches {a: {y: 2}}") assert not qeval("{a: {x: 1, y: 2}} matches {b: {y: 2}}") assert not qeval("{x: 1} matches {x: 1, y: 2}") assert not qeval("{y: 2} matches {x: 1, y: 2}") assert not qeval("{} matches {x: 1, y: 2}") assert not qeval("{} matches {x: 1}") assert qeval("new MyClass(x: 1, y: 2) matches {}") assert qeval("new MyClass(x: 1, y: 2) matches {x: 1, y: 2}") assert not qeval("{x: 1, y: 2} matches MyClass{x: 1, y: 2}") assert qeval("new MyClass(x: 1, y: 2) matches MyClass{x: 1}") assert not qeval("new MyClass(y: 2) matches MyClass{x: 1, y: 2}") assert qeval("new OurClass(x: 1, y: 2) matches YourClass") assert qeval("new OurClass(x: 1, y: 2) matches MyClass{x: 1}") assert qeval("new OurClass(x: 1, y: 2) matches MyClass{x: 1, y: 2}") assert not qeval("new MyClass(x: 1, y: 2) matches OurClass{x: 1}") assert not qeval("new MyClass(x: 1, y: 2) matches YourClass") assert not qeval("new MyClass(x: 1, y: 2) matches YourClass{}")
def test_host_methods(qeval): assert qeval('x = "abc" and x.startswith("a") = true and x.find("bc") = 1') assert qeval("i = 4095 and i.bit_length() = 12") assert qeval('f = 3.14159 and f.hex() = "0x1.921f9f01b866ep+1"') assert qeval("l = [1, 2, 3] and l.index(3) = 2 and l.copy() = [1, 2, 3]") assert qeval('d = {a: 1} and d.get("a") = 1 and d.get("b", 2) = 2')
def test_in(polar, qeval): polar.load_str("g(x, y) if not x in y;") polar.load_str("f(x) if not (x=1 or x=2);") assert not qeval("f(1)") assert qeval("g(4, [1,2,3])") assert not qeval("g(1, [1,1,1])")
def test_booleans(qeval): assert qeval("true = true") assert qeval("false = false") assert not qeval("true = false")
def test_missing_rule(tell, qeval): tell("a(x) if b(x) and c(x);") tell('b("apple")') tell('c("apple")') assert not qeval('d("apple")')
def test_negation(tell, qeval): tell('b("apple")') assert qeval('b("apple")') assert not qeval('not (b("apple"))') assert qeval('not (b("notanapple"))')
def test_define_rule(tell, qeval): tell("a(x) if b(x) and c(x);") tell('b("apple")') tell('c("apple")') assert qeval('a("apple")')
def test_field_unification_external(qeval, externals): # test instance field unification assert qeval("new MyClass(x: 1, y: 2) = new MyClass(y: 2, x: 1)") assert not qeval("new MyClass(x: 1, y: 2) = {y: 2, x: 1}") assert qeval("new MyClass(x: 1, y: 2) = new OurClass(y: 2, x: 1)")
def test_external_classes(tell, qeval, qvar, externals): assert qeval("new Bar() matches Foo") assert not qeval("new Qux() matches Foo") assert qeval('new Foo().foo() = "Foo!"') assert qeval('new Bar().foo() = "Bar!"')
def test_arities(tell, qeval): tell("f(1);") tell("f(x, y);") assert qeval("f(1)") assert not qeval("f(2)") assert qeval("f(2, 3)")
def test_unify_class_fields(tell, qeval, qvar, externals): tell("check(name, new Foo(name: name))") assert qeval('check("sam", new Foo(name: "sam"))') assert not qeval('check("alex", new Foo(name: "sam"))')