aboutsummaryrefslogtreecommitdiffstats
path: root/test/query
diff options
context:
space:
mode:
Diffstat (limited to 'test/query')
-rw-r--r--test/query/__init__.py0
-rw-r--r--test/query/ast_test/__init__.py0
-rw-r--r--test/query/ast_test/test_fetch.py234
-rw-r--r--test/query/ast_test/test_filter_.py614
-rw-r--r--test/query/test_matcher.py1177
-rw-r--r--test/query/test_validator.py505
6 files changed, 2530 insertions, 0 deletions
diff --git a/test/query/__init__.py b/test/query/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/query/__init__.py
diff --git a/test/query/ast_test/__init__.py b/test/query/ast_test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/query/ast_test/__init__.py
diff --git a/test/query/ast_test/test_fetch.py b/test/query/ast_test/test_fetch.py
new file mode 100644
index 0000000..ccb680e
--- /dev/null
+++ b/test/query/ast_test/test_fetch.py
@@ -0,0 +1,234 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.query.ast.fetch import FetchExpression
+from bsfs.query.ast.fetch import All, This
+from bsfs.query.ast.fetch import _Branch, Fetch
+from bsfs.query.ast.fetch import _Named, Node, Value
+
+
+## code ##
+
+class TestExpression(unittest.TestCase): # FetchExpression
+ def test_essentials(self):
+ class Foo(FetchExpression): pass
+ # comparison
+ self.assertEqual(FetchExpression(), FetchExpression())
+ self.assertEqual(hash(FetchExpression()), hash(FetchExpression()))
+ # comparison respects type
+ self.assertNotEqual(FetchExpression(), Foo())
+ self.assertNotEqual(hash(FetchExpression()), hash(Foo()))
+ # string conversion
+ self.assertEqual(str(FetchExpression()), 'FetchExpression()')
+ self.assertEqual(repr(FetchExpression()), 'FetchExpression()')
+ self.assertEqual(str(Foo()), 'Foo()')
+ self.assertEqual(repr(Foo()), 'Foo()')
+
+
+class TestAll(unittest.TestCase): # All
+ def test_essentials(self):
+ class Foo(All): pass
+ expr0 = This('hello')
+ expr1 = This('world')
+ # comparison
+ self.assertEqual(All(expr0), All(expr0))
+ self.assertEqual(hash(All(expr0)), hash(All(expr0)))
+ # comparison respects type
+ self.assertNotEqual(All(expr0), Foo(expr0))
+ self.assertNotEqual(hash(All(expr0)), hash(Foo(expr0)))
+ # comparison respects expressions
+ self.assertEqual(All(expr0, expr1), All(expr0, expr1))
+ self.assertEqual(hash(All(expr0, expr1)), hash(All(expr0, expr1)))
+ self.assertNotEqual(All(expr0), All(expr1))
+ self.assertNotEqual(hash(All(expr0)), hash(All(expr1)))
+ # expressions are unordered
+ self.assertEqual(All(expr0, expr1), All(expr1, expr0))
+ self.assertEqual(hash(All(expr0, expr1)), hash(All(expr1, expr0)))
+ # string conversion
+ self.assertIn(str(All(expr0, expr1)), {
+ 'All({This(world), This(hello)})',
+ 'All({This(hello), This(world)})'})
+ self.assertIn(repr(All(expr0, expr1)), {
+ 'All({This(world), This(hello)})',
+ 'All({This(hello), This(world)})'})
+
+ def test_members(self):
+ class Foo(): pass
+ expr0 = This('hello')
+ expr1 = This('world')
+ # requires at least one child expression
+ self.assertRaises(AttributeError, All)
+ # expr returns child expressions
+ self.assertEqual(All(expr0, expr1).expr, {expr0, expr1})
+ # can pass expressions as arguments
+ self.assertEqual(All(expr0, expr1).expr, {expr0, expr1})
+ # can pass a single expression as argument
+ self.assertEqual(All(expr0).expr, {expr0})
+ # can pass expressions as list-like
+ self.assertEqual(All([expr0, expr1]).expr, {expr0, expr1})
+ self.assertEqual(All((expr0, expr1)).expr, {expr0, expr1})
+ self.assertEqual(All({expr0, expr1}).expr, {expr0, expr1})
+ # can pass a single expression as list-like
+ self.assertEqual(All([expr0]).expr, {expr0})
+ # must pass a FilterExpression
+ self.assertRaises(TypeError, All, Foo())
+ self.assertRaises(TypeError, All, 1234)
+ self.assertRaises(TypeError, All, 'hello world')
+ # len returns the number of child expressions
+ self.assertEqual(len(All(expr0)), 1)
+ self.assertEqual(len(All(expr0, expr1)), 2)
+ # iter iterates over child expressions
+ self.assertSetEqual(set(All(expr0, expr1)), {expr0, expr1})
+
+
+class TestThis(unittest.TestCase): # This
+ def test_essentials(self):
+ class Foo(This): pass
+ # comparison
+ self.assertEqual(This('hello'), This('hello'))
+ self.assertEqual(hash(This('hello')), hash(This('hello')))
+ # comparison respects type
+ self.assertNotEqual(This('hello'), Foo('hello'))
+ self.assertNotEqual(hash(This('hello')), hash(Foo('hello')))
+ # comparison respects name
+ self.assertNotEqual(This('hello'), This('world'))
+ self.assertNotEqual(hash(This('hello')), hash(This('world')))
+ # string conversion
+ self.assertEqual(str(This('hello')), 'This(hello)')
+ self.assertEqual(repr(This('hello')), 'This(hello)')
+
+ def test_members(self):
+ class Foo(): pass
+ # name returns member
+ self.assertEqual(This('hello').name, 'hello')
+ self.assertEqual(This('world').name, 'world')
+ # name is converted to a string
+ self.assertEqual(This(1234).name, '1234')
+ foo = Foo()
+ self.assertEqual(This(foo).name, str(foo))
+
+
+class TestBranch(unittest.TestCase): # _Branch, Fetch
+ def test_essentials(self):
+ pred = ns.bse.tag
+ expr = FetchExpression()
+ # comparison
+ self.assertEqual(_Branch(pred), _Branch(pred))
+ self.assertEqual(hash(_Branch(pred)), hash(_Branch(pred)))
+ self.assertEqual(Fetch(pred, expr), Fetch(pred, expr))
+ self.assertEqual(hash(Fetch(pred, expr)), hash(Fetch(pred, expr)))
+ # comparison respects type
+ self.assertNotEqual(_Branch(pred), Fetch(pred, expr))
+ self.assertNotEqual(hash(_Branch(pred)), hash(Fetch(pred, expr)))
+ self.assertNotEqual(Fetch(pred, expr), _Branch(pred))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(_Branch(pred)))
+ # comparison respects predicate
+ self.assertNotEqual(_Branch(pred), _Branch(ns.bse.filesize))
+ self.assertNotEqual(hash(_Branch(pred)), hash(_Branch(ns.bse.filesize)))
+ self.assertNotEqual(Fetch(pred, expr), Fetch(ns.bse.filesize, expr))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(Fetch(ns.bse.filesize, expr)))
+ # comparison respects expression
+ self.assertNotEqual(Fetch(pred, expr), Fetch(pred, This('foo')))
+ self.assertNotEqual(hash(Fetch(pred, expr)), hash(Fetch(pred, This('foo'))))
+ # string conversion
+ self.assertEqual(str(_Branch(pred)), f'_Branch({pred})')
+ self.assertEqual(repr(_Branch(pred)), f'_Branch({pred})')
+ self.assertEqual(str(Fetch(pred, expr)), f'Fetch({pred}, {expr})')
+ self.assertEqual(repr(Fetch(pred, expr)), f'Fetch({pred}, {expr})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = ns.bse.tag
+ expr = FetchExpression()
+
+ # predicate returns member
+ self.assertEqual(_Branch(pred).predicate, pred)
+ self.assertEqual(Fetch(pred, expr).predicate, pred)
+ # can pass an URI
+ self.assertEqual(_Branch(ns.bse.filename).predicate, ns.bse.filename)
+ self.assertEqual(Fetch(ns.bse.filename, expr).predicate, ns.bse.filename)
+ # must pass an URI
+ self.assertRaises(TypeError, _Branch, Foo())
+ self.assertRaises(TypeError, Fetch, Foo(), expr)
+ # expression returns member
+ self.assertEqual(Fetch(pred, expr).expr, expr)
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, 'hello')
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, 1234)
+ self.assertRaises(TypeError, Fetch, ns.bse.filename, Foo())
+
+
+class TestNamed(unittest.TestCase): # _Named, Node, Value
+ def test_essentials(self):
+ pred = ns.bse.tag
+ name = 'foobar'
+ # comparison
+ self.assertEqual(_Named(pred, name), _Named(pred, name))
+ self.assertEqual(hash(_Named(pred, name)), hash(_Named(pred, name)))
+ # comparison respects type
+ self.assertNotEqual(_Named(pred, name), Node(pred, name))
+ self.assertNotEqual(Node(pred, name), Value(pred, name))
+ self.assertNotEqual(Value(pred, name), _Named(pred, name))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(Node(pred, name)))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Value(pred, name)))
+ self.assertNotEqual(hash(Value(pred, name)), hash(_Named(pred, name)))
+ # comparison respects predicate
+ self.assertNotEqual(_Named(pred, name), _Named(ns.bse.filesize, name))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(_Named(ns.bse.filesize, name)))
+ self.assertNotEqual(Node(pred, name), Node(ns.bse.filesize, name))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Node(ns.bse.filesize, name)))
+ self.assertNotEqual(Value(pred, name), Value(ns.bse.filesize, name))
+ self.assertNotEqual(hash(Value(pred, name)), hash(Value(ns.bse.filesize, name)))
+ # comparison respects name
+ self.assertNotEqual(_Named(pred, name), _Named(pred, 'foo'))
+ self.assertNotEqual(hash(_Named(pred, name)), hash(_Named(pred, 'foo')))
+ self.assertNotEqual(Node(pred, name), Node(pred, 'foo'))
+ self.assertNotEqual(hash(Node(pred, name)), hash(Node(pred, 'foo')))
+ self.assertNotEqual(Value(pred, name), Value(pred, 'foo'))
+ self.assertNotEqual(hash(Value(pred, name)), hash(Value(pred, 'foo')))
+ # string conversion
+ self.assertEqual(str(_Named(pred, name)), f'_Named({pred}, {name})')
+ self.assertEqual(repr(_Named(pred, name)), f'_Named({pred}, {name})')
+ self.assertEqual(str(Node(pred, name)), f'Node({pred}, {name})')
+ self.assertEqual(repr(Node(pred, name)), f'Node({pred}, {name})')
+ self.assertEqual(str(Value(pred, name)), f'Value({pred}, {name})')
+ self.assertEqual(repr(Value(pred, name)), f'Value({pred}, {name})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = ns.bse.tag
+ name = 'foobar'
+ # predicate returns member
+ self.assertEqual(_Named(pred, name).predicate, pred)
+ self.assertEqual(Node(pred, name).predicate, pred)
+ self.assertEqual(Value(pred, name).predicate, pred)
+ # can pass an URI as predicate
+ self.assertEqual(_Named(ns.bse.filename, name).predicate, ns.bse.filename)
+ self.assertEqual(Node(ns.bse.filename, name).predicate, ns.bse.filename)
+ self.assertEqual(Value(ns.bse.filename, name).predicate, ns.bse.filename)
+ # must pass an URI
+ self.assertRaises(TypeError, _Named, Foo(), name)
+ self.assertRaises(TypeError, Node, Foo(), name)
+ self.assertRaises(TypeError, Value, Foo(), name)
+ # name returns member
+ self.assertEqual(_Named(pred, name).name, name)
+ self.assertEqual(Node(pred, name).name, name)
+ self.assertEqual(Value(pred, name).name, name)
+ # name is converted to a string
+ self.assertEqual(_Named(pred, 1234).name, '1234')
+ self.assertEqual(Node(pred, 1234).name, '1234')
+ self.assertEqual(Value(pred, 1234).name, '1234')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/ast_test/test_filter_.py b/test/query/ast_test/test_filter_.py
new file mode 100644
index 0000000..d0d42ea
--- /dev/null
+++ b/test/query/ast_test/test_filter_.py
@@ -0,0 +1,614 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.query.ast.filter_ import _Expression, FilterExpression, PredicateExpression
+from bsfs.query.ast.filter_ import _Branch, Any, All
+from bsfs.query.ast.filter_ import _Agg, And, Or
+from bsfs.query.ast.filter_ import Not, Has, Distance
+from bsfs.query.ast.filter_ import _Value, Is, Equals, Substring, StartsWith, EndsWith
+from bsfs.query.ast.filter_ import _Bounded, LessThan, GreaterThan
+from bsfs.query.ast.filter_ import Predicate, OneOf
+from bsfs.query.ast.filter_ import IsIn, IsNotIn
+from bsfs.query.ast.filter_ import Includes, Excludes, Between
+
+
+## code ##
+
+class TestExpression(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ self.assertEqual(_Expression(), _Expression())
+ self.assertEqual(FilterExpression(), FilterExpression())
+ self.assertEqual(PredicateExpression(), PredicateExpression())
+ self.assertEqual(hash(_Expression()), hash(_Expression()))
+ self.assertEqual(hash(FilterExpression()), hash(FilterExpression()))
+ self.assertEqual(hash(PredicateExpression()), hash(PredicateExpression()))
+ # comparison respects type
+ self.assertNotEqual(FilterExpression(), _Expression())
+ self.assertNotEqual(_Expression(), PredicateExpression())
+ self.assertNotEqual(PredicateExpression(), FilterExpression())
+ self.assertNotEqual(hash(FilterExpression()), hash(_Expression()))
+ self.assertNotEqual(hash(_Expression()), hash(PredicateExpression()))
+ self.assertNotEqual(hash(PredicateExpression()), hash(FilterExpression()))
+ # string conversion
+ self.assertEqual(str(_Expression()), '_Expression()')
+ self.assertEqual(str(FilterExpression()), 'FilterExpression()')
+ self.assertEqual(str(PredicateExpression()), 'PredicateExpression()')
+ self.assertEqual(repr(_Expression()), '_Expression()')
+ self.assertEqual(repr(FilterExpression()), 'FilterExpression()')
+ self.assertEqual(repr(PredicateExpression()), 'PredicateExpression()')
+
+
+class TestBranch(unittest.TestCase): # _Branch, Any, All
+ def test_essentials(self):
+ pred = PredicateExpression()
+ expr = FilterExpression()
+
+ # comparison respects type
+ self.assertNotEqual(_Branch(pred, expr), Any(pred, expr))
+ self.assertNotEqual(Any(pred, expr), All(pred, expr))
+ self.assertNotEqual(All(pred, expr), _Branch(pred, expr))
+ self.assertNotEqual(hash(_Branch(pred, expr)), hash(Any(pred, expr)))
+ self.assertNotEqual(hash(Any(pred, expr)), hash(All(pred, expr)))
+ self.assertNotEqual(hash(All(pred, expr)), hash(_Branch(pred, expr)))
+
+ for cls in (_Branch, Any, All):
+ # comparison
+ self.assertEqual(cls(pred, expr), cls(pred, expr))
+ self.assertEqual(hash(cls(pred, expr)), hash(cls(pred, expr)))
+ # comparison respects predicate
+ self.assertNotEqual(cls(ns.bse.filename, expr), cls(ns.bse.filesize, expr))
+ self.assertNotEqual(hash(cls(ns.bse.filename, expr)), hash(cls(ns.bse.filesize, expr)))
+ # comparison respects expression
+ self.assertNotEqual(cls(pred, Equals('hello')), cls(pred, Equals('world')))
+ self.assertNotEqual(hash(cls(pred, Equals('hello'))), hash(cls(pred, Equals('world'))))
+
+ # string conversion
+ self.assertEqual(str(_Branch(pred, expr)), f'_Branch({pred}, {expr})')
+ self.assertEqual(repr(_Branch(pred, expr)), f'_Branch({pred}, {expr})')
+ self.assertEqual(str(Any(pred, expr)), f'Any({pred}, {expr})')
+ self.assertEqual(repr(Any(pred, expr)), f'Any({pred}, {expr})')
+ self.assertEqual(str(All(pred, expr)), f'All({pred}, {expr})')
+ self.assertEqual(repr(All(pred, expr)), f'All({pred}, {expr})')
+
+ def test_members(self):
+ class Foo(): pass
+ pred = PredicateExpression()
+ expr = FilterExpression()
+
+ for cls in (_Branch, Any, All):
+ # predicate returns member
+ self.assertEqual(cls(PredicateExpression(), expr).predicate, PredicateExpression())
+ # can pass an URI
+ self.assertEqual(cls(ns.bse.filename, expr).predicate, Predicate(ns.bse.filename))
+ # can pass a PredicateExpression
+ self.assertEqual(cls(Predicate(ns.bse.filename), expr).predicate, Predicate(ns.bse.filename))
+ # must pass an URI or PredicateExpression
+ self.assertRaises(TypeError, cls, Foo(), expr)
+ # expression returns member
+ self.assertEqual(cls(pred, Equals('hello')).expr, Equals('hello'))
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, cls, ns.bse.filename, 'hello')
+ self.assertRaises(TypeError, cls, ns.bse.filename, 1234)
+ self.assertRaises(TypeError, cls, ns.bse.filename, Foo())
+
+
+class TestAgg(unittest.TestCase): # _Agg, And, Or
+ def test_essentials(self):
+ expr = {Equals('hello'), Equals('world')}
+
+ # comparison respects type
+ self.assertNotEqual(_Agg(expr), And(expr))
+ self.assertNotEqual(And(expr), Or(expr))
+ self.assertNotEqual(Or(expr), _Agg(expr))
+ self.assertNotEqual(hash(_Agg(expr)), hash(And(expr)))
+ self.assertNotEqual(hash(And(expr)), hash(Or(expr)))
+ self.assertNotEqual(hash(Or(expr)), hash(_Agg(expr)))
+
+ for cls in (_Agg, And, Or):
+ # comparison
+ self.assertEqual(cls(expr), cls(expr))
+ self.assertEqual(hash(cls(expr)), hash(cls(expr)))
+ # comparison respects expression
+ self.assertNotEqual(cls(expr), cls(Equals('world')))
+ self.assertNotEqual(hash(cls(expr)), hash(cls(Equals('world'))))
+ self.assertNotEqual(cls(Equals('hello')), cls(Equals('world')))
+ self.assertNotEqual(hash(cls(Equals('hello'))), hash(cls(Equals('world'))))
+
+ # string conversion
+ self.assertEqual(str(_Agg(Equals('hello'))), '_Agg({Equals(hello)})')
+ self.assertEqual(repr(_Agg(Equals('hello'))), '_Agg({Equals(hello)})')
+ self.assertEqual(str(And(Equals('hello'))), 'And({Equals(hello)})')
+ self.assertEqual(repr(And(Equals('hello'))), 'And({Equals(hello)})')
+ self.assertEqual(str(Or(Equals('hello'))), 'Or({Equals(hello)})')
+ self.assertEqual(repr(Or(Equals('hello'))), 'Or({Equals(hello)})')
+
+ def test_expression(self):
+ class Foo(): pass
+
+ for cls in (_Agg, And, Or):
+ # can pass expressions as arguments
+ self.assertSetEqual(cls(Equals('hello'), Equals('world')).expr, {Equals('hello'), Equals('world')})
+ # can pass one expressions as argument
+ self.assertSetEqual(cls(Equals('hello')).expr, {Equals('hello')})
+ # can pass expressions as iterator
+ self.assertSetEqual(cls(iter((Equals('hello'), Equals('world')))).expr, {Equals('hello'), Equals('world')})
+ # can pass expressions as generator
+ def gen():
+ yield Equals('hello')
+ yield Equals('world')
+ self.assertSetEqual(cls(gen()).expr, {Equals('hello'), Equals('world')})
+ # can pass expressions as list-like
+ self.assertSetEqual(cls((Equals('hello'), Equals('world'))).expr, {Equals('hello'), Equals('world')})
+ # can pass one expression as list-like
+ self.assertSetEqual(cls([Equals('hello')]).expr, {Equals('hello')})
+ # must pass expressions
+ self.assertRaises(TypeError, cls, Foo(), Foo())
+ self.assertRaises(TypeError, cls, [Foo(), Foo()])
+
+ # iter
+ self.assertSetEqual(set(iter(cls(Equals('hello'), Equals('world')))), {Equals('hello'), Equals('world')})
+ # contains
+ self.assertIn(Equals('world'), cls(Equals('hello'), Equals('world')))
+ self.assertNotIn(Equals('foo'), cls(Equals('hello'), Equals('world')))
+ # len
+ self.assertEqual(len(cls(Equals('hello'), Equals('world'))), 2)
+ self.assertEqual(len(cls(Equals('hello'), Equals('world'), Equals('foo'))), 3)
+
+
+
+class TestNot(unittest.TestCase):
+ def test_essentials(self):
+ expr = FilterExpression()
+ # comparison
+ self.assertEqual(Not(expr), Not(expr))
+ self.assertEqual(hash(Not(expr)), hash(Not(expr)))
+ # comparison respects type
+ self.assertNotEqual(Not(expr), FilterExpression())
+ self.assertNotEqual(hash(Not(expr)), hash(FilterExpression()))
+ # comparison respects expression
+ self.assertNotEqual(Not(Equals('hello')), Not(Equals('world')))
+ self.assertNotEqual(hash(Not(Equals('hello'))), hash(Not(Equals('world'))))
+ # string conversion
+ self.assertEqual(str(Not(Equals('hello'))), 'Not(Equals(hello))')
+ self.assertEqual(repr(Not(Equals('hello'))), 'Not(Equals(hello))')
+
+ def test_expression(self):
+ # Not requires an expression argument
+ self.assertRaises(TypeError, Not)
+ # expression must be a FilterExpression
+ self.assertRaises(TypeError, Not, 'hello')
+ self.assertRaises(TypeError, Not, 1234)
+ self.assertRaises(TypeError, Not, Predicate(ns.bse.filesize))
+ # member returns expression
+ self.assertEqual(Not(Equals('hello')).expr, Equals('hello'))
+
+
+class TestHas(unittest.TestCase):
+ def test_essentials(self):
+ pred = PredicateExpression()
+ count = FilterExpression()
+ # comparison
+ self.assertEqual(Has(pred, count), Has(pred, count))
+ self.assertEqual(hash(Has(pred, count)), hash(Has(pred, count)))
+ # comparison respects type
+ self.assertNotEqual(Has(pred, count), FilterExpression())
+ self.assertNotEqual(hash(Has(pred, count)), hash(FilterExpression()))
+ # comparison respects predicate
+ self.assertNotEqual(Has(pred, count), Has(Predicate(ns.bse.filesize), count))
+ self.assertNotEqual(hash(Has(pred, count)), hash(Has(Predicate(ns.bse.filesize), count)))
+ # comparison respects count
+ self.assertNotEqual(Has(pred, count), Has(pred, LessThan(5)))
+ self.assertNotEqual(hash(Has(pred, count)), hash(Has(pred, LessThan(5))))
+ # string conversion
+ self.assertEqual(str(Has(Predicate(ns.bse.filesize), LessThan(5))),
+ f'Has(Predicate({ns.bse.filesize}, False), LessThan(5.0, True))')
+ self.assertEqual(repr(Has(Predicate(ns.bse.filesize), LessThan(5))),
+ f'Has(Predicate({ns.bse.filesize}, False), LessThan(5.0, True))')
+
+ def test_members(self):
+ pred = PredicateExpression()
+ count = FilterExpression()
+ # member returns expression
+ # predicate must be an URI or a PredicateExpression
+ self.assertEqual(Has(ns.bse.filesize, count).predicate, Predicate(ns.bse.filesize))
+ self.assertEqual(Has(Predicate(ns.bse.filesize), count).predicate, Predicate(ns.bse.filesize))
+ self.assertRaises(TypeError, Has, 1234, FilterExpression())
+ self.assertRaises(TypeError, Has, FilterExpression(), FilterExpression())
+ # member returns count
+ # count must be None, an integer, or a FilterExpression
+ self.assertEqual(Has(pred).count, GreaterThan(1, False))
+ self.assertEqual(Has(pred, LessThan(5)).count, LessThan(5))
+ self.assertEqual(Has(pred, 5).count, Equals(5))
+ self.assertRaises(TypeError, Has, pred, 'hello')
+ self.assertRaises(TypeError, Has, pred, Predicate(ns.bse.filesize))
+
+
+
+class TestValue(unittest.TestCase):
+ def test_essentials(self):
+ # comparison respects type
+ self.assertNotEqual(_Value('hello'), Equals('hello'))
+ self.assertNotEqual(Equals('hello'), Is('hello'))
+ self.assertNotEqual(Is('hello'), Substring('hello'))
+ self.assertNotEqual(Substring('hello'), StartsWith('hello'))
+ self.assertNotEqual(StartsWith('hello'), EndsWith('hello'))
+ self.assertNotEqual(EndsWith('hello'), _Value('hello'))
+ self.assertNotEqual(hash(_Value('hello')), hash(Equals('hello')))
+ self.assertNotEqual(hash(Equals('hello')), hash(Is('hello')))
+ self.assertNotEqual(hash(Is('hello')), hash(Substring('hello')))
+ self.assertNotEqual(hash(Substring('hello')), hash(StartsWith('hello')))
+ self.assertNotEqual(hash(StartsWith('hello')), hash(EndsWith('hello')))
+ self.assertNotEqual(hash(EndsWith('hello')), hash(_Value('hello')))
+
+ for cls in (_Value, Is, Equals, Substring, StartsWith, EndsWith):
+ # comparison
+ self.assertEqual(cls('hello'), cls('hello'))
+ self.assertEqual(hash(cls('hello')), hash(cls('hello')))
+ # comparison respects value
+ self.assertNotEqual(cls('hello'), cls('world'))
+ self.assertNotEqual(hash(cls('hello')), hash(cls('world')))
+
+ # string conversion
+ self.assertEqual(str(_Value('hello')), '_Value(hello)')
+ self.assertEqual(repr(_Value('hello')), '_Value(hello)')
+ self.assertEqual(str(Is('hello')), 'Is(hello)')
+ self.assertEqual(repr(Is('hello')), 'Is(hello)')
+ self.assertEqual(str(Equals('hello')), 'Equals(hello)')
+ self.assertEqual(repr(Equals('hello')), 'Equals(hello)')
+ self.assertEqual(str(Substring('hello')), 'Substring(hello)')
+ self.assertEqual(repr(Substring('hello')), 'Substring(hello)')
+ self.assertEqual(str(StartsWith('hello')), 'StartsWith(hello)')
+ self.assertEqual(repr(StartsWith('hello')), 'StartsWith(hello)')
+ self.assertEqual(str(EndsWith('hello')), 'EndsWith(hello)')
+ self.assertEqual(repr(EndsWith('hello')), 'EndsWith(hello)')
+
+ def test_value(self):
+ class Foo(): pass
+ for cls in (_Value, Is, Equals, Substring, StartsWith, EndsWith):
+ # value can be anything
+ # value returns member
+ f = Foo()
+ self.assertEqual(cls('hello').value, 'hello')
+ self.assertEqual(cls(1234).value, 1234)
+ self.assertEqual(cls(f).value, f)
+
+
+class TestDistance(unittest.TestCase):
+ def test_essentials(self):
+ ref = (1,2,3)
+ # comparison
+ self.assertEqual(Distance(ref, 3), Distance(ref, 3))
+ self.assertEqual(hash(Distance(ref, 3)), hash(Distance(ref, 3)))
+ # comparison respects type
+ self.assertNotEqual(Distance(ref, 3), FilterExpression())
+ self.assertNotEqual(hash(Distance(ref, 3)), hash(FilterExpression()))
+ # comparison respects reference
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2), 3, False)))
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,5,3), 3, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,5,3), 3, False)))
+ # comparison respects threshold
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3.1, False))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3.1, False)))
+ # comparison respects strict flag
+ self.assertNotEqual(Distance((1,2,3), 3, False), Distance((1,2,3), 3, True))
+ self.assertNotEqual(hash(Distance((1,2,3), 3, False)), hash(Distance((1,2,3), 3, True)))
+ # string conversion
+ self.assertEqual(str(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+ self.assertEqual(repr(Distance(ref, 3, False)), 'Distance((1, 2, 3), 3.0, False)')
+
+ def test_members(self):
+ self.assertEqual(Distance((1,2,3), 3, False).reference, (1,2,3))
+ self.assertEqual(Distance((3,2,1), 3, False).reference, (3,2,1))
+ self.assertEqual(Distance((1,2,3), 3, False).threshold, 3.0)
+ self.assertEqual(Distance((1,2,3), 53.45, False).threshold, 53.45)
+ self.assertEqual(Distance((1,2,3), 3, False).strict, False)
+ self.assertEqual(Distance((1,2,3), 3, True).strict, True)
+
+
+class TestBounded(unittest.TestCase):
+ def test_essentials(self):
+ # comparison respects type
+ self.assertNotEqual(_Bounded(1234), LessThan(1234))
+ self.assertNotEqual(LessThan(1234), GreaterThan(1234))
+ self.assertNotEqual(GreaterThan(1234), _Bounded(1234))
+ self.assertNotEqual(hash(_Bounded(1234)), hash(LessThan(1234)))
+ self.assertNotEqual(hash(LessThan(1234)), hash(GreaterThan(1234)))
+ self.assertNotEqual(hash(GreaterThan(1234)), hash(_Bounded(1234)))
+
+ for cls in (_Bounded, LessThan, GreaterThan):
+ # comparison
+ self.assertEqual(cls(1234), cls(1234))
+ self.assertEqual(hash(cls(1234)), hash(cls(1234)))
+ # comparison respects threshold
+ self.assertNotEqual(cls(1234), cls(4321))
+ self.assertNotEqual(hash(cls(1234)), hash(cls(4321)))
+ # comparison respects strict
+ self.assertNotEqual(cls(1234, True), cls(1234, False))
+ self.assertNotEqual(hash(cls(1234, True)), hash(cls(1234, False)))
+
+ # string conversion
+ self.assertEqual(str(_Bounded(1234, False)), '_Bounded(1234.0, False)')
+ self.assertEqual(repr(_Bounded(1234, False)), '_Bounded(1234.0, False)')
+ self.assertEqual(str(LessThan(1234, False)), 'LessThan(1234.0, False)')
+ self.assertEqual(repr(LessThan(1234, False)), 'LessThan(1234.0, False)')
+ self.assertEqual(str(GreaterThan(1234, False)), 'GreaterThan(1234.0, False)')
+ self.assertEqual(repr(GreaterThan(1234, False)), 'GreaterThan(1234.0, False)')
+
+ def test_members(self):
+ class Foo(): pass
+ for cls in (_Bounded, LessThan, GreaterThan):
+ # threshold becomes float
+ self.assertEqual(cls(1.234).threshold, 1.234)
+ self.assertEqual(cls(1234).threshold, 1234.0)
+ self.assertEqual(cls('1234').threshold, 1234)
+ self.assertRaises(TypeError, cls, Foo())
+ # strict becomes bool
+ self.assertEqual(cls(1234, True).strict, True)
+ self.assertEqual(cls(1234, False).strict, False)
+ self.assertEqual(cls(1234, Foo()).strict, True)
+
+
+class TestPredicate(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ self.assertEqual(Predicate(ns.bse.filesize), Predicate(ns.bse.filesize))
+ self.assertEqual(hash(Predicate(ns.bse.filesize)), hash(Predicate(ns.bse.filesize)))
+ # comparison respects type
+ self.assertNotEqual(Predicate(ns.bse.filesize), PredicateExpression())
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize)), hash(PredicateExpression()))
+ # comparison respects predicate
+ self.assertNotEqual(Predicate(ns.bse.filesize), Predicate(ns.bse.filename))
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize)), hash(Predicate(ns.bse.filename)))
+ # comparison respects reverse
+ self.assertNotEqual(Predicate(ns.bse.filesize, True), Predicate(ns.bse.filesize, False))
+ self.assertNotEqual(hash(Predicate(ns.bse.filesize, True)), hash(Predicate(ns.bse.filesize, False)))
+ # string conversion
+ self.assertEqual(str(Predicate(ns.bse.filesize)), f'Predicate({ns.bse.filesize}, False)')
+ self.assertEqual(str(Predicate(ns.bse.filesize, True)),
+ f'Predicate({ns.bse.filesize}, True)')
+ self.assertEqual(repr(Predicate(ns.bse.filesize)), f'Predicate({ns.bse.filesize}, False)')
+ self.assertEqual(repr(Predicate(ns.bse.filesize, True)),
+ f'Predicate({ns.bse.filesize}, True)')
+
+ def test_members(self):
+ # member returns predicate
+ # predicate must be an URI
+ self.assertEqual(Predicate(ns.bse.filesize).predicate, ns.bse.filesize)
+ self.assertEqual(Predicate(URI('hello_world')).predicate, URI('hello_world'))
+ self.assertRaises(TypeError, Predicate, 1234)
+ self.assertRaises(TypeError, Predicate, FilterExpression())
+ self.assertRaises(TypeError, Predicate, FilterExpression())
+ # reverse becomes a boolean
+ self.assertEqual(Predicate(ns.bse.filesize, True).reverse, True)
+ self.assertEqual(Predicate(ns.bse.filesize, False).reverse, False)
+ self.assertEqual(Predicate(ns.bse.filesize, 'abc').reverse, True)
+
+
+class TestOneOf(unittest.TestCase):
+ def test_essentials(self):
+ expr = {Predicate(ns.bse.filename), Predicate(ns.bse.filesize)}
+ # comparison
+ self.assertEqual(OneOf(expr), OneOf(expr))
+ self.assertEqual(hash(OneOf(expr)), hash(OneOf(expr)))
+ # comparison respects type
+ self.assertNotEqual(OneOf(expr), PredicateExpression())
+ self.assertNotEqual(hash(OneOf(expr)), hash(PredicateExpression()))
+ # comparison respects expression
+ self.assertNotEqual(OneOf(expr), OneOf(Predicate(ns.bse.filename)))
+ self.assertNotEqual(hash(OneOf(expr)), hash(OneOf(Predicate(ns.bse.filename))))
+ # string conversion
+ self.assertEqual(str(OneOf(Predicate(ns.bse.filesize))),
+ f'OneOf({{Predicate({ns.bse.filesize}, False)}})')
+ self.assertEqual(repr(OneOf(Predicate(ns.bse.filesize))),
+ f'OneOf({{Predicate({ns.bse.filesize}, False)}})')
+
+ def test_expression(self):
+ class Foo(): pass
+ # can pass expressions as arguments
+ self.assertSetEqual(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass one expressions as argument
+ self.assertSetEqual(OneOf(Predicate(ns.bse.filesize)).expr,
+ {Predicate(ns.bse.filesize)})
+ # can pass expressions as iterator
+ self.assertSetEqual(OneOf(iter((Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass expressions as generator
+ def gen():
+ yield Predicate(ns.bse.filesize)
+ yield Predicate(ns.bse.filename)
+ self.assertSetEqual(OneOf(gen()).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass expressions as list-like
+ self.assertSetEqual(OneOf((Predicate(ns.bse.filesize), Predicate(ns.bse.filename))).expr,
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # can pass one expression as list-like
+ self.assertSetEqual(OneOf([Predicate(ns.bse.filesize)]).expr,
+ {Predicate(ns.bse.filesize)})
+ # must pass expressions
+ self.assertRaises(TypeError, OneOf, Foo(), Foo())
+ self.assertRaises(TypeError, OneOf, [Foo(), Foo()])
+ # must pass at least one expression
+ self.assertRaises(AttributeError, OneOf)
+
+ # iter
+ self.assertSetEqual(set(iter(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))),
+ {Predicate(ns.bse.filesize), Predicate(ns.bse.filename)})
+ # contains
+ self.assertIn(Predicate(ns.bse.filesize),
+ OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))
+ self.assertNotIn(Predicate(ns.bse.tag),
+ OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename)))
+ # len
+ self.assertEqual(len(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename))), 2)
+ self.assertEqual(len(OneOf(Predicate(ns.bse.filesize), Predicate(ns.bse.filename), Predicate(ns.bse.tag))), 3)
+
+
+ def test_IsIn(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, IsIn)
+ # can pass expressions as arguments
+ self.assertEqual(IsIn('http://example.com/entity#1234', 'http://example.com/entity#4321'),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass one expression as argument
+ self.assertEqual(IsIn('http://example.com/entity#1234'),
+ Is('http://example.com/entity#1234'))
+ # can pass expressions as iterator
+ self.assertEqual(IsIn(iter(('http://example.com/entity#1234', 'http://example.com/entity#4321'))),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass expressions as generator
+ def gen():
+ yield 'http://example.com/entity#1234'
+ yield 'http://example.com/entity#4321'
+ self.assertEqual(IsIn(gen()),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass expressions as list-like
+ self.assertEqual(IsIn(['http://example.com/entity#1234', 'http://example.com/entity#4321']),
+ Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321')))
+ # can pass one expression as list-like
+ self.assertEqual(IsIn(['http://example.com/entity#1234']),
+ Is('http://example.com/entity#1234'))
+
+
+ def test_IsNotIn(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, IsNotIn)
+ # can pass expressions as arguments
+ self.assertEqual(IsNotIn('http://example.com/entity#1234', 'http://example.com/entity#4321'),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass one expression as argument
+ self.assertEqual(IsNotIn('http://example.com/entity#1234'),
+ Not(Is('http://example.com/entity#1234')))
+ # can pass expressions as iterator
+ self.assertEqual(IsNotIn(iter(('http://example.com/entity#1234', 'http://example.com/entity#4321'))),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass expressions as generator
+ def gen():
+ yield 'http://example.com/entity#1234'
+ yield 'http://example.com/entity#4321'
+ self.assertEqual(IsNotIn(gen()),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass expressions as list-like
+ self.assertEqual(IsNotIn(['http://example.com/entity#1234', 'http://example.com/entity#4321']),
+ Not(Or(Is('http://example.com/entity#1234'), Is('http://example.com/entity#4321'))))
+ # can pass one expression as list-like
+ self.assertEqual(IsNotIn(['http://example.com/entity#1234']),
+ Not(Is('http://example.com/entity#1234')))
+
+
+ def test_Includes(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, Includes)
+ # can pass expressions as arguments
+ self.assertEqual(Includes('hello', 'world'),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes('hello', 'world', approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass one expression as argument
+ self.assertEqual(Includes('hello'),
+ Equals('hello'))
+ self.assertEqual(Includes('hello', approx=True),
+ Substring('hello'))
+ # can pass expressions as iterator
+ self.assertEqual(Includes(iter(('hello', 'world'))),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(iter(('hello', 'world')), approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass expressions as generator
+ def gen():
+ yield 'hello'
+ yield 'world'
+ self.assertEqual(Includes(gen()),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(gen(), approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass expressions as list-like
+ self.assertEqual(Includes(['hello', 'world']),
+ Or(Equals('hello'), Equals('world')))
+ self.assertEqual(Includes(['hello', 'world'], approx=True),
+ Or(Substring('hello'), Substring('world')))
+ # can pass one expression as list-like
+ self.assertEqual(Includes(['hello']),
+ Equals('hello'))
+ self.assertEqual(Includes(['hello'], approx=True),
+ Substring('hello'))
+
+
+ def test_Excludes(self):
+ # cannot pass zero arguments
+ self.assertRaises(AttributeError, Excludes)
+ # can pass expressions as arguments
+ self.assertEqual(Excludes('hello', 'world'),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes('hello', 'world', approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass one expression as argument
+ self.assertEqual(Excludes('hello'),
+ Not(Equals('hello')))
+ self.assertEqual(Excludes('hello', approx=True),
+ Not(Substring('hello')))
+ # can pass expressions as iterator
+ self.assertEqual(Excludes(iter(('hello', 'world'))),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(iter(('hello', 'world')), approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass expressions as generator
+ def gen():
+ yield 'hello'
+ yield 'world'
+ self.assertEqual(Excludes(gen()),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(gen(), approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass expressions as list-like
+ self.assertEqual(Excludes(['hello', 'world']),
+ Not(Or(Equals('hello'), Equals('world'))))
+ self.assertEqual(Excludes(['hello', 'world'], approx=True),
+ Not(Or(Substring('hello'), Substring('world'))))
+ # can pass one expression as list-like
+ self.assertEqual(Excludes(['hello']),
+ Not(Equals('hello')))
+ self.assertEqual(Excludes(['hello'], approx=True),
+ Not(Substring('hello')))
+
+
+ def test_Between(self):
+ # must specify at least one bound
+ self.assertRaises(ValueError, Between, float('inf'), float('inf'))
+ # lower bound must be less than the upper bound
+ self.assertRaises(ValueError, Between, 321, 123)
+ # can set a lower bound only
+ self.assertEqual(Between(123),
+ GreaterThan(123, strict=True))
+ self.assertEqual(Between(123, lo_strict=False),
+ GreaterThan(123, strict=False))
+ # can set an upper bound only
+ self.assertEqual(Between(hi=123),
+ LessThan(123, strict=True))
+ self.assertEqual(Between(hi=123, hi_strict=False),
+ LessThan(123, strict=False))
+ # can set both bounds
+ self.assertEqual(Between(123, 321),
+ And(GreaterThan(123, strict=True), LessThan(321, strict=True)))
+ self.assertEqual(Between(123, 321, False, False),
+ And(GreaterThan(123, strict=False), LessThan(321, strict=False)))
+ # can set identical bounds
+ self.assertRaises(ValueError, Between, 123, 123)
+ self.assertEqual(Between(123, 123, False, False),
+ Equals(123))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/test_matcher.py b/test/query/test_matcher.py
new file mode 100644
index 0000000..6b975b2
--- /dev/null
+++ b/test/query/test_matcher.py
@@ -0,0 +1,1177 @@
+
+# imports
+import operator
+import unittest
+
+# bsfs imports
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.query.matcher import Any, Filter, Partial, Rest, _set_matcher
+
+
+## code ##
+
+class TestAny(unittest.TestCase):
+ def test_essentials(self):
+ # comparison
+ a = Any()
+ b = Any()
+ self.assertNotEqual(Any(), Any())
+ self.assertNotEqual(hash(Any()), hash(Any()))
+ self.assertNotEqual(a, Any())
+ self.assertNotEqual(hash(a), hash(Any()))
+ self.assertNotEqual(a, b)
+ self.assertNotEqual(hash(a), hash(b))
+ # comparison within sets
+ self.assertEqual(len({Any(), Any(), Any(), Any()}), 4)
+ self.assertEqual(len({Any() for _ in range(1000)}), 1000)
+ # string representation
+ self.assertEqual(str(Any()), 'Any()')
+ self.assertEqual(repr(Any()), 'Any()')
+
+
+class TestRest(unittest.TestCase):
+ def test_essentials(self):
+ expr = ast.filter.Equals('hello')
+ # comparison
+ self.assertEqual(Rest(expr), Rest(expr))
+ self.assertEqual(hash(Rest(expr)), hash(Rest(expr)))
+ # comparison respects type
+ class Foo(): pass
+ self.assertNotEqual(Rest(expr), 1234)
+ self.assertNotEqual(hash(Rest(expr)), hash(1234))
+ self.assertNotEqual(Rest(expr), Foo())
+ self.assertNotEqual(hash(Rest(expr)), hash(Foo()))
+ # comparison respects expr
+ self.assertNotEqual(Rest(expr), Rest(ast.filter.Equals('world')))
+ self.assertNotEqual(hash(Rest(expr)), hash(Rest(ast.filter.Equals('world'))))
+ # default constructor -> Any -> Not equal
+ self.assertNotEqual(Rest(), Rest())
+ self.assertNotEqual(hash(Rest()), hash(Rest()))
+ # string representation
+ self.assertEqual(str(Rest()), 'Rest(Any())')
+ self.assertEqual(str(Rest(expr)), 'Rest(Equals(hello))')
+ self.assertEqual(repr(Rest()), 'Rest(Any())')
+ self.assertEqual(repr(Rest(expr)), 'Rest(Equals(hello))')
+
+
+
+class TestPartial(unittest.TestCase):
+ def test_match(self):
+ p0 = Partial(ast.filter.LessThan)
+ p1 = Partial(ast.filter.LessThan, threshold=3)
+ p2 = Partial(ast.filter.LessThan, strict=False)
+ p3 = Partial(ast.filter.LessThan, threshold=3, strict=False)
+ # match respects name
+ self.assertTrue(p0.match('foo', None))
+ self.assertTrue(p1.match('foo', None))
+ self.assertTrue(p2.match('foo', None))
+ self.assertTrue(p3.match('foo', None))
+ # match respects correct value
+ self.assertTrue(p0.match('threshold', 3))
+ self.assertTrue(p1.match('threshold', 3))
+ self.assertTrue(p2.match('threshold', 3))
+ self.assertTrue(p3.match('threshold', 3))
+ self.assertTrue(p0.match('strict', False))
+ self.assertTrue(p1.match('strict', False))
+ self.assertTrue(p2.match('strict', False))
+ self.assertTrue(p3.match('strict', False))
+ # match respects incorrect value
+ self.assertTrue(p0.match('threshold', 5))
+ self.assertFalse(p1.match('threshold', 5))
+ self.assertTrue(p2.match('threshold', 5))
+ self.assertFalse(p3.match('threshold', 5))
+ self.assertTrue(p0.match('strict', True))
+ self.assertTrue(p1.match('strict', True))
+ self.assertFalse(p2.match('strict', True))
+ self.assertFalse(p3.match('strict', True))
+
+ def test_members(self):
+ # node returns expression
+ self.assertEqual(Partial(ast.filter.Equals).node, ast.filter.Equals)
+ self.assertEqual(Partial(ast.filter.LessThan).node, ast.filter.LessThan)
+ # kwargs returns arguments
+ self.assertDictEqual(Partial(ast.filter.Equals, value='hello').kwargs,
+ {'value': 'hello'})
+ self.assertDictEqual(Partial(ast.filter.LessThan, threshold=3, strict=False).kwargs,
+ {'threshold': 3, 'strict': False})
+ # Partial does not check about kwargs
+ self.assertDictEqual(Partial(ast.filter.LessThan, value='hello').kwargs,
+ {'value': 'hello'})
+ self.assertDictEqual(Partial(ast.filter.Equals, threshold=3, strict=False).kwargs,
+ {'threshold': 3, 'strict': False})
+
+ def test_essentials(self):
+ # comparison respects type
+ class Foo(): pass
+ self.assertNotEqual(Partial(ast.filter.Equals), 1234)
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(1234))
+ self.assertNotEqual(Partial(ast.filter.Equals), Foo())
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(Foo()))
+ self.assertNotEqual(Partial(ast.filter.Equals), ast.filter.Equals)
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(ast.filter.Equals))
+ self.assertNotEqual(Partial(ast.filter.Equals), ast.filter.Equals('hello'))
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(ast.filter.Equals('hello')))
+ # comparison respects node
+ self.assertEqual(Partial(ast.filter.Equals), Partial(ast.filter.Equals))
+ self.assertEqual(hash(Partial(ast.filter.Equals)), hash(Partial(ast.filter.Equals)))
+ self.assertEqual(Partial(ast.filter.LessThan), Partial(ast.filter.LessThan))
+ self.assertEqual(hash(Partial(ast.filter.LessThan)), hash(Partial(ast.filter.LessThan)))
+ self.assertNotEqual(Partial(ast.filter.Equals), Partial(ast.filter.LessThan))
+ self.assertNotEqual(hash(Partial(ast.filter.Equals)), hash(Partial(ast.filter.LessThan)))
+ # comparison respects kwargs
+ self.assertEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals, value='hello'))
+ self.assertEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals, value='hello')))
+ self.assertEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=False))
+ self.assertEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)))
+ self.assertNotEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals)))
+ self.assertNotEqual(
+ Partial(ast.filter.Equals, value='hello'),
+ Partial(ast.filter.Equals, value='world'))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.Equals, value='hello')),
+ hash(Partial(ast.filter.Equals, value='world')))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=5))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=5)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=False))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, strict=False)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=True))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=True)))
+ self.assertNotEqual(
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=5, strict=False))
+ self.assertNotEqual(
+ hash(Partial(ast.filter.LessThan, threshold=3, strict=False)),
+ hash(Partial(ast.filter.LessThan, threshold=5, strict=False)))
+ # string representation
+ self.assertEqual(str(Partial(ast.filter.Equals)), 'Partial(Equals, {})')
+ self.assertEqual(repr(Partial(ast.filter.Equals)), 'Partial(Equals, {})')
+ self.assertEqual(str(Partial(ast.filter.LessThan)), 'Partial(LessThan, {})')
+ self.assertEqual(repr(Partial(ast.filter.LessThan)), 'Partial(LessThan, {})')
+ self.assertEqual(str(Partial(ast.filter.Equals, value='hello')), "Partial(Equals, {'value': 'hello'})")
+ self.assertEqual(repr(Partial(ast.filter.Equals, value='hello')), "Partial(Equals, {'value': 'hello'})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, threshold=3)), "Partial(LessThan, {'threshold': 3})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, threshold=3)), "Partial(LessThan, {'threshold': 3})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, strict=False)), "Partial(LessThan, {'strict': False})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, strict=False)), "Partial(LessThan, {'strict': False})")
+ self.assertEqual(str(Partial(ast.filter.LessThan, threshold=3, strict=False)), "Partial(LessThan, {'threshold': 3, 'strict': False})")
+ self.assertEqual(repr(Partial(ast.filter.LessThan, threshold=3, strict=False)), "Partial(LessThan, {'threshold': 3, 'strict': False})")
+
+
+class TestSetMatcher(unittest.TestCase):
+ def test_set_matcher(self):
+ # setup
+ A = ast.filter.Equals('A')
+ B = ast.filter.Equals('B')
+ C = ast.filter.Equals('C')
+ D = ast.filter.Equals('D')
+ matcher = Filter()
+
+ # identical sets match
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, C),
+ matcher._parse_filter_expression,
+ ))
+
+ # order is irrelevant
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(B, C, A),
+ matcher._parse_filter_expression,
+ ))
+
+ # all reference items must be present
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, C),
+ matcher._parse_filter_expression,
+ ))
+
+ # all reference items must have a match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(D, B, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, D, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, D),
+ matcher._parse_filter_expression,
+ ))
+
+ # Any matches every item
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), B, C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Any(), C),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # there can be multiple Any's
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any(), C),
+ matcher._parse_filter_expression,
+ ))
+
+ # Any covers exactly one element
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C, D),
+ ast.filter.And(A, B, Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # each Any covers exactly one element
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(Any(), Any(), Any()),
+ matcher._parse_filter_expression,
+ ))
+
+ # Rest captures remainder
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C, D),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # remainder matches the empty set
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest does not absolve other refernce items from having a match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, B, Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can be combined with Any ...
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # ... explicit items still need to match
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, C, D),
+ ast.filter.And(B, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # ... Any still determines minimum element count
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any(), Any(), Rest()),
+ matcher._parse_filter_expression,
+ ))
+ # Rest cannot be repeated ...
+ self.assertRaises(errors.BackendError, _set_matcher,
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(), Rest(ast.filter.Equals('hello'))),
+ matcher._parse_filter_expression,
+ )
+ # ... unless they are identical
+ self.assertRaises(errors.BackendError, _set_matcher,
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(), Rest()), # Any instances are different!
+ matcher._parse_filter_expression,
+ )
+ # ... unless they are identical
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(C), Rest(C)),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can mandate a specific expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(C)),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(D)),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can mandate a partial expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Substring))),
+ matcher._parse_filter_expression,
+ ))
+ self.assertFalse(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(A, B, Rest(Partial(ast.filter.Equals, value='D'))),
+ matcher._parse_filter_expression,
+ ))
+ # Rest can be the only expression
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Rest(Partial(ast.filter.Equals))),
+ matcher._parse_filter_expression,
+ ))
+ # Rest's expression defaults to Any
+ self.assertTrue(_set_matcher(
+ ast.filter.And(A, B, C),
+ ast.filter.And(Rest()),
+ matcher._parse_filter_expression,
+ ))
+
+
+class TestFilter(unittest.TestCase):
+ def setUp(self):
+ self.match = Filter()
+
+ def test_call(self):
+ # query must be a filter expression
+ self.assertRaises(errors.BackendError, self.match, 1234, Any())
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Predicate(ns.bse.filename), Any())
+ # reference must be a filter expression
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Equals('hello'), 1234)
+ self.assertRaises(errors.BackendError, self.match, ast.filter.Equals('hello'), ast.filter.Predicate(ns.bse.filename))
+ # reference can be Any or Partial
+ self.assertTrue(self.match(
+ ast.filter.Equals('hello'),
+ Any(),
+ ))
+ self.assertTrue(self.match(
+ ast.filter.Equals('hello'),
+ Partial(ast.filter.Equals),
+ ))
+ # call parses expression
+ self.assertTrue(self.match(
+ # query
+ ast.filter.And(
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.All(ns.bse.label,
+ ast.filter.Or(
+ ast.filter.Equals('hello'),
+ ast.filter.Equals('world'),
+ ast.filter.StartsWith('foo'),
+ ast.filter.EndsWith('bar'),
+ )
+ )
+ ),
+ ast.filter.Any(ns.bse.iso,
+ ast.filter.And(
+ ast.filter.GreaterThan(100, strict=True),
+ ast.filter.LessThan(200, strict=False),
+ )
+ ),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.featureA, ns.bse.featureB),
+ ast.filter.Distance([1,2,3], 1)
+ ),
+ ),
+ # reference
+ ast.filter.And(
+ ast.filter.Any(Any(),
+ ast.filter.All(Partial(ast.filter.Predicate, reverse=False),
+ ast.filter.Or(
+ Partial(ast.filter.StartsWith),
+ ast.filter.EndsWith('bar'),
+ Rest(Partial(ast.filter.Equals)),
+ )
+ )
+ ),
+ ast.filter.Any(ns.bse.iso,
+ ast.filter.And(
+ Partial(ast.filter.GreaterThan, strict=True),
+ Any(),
+ Rest(),
+ )
+ ),
+ ast.filter.Any(ast.filter.OneOf(Rest()),
+ Partial(ast.filter.Distance)
+ ),
+ ),
+ ))
+ self.assertFalse(self.match(
+ # query
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.And(
+ ast.filter.Any(ns.bse.label, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.collection, ast.filter.Is('http://example.com/col#123')),
+ ast.filter.Not(ast.filter.Has(ns.bse.label)),
+ )
+ ),
+ # reference
+ ast.filter.Any(ns.bse.tag,
+ ast.filter.And(
+ Any(),
+ ast.filter.Any(Partial(ast.filter.Predicate, reverse=True), # reverse mismatch
+ Partial(ast.filter.Is)),
+ ast.filter.Not(ast.filter.Has(Any(), Any())),
+ )
+ )
+ ))
+
+ def test_parse_filter_expression(self):
+ # Any matches every filter expression
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Not(ast.filter.FilterExpression()), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Has(ns.bse.filename), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Distance([1,2,3], 1.0), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.And(ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Or(ast.filter.Equals('hello')), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Equals('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Substring('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.StartsWith('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.EndsWith('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.Is('hello'), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.LessThan(3), Any()))
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.GreaterThan(3), Any()))
+ # Any matches invalid filter expressions
+ self.assertTrue(self.match._parse_filter_expression(
+ ast.filter.FilterExpression(), Any()))
+ # node must be an appropriate filter expression
+ self.assertRaises(errors.BackendError, self.match._parse_filter_expression,
+ ast.filter.FilterExpression(), ast.filter.FilterExpression())
+ self.assertRaises(errors.BackendError, self.match._parse_filter_expression,
+ 1234, ast.filter.FilterExpression())
+
+ def test_parse_predicate_expression(self):
+ # Any matches every predicate expression
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.Predicate(ns.bse.filename), Any()))
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.OneOf(ns.bse.filename), Any()))
+ # Any matches invalid predicate expression
+ self.assertTrue(self.match._parse_predicate_expression(
+ ast.filter.FilterExpression(), Any()))
+ # node must be an appropriate predicate expression
+ self.assertRaises(errors.BackendError, self.match._parse_predicate_expression,
+ ast.filter.PredicateExpression(), ast.filter.PredicateExpression())
+ self.assertRaises(errors.BackendError, self.match._parse_predicate_expression,
+ 1234, ast.filter.PredicateExpression())
+
+ def test_predicate(self):
+ # identical expressions match
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ))
+ # _predicate respects type
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.FilterExpression(),
+ ))
+ # _predicate respects predicate
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ ))
+ # _predicate respects reverse
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ ast.filter.Predicate(ns.bse.filename, reverse=True),
+ ))
+ # Partial requires ast.filter.Predicate
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Equals),
+ ))
+ # predicate and reverse can be specified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filesize, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename, reverse=True),
+ ))
+ # predicate can remain unspecified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, reverse=False),
+ ))
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ Partial(ast.filter.Predicate, reverse=False),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filesize, reverse=False),
+ Partial(ast.filter.Predicate, reverse=True),
+ ))
+ # reverse can remain unspecified
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename),
+ ))
+ self.assertTrue(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=True),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filename),
+ ))
+ self.assertFalse(self.match._predicate(
+ ast.filter.Predicate(ns.bse.filename, reverse=False),
+ Partial(ast.filter.Predicate, predicate=ns.bse.filesize),
+ ))
+
+ def test_one_of(self):
+ A = ast.filter.Predicate(ns.bse.filename)
+ B = ast.filter.Predicate(ns.bse.filesize)
+ C = ast.filter.Predicate(ns.bse.filename, reverse=True)
+ # identical expressions match
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, B),
+ ))
+ # _one_of respects type
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.Predicate(ns.bse.filesize, reverse=True),
+ ))
+ # _one_of respects child expressions
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, C),
+ ))
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A),
+ ))
+ self.assertFalse(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, B, C),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(B, A),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(A, Any()),
+ ))
+ self.assertTrue(self.match._one_of(
+ ast.filter.OneOf(A, B),
+ ast.filter.OneOf(B, Rest()),
+ ))
+
+ def test_branch(self):
+ # identical expressions match
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ # _agg respects type
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ # _agg respects predicate expression
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.Predicate(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filesize, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filesize, ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.OneOf(ns.bse.filename), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ast.filter.Predicate(ns.bse.filename, reverse=True), ast.filter.Equals('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ast.filter.Predicate(ns.bse.filename, reverse=True), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(Any(), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(Any(), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(Partial(ast.filter.Predicate), ast.filter.Equals('hello')),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(Partial(ast.filter.Predicate), ast.filter.Equals('hello')),
+ ))
+ # _agg respects filter expression
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Substring('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.Substring('hello')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, ast.filter.Any(Any(), Any())),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, ast.filter.All(Any(), Any())),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Any()),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Any()),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Partial(ast.filter.Equals)),
+ ))
+ self.assertTrue(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Partial(ast.filter.Equals)),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.Any(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.Any(ns.bse.filename, Partial(ast.filter.Equals, value='world')),
+ ))
+ self.assertFalse(self.match._branch(
+ ast.filter.All(ns.bse.filename, ast.filter.Equals('hello')),
+ ast.filter.All(ns.bse.filename, Partial(ast.filter.Equals, value='world')),
+ ))
+
+ def test_agg(self):
+ A = ast.filter.Equals('hello')
+ B = ast.filter.Equals('world')
+ C = ast.filter.Equals('foobar')
+ # identical expressions match
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, B),
+ ))
+ # _agg respects type
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.Or(A, B),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.And(A, B),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Equals('hello'),
+ ))
+ # _agg respects child expressions
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, ast.filter.Equals('bar')),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, ast.filter.Equals('bar')),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, B, C),
+ ))
+ self.assertFalse(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, B, C),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(B, A),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(B, A),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(A, Any()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(A, Any()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.And(A, B),
+ ast.filter.And(B, Rest()),
+ ))
+ self.assertTrue(self.match._agg(
+ ast.filter.Or(A, B),
+ ast.filter.Or(B, Rest()),
+ ))
+
+ def test_not(self):
+ # identical expressions match
+ self.assertTrue(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ))
+ # _not respects type
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Equals('hello'),
+ ))
+ # _not respects child expression
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Equals('world')),
+ ))
+ self.assertFalse(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(ast.filter.Substring('hello')),
+ ))
+ self.assertTrue(self.match._not(
+ ast.filter.Not(ast.filter.Equals('hello')),
+ ast.filter.Not(Any()),
+ ))
+
+ def test_has(self):
+ # identical expressions match
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Has(ns.bse.filesize),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ))
+ # _has respects type
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize),
+ ast.filter.Equals('hello'),
+ ))
+ # _has respects predicate
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.iso, ast.filter.LessThan(3)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(Any(), ast.filter.LessThan(3)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(Partial(ast.filter.Predicate), ast.filter.LessThan(3)),
+ ))
+ # _has respects count
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.GreaterThan(3)),
+ ))
+ self.assertFalse(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(5)),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, Any()),
+ ))
+ self.assertTrue(self.match._has(
+ ast.filter.Has(ns.bse.filesize, ast.filter.LessThan(3)),
+ ast.filter.Has(ns.bse.filesize, Partial(ast.filter.LessThan)),
+ ))
+
+ def test_distance(self):
+ # identical expressions match
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 5, True),
+ ))
+ # _distance respects type
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Equals('hello'),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Equals),
+ ))
+ # _distance respects reference value
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([3,2,1], 5, True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, threshold=5, strict=True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[3,2,1], threshold=5, strict=True),
+ ))
+ # _distance respects threshold
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 8, True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], strict=True),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=8, strict=True),
+ ))
+ # _distance respects strict
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ ast.filter.Distance([1,2,3], 5, False),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5),
+ ))
+ self.assertTrue(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=True),
+ ))
+ self.assertFalse(self.match._distance(
+ ast.filter.Distance([1,2,3], 5, True),
+ Partial(ast.filter.Distance, reference=[1,2,3], threshold=5, strict=False),
+ ))
+
+ def test_value(self):
+ # identical expressions match
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), ast.filter.Equals('hello')))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), ast.filter.Substring('hello')))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), ast.filter.StartsWith('hello')))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), ast.filter.EndsWith('hello')))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), ast.filter.Is('hello')))
+ # _value respects type
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), ast.filter.Is('hello')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), ast.filter.Equals('hello')))
+ # _value respects value
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), ast.filter.Equals('world')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), ast.filter.Substring('world')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), ast.filter.StartsWith('world')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), ast.filter.EndsWith('world')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), ast.filter.Is('world')))
+ # Partial requires correct type
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.Is)))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Equals)))
+ # value can be specified
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals, value='world')))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring, value='world')))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith, value='world')))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith, value='world')))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is, value='hello')))
+ self.assertFalse(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is, value='world')))
+ # value can remain unspecified
+ self.assertTrue(self.match._value(ast.filter.Equals('hello'), Partial(ast.filter.Equals)))
+ self.assertTrue(self.match._value(ast.filter.Substring('hello'), Partial(ast.filter.Substring)))
+ self.assertTrue(self.match._value(ast.filter.StartsWith('hello'), Partial(ast.filter.StartsWith)))
+ self.assertTrue(self.match._value(ast.filter.EndsWith('hello'), Partial(ast.filter.EndsWith)))
+ self.assertTrue(self.match._value(ast.filter.Is('hello'), Partial(ast.filter.Is)))
+
+ def test_bounded(self):
+ # identical expressions match
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ))
+ # _bounded respects type
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=False),
+ ))
+ # _bounded respects threshold
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=4, strict=False),
+ ))
+ # _bounded respects strict
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ ast.filter.LessThan(threshold=3, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ ast.filter.GreaterThan(threshold=3, strict=True),
+ ))
+ # Partial requires correct type
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan),
+ ))
+ # threshold and strict can be specified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=4, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3, strict=True),
+ ))
+ # threshold can remain unspecified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=False),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, strict=False),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, strict=True),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, strict=True),
+ ))
+ # strict can remain unspecified
+ self.assertTrue(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=3),
+ ))
+ self.assertTrue(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=3),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.LessThan(threshold=3, strict=False),
+ Partial(ast.filter.LessThan, threshold=4),
+ ))
+ self.assertFalse(self.match._bounded(
+ ast.filter.GreaterThan(threshold=3, strict=False),
+ Partial(ast.filter.GreaterThan, threshold=4),
+ ))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/query/test_validator.py b/test/query/test_validator.py
new file mode 100644
index 0000000..418463e
--- /dev/null
+++ b/test/query/test_validator.py
@@ -0,0 +1,505 @@
+
+# imports
+import unittest
+
+# bsfs imports
+from bsfs import schema as _schema
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors
+
+# objects to test
+from bsfs.query.validator import Filter, Fetch
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestFilter(unittest.TestCase):
+ def setUp(self):
+ self.schema = _schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:URI rdfs:subClassOf bsfs:Literal .
+
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ <https://schema.bsfs.io/core/Literal/Array/Feature> rdfs:subClassOf bsl:Array .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ bsfs:Colors rdfs:subClassOf <https://schema.bsfs.io/core/Literal/Array/Feature> ;
+ bsfs:dimension "5"^^xsd:integer ;
+ bsfs:dtype bsfs:f32 .
+
+ bse:color rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range bsfs:Colors ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:buddy rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.validate = Filter(self.schema)
+
+ def test_call(self): # tests validate implicitly
+ # root_type must be a _schema.Node
+ self.assertRaises(TypeError, self.validate, 1234, None)
+ self.assertRaises(TypeError, self.validate, '1234', None)
+ self.assertRaises(TypeError, self.validate, self.schema.literal(ns.bsfs.URI), None)
+ # root_type must exist in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Image), None)
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.Image), None)
+ # valid query returns true
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Or(
+ ast.filter.Is('http://example.com/symbol#1234'),
+ ast.filter.All(ns.bse.comment, ast.filter.StartsWith('foo')),
+ ast.filter.And(
+ ast.filter.Has(ns.bse.comment, ast.filter.Or(
+ ast.filter.GreaterThan(5),
+ ast.filter.LessThan(1),
+ )
+ ),
+ ast.filter.Not(ast.filter.Any(ns.bse.comment,
+ ast.filter.Not(ast.filter.Equals('hello world')))),
+ ast.filter.Any(ns.bse.color, ast.filter.Distance([1,2,3,4,5], 3)),
+ )))))
+ # invalid paths raise consistency error
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy),
+ ast.filter.Or(
+ ast.filter.All(ns.bse.comment, ast.filter.Equals('hello world')),
+ ast.filter.All(ns.bse.label, ast.filter.Equals('hello world')), # domain mismatch
+ )))
+
+ def test_routing(self):
+ self.assertRaises(errors.BackendError, self.validate._parse_filter_expression, ast.filter.FilterExpression(), self.schema.node(ns.bsfs.Node))
+ self.assertRaises(errors.BackendError, self.validate._parse_predicate_expression, ast.filter.PredicateExpression())
+
+ def test_predicate(self):
+ # predicate must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._predicate, ast.filter.Predicate(ns.bse.invalid))
+ # predicate must have a range
+ self.assertRaises(errors.BackendError, self.validate._predicate, ast.filter.Predicate(ns.bsfs.Predicate))
+ # predicate returns domain and range
+ self.assertEqual(self.validate._predicate(ast.filter.Predicate(ns.bse.tag)),
+ (self.schema.node(ns.bsfs.Entity), self.schema.node(ns.bsfs.Tag)))
+ # reverse is applied
+ self.assertEqual(self.validate._predicate(ast.filter.Predicate(ns.bse.tag, reverse=True)),
+ (self.schema.node(ns.bsfs.Tag), self.schema.node(ns.bsfs.Entity)))
+
+ def test_one_of(self):
+ # domains must both be nodes or literals
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ast.filter.Predicate(ns.bse.label, reverse=True)))
+ # domains must be related
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ns.bse.label))
+ # ranges must both be nodes or literals
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ns.bse.comment))
+ # ranges must be related
+ self.assertRaises(errors.ConsistencyError, self.validate._one_of, ast.filter.OneOf(ns.bse.tag, ast.filter.Predicate(ns.bse.buddy, reverse=True)))
+ # one_of returns most specific domain
+ self.assertEqual(self.validate._one_of(ast.filter.OneOf(ns.bse.comment, ns.bse.label)),
+ (self.schema.node(ns.bsfs.Tag), self.schema.literal(ns.xsd.string)))
+ # one_of returns the most generic range
+ self.assertEqual(self.validate._one_of(ast.filter.OneOf(ns.bse.tag, ns.bse.buddy)),
+ (self.schema.node(ns.bsfs.Entity), self.schema.node(ns.bsfs.Node)))
+
+ def test_branch(self):
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal), None)
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), None)
+ # predicate is verified
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bsfs.Invalid, ast.filter.Is('http://example.com/entity#1234')))
+ # predicate must match the domain
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Tag),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#1234')))
+ # child expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world')))
+ # branch accepts valid expressions
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234'))))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.All(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234'))))
+
+ def test_agg(self):
+ # agg evaluates child expressions
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.literal(ns.xsd.string),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._agg, self.schema.literal(ns.xsd.string),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Equals('hello world')))
+ # agg works on nodes
+ self.assertIsNone(self.validate._agg(self.schema.node(ns.bsfs.Entity),
+ ast.filter.And(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Is('http://example.com/entity#4321'))))
+ self.assertIsNone(self.validate._agg(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Or(ast.filter.Is('http://example.com/entity#1234'), ast.filter.Is('http://example.com/entity#4321'))))
+ # agg works on literals
+ self.assertIsNone(self.validate._agg(self.schema.literal(ns.xsd.string),
+ ast.filter.And(ast.filter.Equals('foobar'), ast.filter.Equals('hello world'))))
+ self.assertIsNone(self.validate._agg(self.schema.literal(ns.xsd.string),
+ ast.filter.Or(ast.filter.Equals('foobar'), ast.filter.Equals('hello world'))))
+
+ def test_not(self):
+ # not evaluates child expressions
+ self.assertRaises(errors.ConsistencyError, self.validate._not, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Equals('hello world')))
+ self.assertRaises(errors.ConsistencyError, self.validate._not, self.schema.literal(ns.xsd.string),
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234')))
+ # not works on nodes
+ self.assertIsNone(self.validate._not(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Not(ast.filter.Is('http://example.com/entity#1234'))))
+ # not works on literals
+ self.assertIsNone(self.validate._not(self.schema.literal(ns.xsd.string),
+ ast.filter.Not(ast.filter.Equals('hello world'))))
+
+ def test_has(self):
+ # type must be node
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Has(ns.bse.tag))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.Has(ns.bse.tag))
+ # has checks predicate
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.invalid))
+ # predicate must match domain
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Tag),
+ ast.filter.Has(ns.bse.tag))
+ # has checks count expression
+ self.assertRaises(errors.ConsistencyError, self.validate._has, self.schema.node(ns.bsfs.Entity),
+ ast.filter.Has(ns.bse.tag, ast.filter.Is('http://example.com/entity#1234')))
+ # has accepts correct expressions
+ self.assertIsNone(self.validate._has(self.schema.node(ns.bsfs.Entity), ast.filter.Has(ns.bse.tag, ast.filter.GreaterThan(5))))
+
+ def test_is(self):
+ # type must be node
+ self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.literal(ns.bsfs.Literal),
+ ast.filter.Is('http://example.com/foo'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._is, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.Is('http://example.com/foo'))
+ # is accepts correct expressions
+ self.assertIsNone(self.validate._is(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#1234')))
+
+ def test_value(self):
+ # type must be literal
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.Equals('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.Substring('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.StartsWith('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Node),
+ ast.filter.EndsWith('hello world'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.Equals('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.Substring('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.StartsWith('hello world'))
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.EndsWith('hello world'))
+ # value accepts correct expressions
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.Equals('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.Substring('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.StartsWith('hello world')))
+ self.assertIsNone(self.validate._value(self.schema.literal(ns.xsd.string), ast.filter.EndsWith('hello world')))
+
+ def test_bounded(self):
+ # type must be literal
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.node(ns.bsfs.Node),
+ ast.filter.GreaterThan(0))
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.node(ns.bsfs.Node),
+ ast.filter.LessThan(0))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.GreaterThan(0))
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.bsfs.Literal).child(ns.bsfs.Invalid),
+ ast.filter.LessThan(0))
+ # type must be a number
+ self.assertRaises(errors.ConsistencyError, self.validate._bounded, self.schema.literal(ns.xsd.string),
+ ast.filter.LessThan(0))
+ # bounded accepts correct expressions
+ self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.LessThan(0)))
+ self.assertIsNone(self.validate._bounded(self.schema.literal(ns.xsd.integer), ast.filter.GreaterThan(0)))
+
+ def test_distance(self):
+ # type must be a literal
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.node(ns.bsfs.Node),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be a feature
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array),
+ ast.filter.Distance([1,2,3], 1, False))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array.Feature).child(ns.bsfs.Invalid),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must be a numpy array
+ # reference must have the correct dimension
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Colors),
+ ast.filter.Distance([1,2,3], 1, False))
+ # FIXME: reference must have the correct dtype
+ # distance accepts correct expressions
+ self.assertIsNone(self.validate._distance(self.schema.literal(ns.bsfs.Colors), ast.filter.Distance([1,2,3,4,5], 1, False)))
+
+
+class TestFetch(unittest.TestCase):
+ def setUp(self):
+ self.schema = _schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:filename rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag .
+
+ bse:label rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range xsd:string .
+
+ ''')
+ self.validate = Fetch(self.schema)
+
+ def test_call(self): # tests validate implicitly
+ # call accepts correct expressions
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'value'))))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.This('this')))
+ self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.This('this'), ast.fetch.Node(ns.bse.tag, 'node'), ast.fetch.Value(ns.bse.filename, 'value'))))
+ # type must be a Node
+ self.assertRaises(TypeError, self.validate, 1234, ast.fetch.This('this'))
+ self.assertRaises(TypeError, self.validate, 'foobar', ast.fetch.This('this'))
+ self.assertRaises(TypeError, self.validate, self.schema.literal(ns.bsfs.Literal), ast.fetch.This('this'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.FetchExpression())
+ # expression must be a fetch expression
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), 1234)
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), 'hello')
+ self.assertRaises(TypeError, self.validate, self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression())
+ # expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Node(ns.bse.label, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+
+ def test_routing(self):
+ # Node passes _branch, _named, and _node checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Node(ns.bse.tag, 'node')) # fails in _branch
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, '')) # fails in _named
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.label, 'node')) # fails in _node
+ # Value passes _branch, _named, and _value checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Value(ns.bse.label, 'value')) # fails in _branch
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, '')) # fails in _named
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value')) # fails in _value
+ # Fetch passes _branch and _fetch checks
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))) # fails in _branch
+ self.assertRaises(errors.ConsistencyError, self.validate._parse_fetch_expression, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.filename, ast.fetch.This('this'))) # fails in _fetch
+ # invalid expressions cannot be parsed
+ type_ = self.schema.node(ns.bsfs.Node)
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ ast.filter.FilterExpression())
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ 1234)
+ self.assertRaises(errors.BackendError, self.validate._parse_fetch_expression, type_,
+ 'hello world')
+
+ def test_all(self):
+ # all accepts correct expressions
+ self.assertIsNone(self.validate._all(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.filename, 'value'), ast.fetch.Node(ns.bse.tag, 'node'))))
+ # child expressions must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.filename, 'value'), ast.fetch.Node(ns.bse.filename, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value'), ast.fetch.Node(ns.bse.tag, 'node')))
+ self.assertRaises(errors.ConsistencyError, self.validate._all, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.All(ast.fetch.Value(ns.bse.tag, 'value'), ast.fetch.Node(ns.bse.filename, 'node')))
+
+ def test_branch(self):
+ # branch accepts correct expressions
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.filename)))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this'))))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch._Branch(ns.bse.filename))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Value(ns.bse.filename, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch._Branch(ns.bse.filename))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Value(ns.bse.filename, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ # predicate must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.invalid))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.invalid, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.invalid, 'value'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.invalid, 'node'))
+ # predicate's domain must be related to the type
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.label))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Fetch(ns.bse.label, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.label, 'node'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.label, 'value'))
+ # predicate's domain cannot be a supertype
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch._Branch(ns.bse.tag))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.This('this')))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Node(ns.bse.tag, 'node'))
+ self.assertRaises(errors.ConsistencyError, self.validate._branch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+ # predicate's domain can be a subtype
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch._Branch(ns.bse.filename)))
+ self.assertIsNone(self.validate._branch(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+
+ def test_fetch(self):
+ # fetch accepts correct expressions
+ self.assertIsNone(self.validate._fetch(self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'value'))))
+ # range must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._fetch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.filename, ast.fetch.This('this')))
+ # child expression must be valid
+ self.assertRaises(errors.ConsistencyError, self.validate._fetch, self.schema.node(ns.bsfs.Node),
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Node(ns.bse.label, 'node')))
+
+ def test_named(self):
+ # named accepts correct expressions
+ self.assertIsNone(self.validate._named(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ self.assertIsNone(self.validate._named(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ # name must be non-empty
+ self.assertRaises(errors.BackendError, self.validate._named, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, ''))
+ self.assertRaises(errors.BackendError, self.validate._named, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, ''))
+
+ def test_node(self):
+ # node accepts correct expressions
+ self.assertIsNone(self.validate._node(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.tag, 'node')))
+ # range must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._node, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Node(ns.bse.filename, 'node'))
+
+ def test_value(self):
+ # value accepts correct expressions
+ self.assertIsNone(self.validate._value(self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.filename, 'value')))
+ # range must be a literal
+ self.assertRaises(errors.ConsistencyError, self.validate._value, self.schema.node(ns.bsfs.Entity),
+ ast.fetch.Value(ns.bse.tag, 'value'))
+
+ def test_this(self):
+ # this accepts correct expressions
+ self.assertIsNone(self.validate._this(self.schema.node(ns.bsfs.Entity), ast.fetch.This('this')))
+ # type must be a node
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.literal(ns.bsfs.Literal),
+ ast.fetch.This('this'))
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.predicate(ns.bsfs.Predicate),
+ ast.fetch.This('this'))
+ # type must be in the schema
+ self.assertRaises(errors.ConsistencyError, self.validate._this, self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.fetch.This('this'))
+ # name must be non-empty
+ self.assertRaises(errors.BackendError, self.validate._this, self.schema.node(ns.bsfs.Entity), ast.fetch.This(''))
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##