diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/parsing/test_filter.py | 751 | ||||
-rw-r--r-- | test/parsing/test_search.py | 707 |
2 files changed, 751 insertions, 707 deletions
diff --git a/test/parsing/test_filter.py b/test/parsing/test_filter.py new file mode 100644 index 0000000..c01c1bf --- /dev/null +++ b/test/parsing/test_filter.py @@ -0,0 +1,751 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import unittest +from datetime import datetime + +# external imports +from pyparsing import ParseException + +# tagit imports +from tagit.utils import bsfs, errors, ns +from tagit.utils.bsfs import ast + +# objects to test +from tagit.parsing.filter import Filter + + +## code ## + +class TestFilterRange(unittest.TestCase): + longMessage = True + + def setUp(self): + #predicates.expose('mime', TestScope('attribute', 'mime'), 'Categorical') + #predicates.expose('iso', TestScope('attribute', 'iso'), 'Continuous', 'Categorical') + #predicates.expose('time', TestScope('generic', 't_image_create_loc'), 'TimeRange', 'Datetime') + #predicates.expose('tag', TestScope('generic', 'tag'), 'Categorical') + + #predicates.expose('mime', TestScope('attribute', 'mime'), 'Categorical') + #predicates.expose('rank', TestScope('attribute', 'rank'), 'Continuous') + #predicates.expose('iso', TestScope('attribute', 'iso'), 'Continuous', 'Categorical') + #predicates.expose('time', TestScope('generic', 't_image_create_loc'), 'TimeRange', 'Datetime') + #predicates.expose('tag', TestScope('generic', 'tag'), 'Categorical') + + + self.schema = bsfs.schema.from_string(''' + # common external prefixes + prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> + prefix xsd: <http://www.w3.org/2001/XMLSchema#> + + # common bsfs prefixes + prefix bsfs: <http://bsfs.ai/schema/> + prefix bse: <http://bsfs.ai/schema/Entity#> + + # nodes + bsfs:Entity rdfs:subClassOf bsfs:Node . + bsfs:Tag rdfs:subClassOf bsfs:Node . + + # literals + bsfs:Time rdfs:subClassOf bsfs:Literal . + xsd:string rdfs:subClassOf bsfs:Literal . + bsfs:Number rdfs:subClassOf bsfs:Literal . + xsd:integer rdfs:subClassOf bsfs:Number . + + # predicates + bse:mime rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Entity ; + rdfs:range xsd:string ; + bsfs:unique "true"^^xsd:boolean . + + bse:iso rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Entity ; + rdfs:range xsd:integer ; + bsfs:unique "true"^^xsd:boolean . + + bse:time rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Entity ; + rdfs:range bsfs:Time; + 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:rank rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Entity ; + rdfs:range xsd:integer ; + bsfs:unique "false"^^xsd:boolean . + + ''') + self.parse = Filter(self.schema) + + def _test_number(self, query, target): + predicate, condition = target + result = self.parse(query) + target = ast.filter.And(ast.filter.Any(predicate, condition)) + self.assertEqual(result, target, msg="in query '{}'".format(query)) + + def test_larger_than(self): + # larger than A (inclusive) + for editable in [ + # range + "{predicate} in [{num}:]", "{predicate} in [{num}:[", "{predicate} in [{num}:)", + "{predicate} : [{num}:]", "{predicate} : [{num}:[", "{predicate} : [{num}:)", + "{predicate} = [{num}:]", "{predicate} = [{num}:[", "{predicate} = [{num}:)", + ]: + # positive + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(1.23, False))) + # negative + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(-1.23, False))) + + for editable in [ + # range + "{predicate} in [{num}-]", "{predicate} in [{num}-[", "{predicate} in [{num}-)", + "{predicate} : [{num}-]", "{predicate} : [{num}-[", "{predicate} : [{num}-)", + "{predicate} = [{num}-]", "{predicate} = [{num}-[", "{predicate} = [{num}-)", + # equation + "{predicate} >= {num}", "{num} <= {predicate}", + ]: + # positive + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(1.23, False))) + # negative + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(-1.23, False))) + # FIXME: date + #self._test_number(editable.format(predicate='time', num="30.04.2012, 13:18"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime.max, True, False))) + + # larger than A (exclusive) + for editable in [ + # range / bracket + "{predicate} in ]{num}:]", "{predicate} in ]{num}:[", "{predicate} in ]{num}:)", + "{predicate} : ]{num}:]", "{predicate} : ]{num}:[", "{predicate} : ]{num}:)", + "{predicate} = ]{num}:]", "{predicate} = ]{num}:[", "{predicate} = ]{num}:)", + # range / parenthesis + "{predicate} in ({num}:]", "{predicate} in ({num}:[", "{predicate} in ({num}:)", + "{predicate} : ({num}:]", "{predicate} : ({num}:[", "{predicate} : ({num}:)", + "{predicate} = ({num}:]", "{predicate} = ({num}:[", "{predicate} = ({num}:)", + ]: + # positive + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(1.23, True))) + # negative + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(-1.23, True))) + + for editable in [ + # range / bracket + "{predicate} in ]{num}-]", "{predicate} in ]{num}-[", "{predicate} in ]{num}-)", + "{predicate} : ]{num}-]", "{predicate} : ]{num}-[", "{predicate} : ]{num}-)", + "{predicate} = ]{num}-]", "{predicate} = ]{num}-[", "{predicate} = ]{num}-)", + # range / parenthesis + "{predicate} in ({num}-]", "{predicate} in ({num}-[", "{predicate} in ({num}-)", + "{predicate} : ({num}-]", "{predicate} : ({num}-[", "{predicate} : ({num}-)", + "{predicate} = ({num}-]", "{predicate} = ({num}-[", "{predicate} = ({num}-)", + # equation + "{predicate} > {num}", "{num} < {predicate}", + ]: + # positive + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(1.23, True))) + # negative + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.GreaterThan(-1.23, True))) + # FIXME: date + #self._test_number(editable.format(predicate='time', num="30.04.2012, 13:18"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime.max, True, False))) + + def test_smaller_than(self): + # smaller than B (inclusive) + for editable in [ + # range + "{predicate} in [:{num}]", "{predicate} in (:{num}]", "{predicate} in ]:{num}]", + "{predicate} : [:{num}]", "{predicate} : (:{num}]", "{predicate} : ]:{num}]", + "{predicate} = [:{num}]", "{predicate} = (:{num}]", "{predicate} = ]:{num}]", + ]: + # positives + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(1.23, False))) + # negatives + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(-1.23, False))) + + for editable in [ + # range + "{predicate} in [-{num}]", "{predicate} in (-{num}]", "{predicate} in ]-{num}]", + "{predicate} : [-{num}]", "{predicate} : (-{num}]", "{predicate} : ]-{num}]", + "{predicate} = [-{num}]", "{predicate} = (-{num}]", "{predicate} = ]-{num}]", + # equation + "{predicate} <={num}", "{num} >= {predicate}", + ]: + # positives + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(1.23, False))) + # negatives + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(-1.23, False))) + # FIXME: date + #self._test_number(editable.format(predicate='time', num="30.04.2012, 13:18"), + # ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 13, 19), False, False))) + + # smaller than B (exclusive) + for editable in [ + # range / bracket + "{predicate} in [:{num}[", "{predicate} in (:{num}[", "{predicate} in ]:{num}[", + "{predicate} : [:{num}[", "{predicate} : (:{num}[", "{predicate} : ]:{num}[", + "{predicate} = [:{num}[", "{predicate} = (:{num}[", "{predicate} = ]:{num}[", + # range / parenthesis + "{predicate} in [:{num})", "{predicate} in (:{num})", "{predicate} in ]:{num})", + "{predicate} : [:{num})", "{predicate} : (:{num})", "{predicate} : ]:{num})", + "{predicate} = [:{num})", "{predicate} = (:{num})", "{predicate} = ]:{num})", + ]: + # positives + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(1.23, True))) + # negatives + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(-1.23, True))) + + for editable in [ + # range / bracket + "{predicate} in [-{num}[", "{predicate} in (-{num}[", "{predicate} in ]-{num}[", + "{predicate} : [-{num}[", "{predicate} : (-{num}[", "{predicate} : ]-{num}[", + "{predicate} = [-{num}[", "{predicate} = (-{num}[", "{predicate} = ]-{num}[", + # range / parenthesis + "{predicate} in [-{num})", "{predicate} in (-{num})", "{predicate} in ]-{num})", + "{predicate} : [-{num})", "{predicate} : (-{num})", "{predicate} : ]-{num})", + "{predicate} = [-{num})", "{predicate} = (-{num})", "{predicate} = ]-{num})", + # equation + "{predicate} <{num}", "{num} > {predicate}", + ]: + # positives + self._test_number(editable.format(num=1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(1.23, True))) + # negatives + self._test_number(editable.format(num=-1.23, predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(-1.23, True))) + # FIXME: date + #self._test_number(editable.format(predicate='time', num="30.04.2012, 13:18"), + # ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 13, 18), False, False))) + + def test_between(self): + # between A and B (including A, including B) + for editable in [ + # range + "{predicate} in [{numA}:{numB}]", "{predicate} : [{numA}:{numB}]", "{predicate} = [{numA}:{numB}]", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, False, False))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, False, False))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, False, False))) + + for editable in [ + # range + "{predicate} in [{numA}-{numB}]", "{predicate} : [{numA}-{numB}]", "{predicate} = [{numA}-{numB}]", + # equation + "{numA} <= {predicate} <= {numB}" + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, False, False))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, False, False))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, False, False))) + # FIXME: date + #self._test_number(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2014, 6, 13, 18, 28), True, False))) + + # between A and B (including A, excluding B) + for editable in [ + # range + "{predicate} in [{numA}:{numB})", "{predicate} in [{numA}:{numB}[", + "{predicate} : [{numA}:{numB})", "{predicate} : [{numA}:{numB}[", + "{predicate} = [{numA}:{numB})", "{predicate} = [{numA}:{numB}[", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, False, True))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, False, True))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, False, True))) + + for editable in [ + # range + "{predicate} in [{numA}-{numB})", "{predicate} in [{numA}-{numB}[", + "{predicate} : [{numA}-{numB})", "{predicate} : [{numA}-{numB}[", + "{predicate} = [{numA}-{numB})", "{predicate} = [{numA}-{numB}[", + # equation + "{numA} <= {predicate} < {numB}", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, False, True))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, False, True))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, False, True))) + # FIXME: date + #self._test_number(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2014, 6, 13, 18, 27), True, False))) + + # between A and B (excluding A, including B) + for editable in [ + # range + "{predicate} in ({numA}:{numB}]", "{predicate} in ]{numA}:{numB}]", + "{predicate} : ({numA}:{numB}]", "{predicate} : ]{numA}:{numB}]", + "{predicate} = ({numA}:{numB}]", "{predicate} = ]{numA}:{numB}]", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, True, False))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, True, False))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, True, False))) + + for editable in [ + # range + "{predicate} in ({numA}-{numB}]", "{predicate} in ]{numA}-{numB}]", + "{predicate} : ({numA}-{numB}]", "{predicate} : ]{numA}-{numB}]", + "{predicate} = ({numA}-{numB}]", "{predicate} = ]{numA}-{numB}]", + # equation + "{numA} < {predicate} <= {numB}", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, True, False))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, True, False))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, True, False))) + # FIXME: date + #self._test_number(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime(2014, 6, 13, 18, 28), True, False))) + + # between A and B (excluding A, excluding B) + for editable in [ + "{predicate} in ({numA}:{numB})", "{predicate} in ]{numA}:{numB}[", + "{predicate} : ({numA}:{numB})", "{predicate} : ]{numA}:{numB}[", + "{predicate} = ({numA}:{numB})", "{predicate} = ]{numA}:{numB}[", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, True, True))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, True, True))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, True, True))) + + for editable in [ + "{predicate} in ({numA}-{numB})", "{predicate} in ]{numA}-{numB}[", + "{predicate} : ({numA}-{numB})", "{predicate} : ]{numA}-{numB}[", + "{predicate} = ({numA}-{numB})", "{predicate} = ]{numA}-{numB}[", + # equation + "{numA} < {predicate} < {numB}", + ]: + # positives + self._test_number(editable.format(predicate='iso', numA=1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, True, True))) + # negatives + self._test_number(editable.format(predicate='iso', numA=-4.56, numB=-1.23), + (ns.bse.iso, ast.filter.Between(-4.56, -1.23, True, True))) + # mixed + self._test_number(editable.format(predicate='iso', numA=-1.23, numB=4.56), + (ns.bse.iso, ast.filter.Between(-1.23, 4.56, True, True))) + # FIXME: date + #self._test_number(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime(2014, 6, 13, 18, 27), True, False))) + + def test_equal(self): + # equal to A + for editable in [ + # range + "{predicate} in [{num}:{num}]", "{predicate} : [{num}:{num}]", "{predicate} = [{num}:{num}]", + ]: + # positives + self._test_number(editable.format(predicate='iso', num=1.23), + (ns.bse.iso, ast.filter.Equals(1.23))) + # negatives + self._test_number(editable.format(predicate='iso', num=-1.23), + (ns.bse.iso, ast.filter.Equals(-1.23))) + + for editable in [ + # range + "{predicate} in [{num}-{num}]", "{predicate} : [{num}-{num}]", "{predicate} = [{num}-{num}]", + # equation + "{predicate} = {num}", "{num} = {predicate}", + ]: + # positives + self._test_number(editable.format(predicate='iso', num=1.23), + (ns.bse.iso, ast.filter.Equals(1.23))) + # negatives + self._test_number(editable.format(predicate='iso', num=-1.23), + (ns.bse.iso, ast.filter.Equals(-1.23))) + # FIXME: date + #self._test_number(editable.format(predicate='time', num="30.04.2012, 13:18"), + # ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2012, 4, 30, 13, 19), True, False))) + + def test_dates(self): + raise NotImplementedError() # FIXME + self._test_number("{predicate} < {num}".format(predicate='time', num="2012"), + ('time', ast.Datetime(datetime.min, datetime(2012, 1, 1), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 1), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04.30"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04.30, 3 pm"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34:12"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12), False, False))) + self._test_number("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34:12.98"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12, 980000), False, False))) + + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012"), + ('time', ast.Datetime(datetime.min, datetime(2013, 1, 1), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04"), + ('time', ast.Datetime(datetime.min, datetime(2012, 5, 1), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04.30"), + ('time', ast.Datetime(datetime.min, datetime(2012, 5, 1), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 3 pm"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 16), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 35), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34:12"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 13), False, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34:12.98"), + ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12, 980001), False, False))) + + def test_timerange(self): + raise NotImplementedError() # FIXME + self._test_number("{predicate} < {num}".format(predicate='time', num="15:34"), + ('time', ast.TimeRange(datetime.utcfromtimestamp(0.0), datetime(1970, 1, 1, 15, 34), True, False))) + self._test_number("{predicate} <= {num}".format(predicate='time', num="15:34"), + ('time', ast.TimeRange(datetime.utcfromtimestamp(0.0), datetime(1970, 1, 1, 15, 35), True, False))) + self._test_number("{predicate} = {num}".format(predicate='time', num="15:34"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 34), datetime(1970, 1, 1, 15, 35), True, False))) + self._test_number("{predicate} > {num}".format(predicate='time', num="15:34"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 35), datetime(1970, 1, 2), True, True))) + self._test_number("{predicate} >= {num}".format(predicate='time', num="15:34"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 34), datetime(1970, 1, 2), True, True))) + + self._test_number("{numA} <= {predicate} <= {numB}".format(predicate='time', numA="12:34", numB="15:28"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 34), datetime(1970, 1, 1, 15, 29), True, False))) + self._test_number("{numA} <= {predicate} < {numB}".format(predicate='time', numA="12:34", numB="15:28"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 34), datetime(1970, 1, 1, 15, 28), True, False))) + self._test_number("{numA} < {predicate} <= {numB}".format(predicate='time', numA="12:34", numB="15:28"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 35), datetime(1970, 1, 1, 15, 29), True, False))) + self._test_number("{numA} < {predicate} < {numB}".format(predicate='time', numA="12:34", numB="15:28"), + ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 35), datetime(1970, 1, 1, 15, 28), True, False))) + + def test_special(self): + # special cases: explicit plus sign + self._test_number("{predicate} in [+1.23-+4.56]".format(predicate='iso'), + (ns.bse.iso, ast.filter.Between(1.23, 4.56, False, False))) + self._test_number("{predicate} in [-+4.56]".format(predicate='iso'), + (ns.bse.iso, ast.filter.LessThan(4.56, False))) + + def test_errors(self): + # parse errors + for editable in [ + # equal with exclusive + "{predicate} in ({num}:{num})", "{predicate} in ({num}-{num})", + "{predicate} in ({num}:{num}[", "{predicate} in ({num}-{num}[", + "{predicate} in ]{num}:{num})", "{predicate} in ]{num}-{num})", + "{predicate} in ]{num}:{num}[", "{predicate} in ]{num}-{num}[", + # invalid parentesis + "{predicate} in ){num}:{num}(", + # misc errors + # FIXME: Currently all special characters are allowed as categorical value. + # If this changes, don't forget to enable the tests below. + #"{predicate} in [{num}{num}]", + #"{predicate} [{num}:{num}:{num}]", + #"{predicate} = ({num})", + #"{predicate} = {num})", + ]: + self.assertRaises(errors.ParserError, self.parse, + editable.format(predicate='iso', num=1.23)) + + for editable in [ + "{predicate} in [{numA}:{numB}]", "{predicate} : [{numA}:{numB}]", "{predicate} = [{numA}:{numB}]", + "{predicate} in ]{numA}:{numB}]", "{predicate} : ]{numA}:{numB}]", "{predicate} = ]{numA}:{numB}]", + "{predicate} in [{numA}:{numB}[", "{predicate} : [{numA}:{numB}[", "{predicate} = [{numA}:{numB}[", + "{predicate} in ({numA}:{numB}]", "{predicate} : ({numA}:{numB}]", "{predicate} = ({numA}:{numB}]", + "{predicate} in [{numA}:{numB})", "{predicate} : [{numA}:{numB})", "{predicate} = [{numA}:{numB})", + "{predicate} in ]{numA}:{numB}[", "{predicate} : ]{numA}:{numB}[", "{predicate} = ]{numA}:{numB}[", + "{predicate} in ]{numA}:{numB})", "{predicate} : ]{numA}:{numB})", "{predicate} = ]{numA}:{numB})", + "{predicate} in ({numA}:{numB}[", "{predicate} : ({numA}:{numB}[", "{predicate} = ({numA}:{numB}[", + "{predicate} in ({numA}:{numB})", "{predicate} : ({numA}:{numB})", "{predicate} = ({numA}:{numB})", + "{numA} < {predicate} < {numB}", + "{numA} <= {predicate} < {numB}", + "{numA} < {predicate} <= {numB}", + ]: + self.assertRaises(errors.ParserError, self.parse, + editable.format(predicate='iso', numA=4.56, numB=1.23)) + # FIXME: + #self.assertRaises(errors.ParserError, self.parse, + # editable.format(predicate='time', numA="17:35", numB="10:55")) + #self.assertRaises(errors.ParserError, self.parse, + # editable.format(predicate='time', numA="18.12.2035", numB="5.7.1999")) + + raise NotImplementedError() # FIXME + # special cases: empty range with boundary + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} in [:]".format(predicate='iso')) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} in (:[".format(predicate='iso')) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} in ]:)".format(predicate='iso')) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} in ".format(predicate='iso')) + # misc + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} in [{num}{num}]".format(predicate='iso', num=1.23)) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} [{num}:{num}:{num}]".format(predicate='iso', num=1.23)) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} = ({num})".format(predicate='iso', num=1.23)) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} = ({num}".format(predicate='iso', num=1.23), dict(parseAll=True)) + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, + "{predicate} = {num})".format(predicate='iso', num=1.23), dict(parseAll=True)) + # range errors + self.assertRaises(errors.ParserError, self.parse, "100 >= iso < 200") + self.assertRaises(errors.ParserError, self.parse, "100 > iso < 200") + self.assertRaises(errors.ParserError, self.parse, "100 > iso <= 200") + self.assertRaises(errors.ParserError, self.parse, "100 >= iso <= 200") + self.assertRaises(errors.ParserError, self.parse, "100 = iso = 200") + # time/date mixture errors + self.assertRaises(errors.ParserError, self.parse, "12:45 < time < 17.5.2004") + self.assertRaises(errors.ParserError, self.parse, "17.5.2004 < time < 12:45") + # date/int mixture errors + self.assertRaises(errors.ParserError, self.parse, "17.5.2004 < time < 1245") + # 1245 is interpreted as the year + #self.assertRaises(errors.ParserError, self.parse, "1245 < time < 17.5.2004") + # time/int mixture errors + self.assertRaises(errors.ParserError, self.parse, "17:12 < time < 1245") + self.assertRaises(errors.ParserError, self.parse, "1712 < time < 12:45") + + # empty query + self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, "") + + + + + def _test(self, query, target): + result = self.parse(query) + target = ast.filter.And(target) + self.assertEqual(result, target, msg="in query '{}'".format(query)) + + def test_parse_existence(self): + self._test('has mime', + ast.filter.Has(ns.bse.mime)) + self._test('has no mime', + ast.filter.Not(ast.filter.Has(ns.bse.mime))) + self._test('has not mime', + ast.filter.Not(ast.filter.Has(ns.bse.mime))) + + def test_parse_categorical(self): + # positive + self._test("iso in 100, 200, 500", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200', '500'))) + self._test("iso in (100, 200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso = (100, 200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + # FIXME! + #self._test("iso = 100, 200", + # ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso : (100, 200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso : 100, 200", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso:(100,200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso in (100,200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso in 100,200", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200'))) + self._test("iso ~ (100,200)", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200', approx=True))) + self._test("iso ~ 100,200", + ast.filter.Any(ns.bse.iso, ast.filter.Includes('100', '200', approx=True))) + + # negative + self._test("iso not in 100,200", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200'))) + self._test("iso not in (100, 200)", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200'))) + self._test("iso != 100,200", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200'))) + self._test("iso != (100, 200)", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200'))) + self._test("iso !~ 100,200", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200', approx=True))) + self._test("iso !~ (100, 200)", + ast.filter.All(ns.bse.iso, ast.filter.Excludes('100', '200', approx=True))) + + # one value + self._test("mime : text", + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text'))) + self._test("mime in text", + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text'))) + self._test("mime = text", + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text'))) + self._test("mime ~ text", + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text', approx=True))) + self._test("mime != text", + ast.filter.All(ns.bse.mime, ast.filter.Excludes('text'))) + self._test("mime not in text", + ast.filter.All(ns.bse.mime, ast.filter.Excludes('text'))) + self._test("mime !~ text", + ast.filter.All(ns.bse.mime, ast.filter.Excludes('text', approx=True))) + + # expressions with slash and comma + self._test('mime : "text"', + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text'))) + self._test('mime : "text", "plain"', + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text', 'plain'))) + self._test('mime : "text, plain"', + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text, plain'))) + self._test('mime ~ "text/plain"', + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text/plain', approx=True))) + self._test('mime = ("text/plain", "image/jpeg")', + ast.filter.Any(ns.bse.mime, ast.filter.Includes('text/plain', 'image/jpeg'))) + + def test_parse_tag(self): + # only tag: tag, tags, (tag), (tags) + self._test("foo", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Equals('foo')))) + self._test("(foo)", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Equals('foo')))) + self._test("foo, bar", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar')))) + self._test("foo,bar", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar')))) + self._test("(foo, bar,foobar)", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar', 'foobar')))) + + # op and tag: !tag, ~tag, !~tag + self._test("~foo", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Substring('foo')))) + self._test("~ foo", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Substring('foo')))) + self._test("!foo", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Not(ast.filter.Equals('foo'))))) + self._test("! foo", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Not(ast.filter.Equals('foo'))))) + self._test("!~foo", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Not(ast.filter.Substring('foo'))))) + self._test("!~ foo", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Not(ast.filter.Substring('foo'))))) + + # op and list: ! (tags), ~tags, ... + self._test("~ foo, bar", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar', approx=True)))) + self._test("~foo, bar", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar', approx=True)))) + self._test("~ (foo, bar)", + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Includes('foo', 'bar', approx=True)))) + self._test("! foo, bar", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar')))) + self._test("! (foo, bar)", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar')))) + self._test("! (foo,bar)", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar')))) + self._test("!~ foo, bar", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar', approx=True)))) + self._test("!~ (foo, bar)", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar', approx=True)))) + self._test("!~(foo,bar)", + ast.filter.All(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Excludes('foo', 'bar', approx=True)))) + + def test_parse_query(self): + # simple query + self.assertEqual(self.parse('foo / bar'), ast.filter.And( + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Equals('foo'))), + ast.filter.Any(ns.bse.tag, ast.filter.Any(ns.bst.label, ast.filter.Equals('bar'))))) + self.assertEqual(self.parse('iso in ("foo", "bar") / mime = plain'), ast.filter.And( + ast.filter.Any(ns.bse.iso, ast.filter.Includes('foo', 'bar')), + ast.filter.Any(ns.bse.mime, ast.filter.Equals('plain')))) + self.assertEqual(self.parse('iso in ("foo", "bar") / mime = plain'), ast.filter.And( + ast.filter.Any(ns.bse.iso, ast.filter.Includes('foo', 'bar')), + ast.filter.Any(ns.bse.mime, ast.filter.Equals('plain')))) + self.assertEqual(self.parse('iso = 1.23 / rank < 5'), ast.filter.And( + ast.filter.Any(ns.bse.iso, ast.filter.Equals(1.23)), + ast.filter.Any(ns.bse.rank, ast.filter.LessThan(5, True)))) + # FIXME + #self.assertEqual(self.parse('time >= 12:50 / time < 13:50'), ast.filter.And( + # ast.filter.Any(ns.bse.time, ast.TimeRange(lo=datetime(1970, 1, 1, 12, 50), lo_inc=True, hi_inc=True)), + # ast.filter.Any(ns.bse.time, ast.TimeRange(hi=datetime(1970, 1, 1, 13, 50), lo_inc=True, hi_inc=False)))) + #self.assertEqual(self.parse('time >= 17.5.2001 / time < 18.4.2002'), ast.filter.And( + # ast.filter.Any(ns.bse.time, ast.Datetime(lo=datetime(2001, 5, 17, 0, 0), lo_inc=True)), + # ast.filter.Any(ns.bse.time, ast.Datetime(hi=datetime(2002, 4, 18, 0, 0))))) + # mixing expressions + #self.assertEqual(self.parse('foo / iso in "bar" / mime ~ "text/plain" / iso < 100 / time >= 17.5.2001 / time < 13:50'), ast.filter.And( + # ast.filter.Any(ns.bse.tag, ast.filter.Equals('foo')), + # ast.filter.Any(ns.bse.iso, ast.filter.Equals('bar')), + # ast.filter.Any(ns.bse.mime, ast.filter.Substring('text/plain')), + # ast.filter.Any(ns.bse.iso, ast.filter.LessThan(100)), + # ast.filter.Any(ns.bse.time, ast.Datetime(lo=datetime(2001, 5, 17, 0, 0), lo_inc=True)), + # ast.filter.Any(ns.bse.time, ast.TimeRange(hi=datetime(1970, 1, 1, 13, 50), lo_inc=True)))) + + # leading/trailing slashes + self.assertRaises(errors.ParserError, self.parse, '/ foobar') + self.assertRaises(errors.ParserError, self.parse, 'foobar /') + self.assertRaises(errors.ParserError, self.parse, 'foobar / ') + self.assertRaises(errors.ParserError, self.parse, 'foo // bar') + self.assertRaises(errors.ParserError, self.parse, 'foo / / bar') + + def test_quoting(self): + self._test("tag in ('(foo, bar)', foobar)", + ast.filter.Any(ns.bse.tag, ast.filter.Includes('(foo, bar)', 'foobar'))) + self._test('tag in ("(foo, bar)", foobar)', + ast.filter.Any(ns.bse.tag, ast.filter.Includes('(foo, bar)', 'foobar'))) + self._test('tag in ("(foo, \\"bar\\")", foobar)', + ast.filter.Any(ns.bse.tag, ast.filter.Includes('(foo, "bar")', 'foobar'))) + self._test('tag in ("(foo, bar)", "foobar")', + ast.filter.Any(ns.bse.tag, ast.filter.Includes('(foo, bar)', 'foobar'))) + self._test('tag in ("(foo, bar)", \'foobar\')', + ast.filter.Any(ns.bse.tag, ast.filter.Includes('(foo, bar)', 'foobar'))) + + # error cases + self.assertRaises(errors.ParserError, self.parse, ('tag in ("(foo, bar, foobar)')) + self.assertRaises(errors.ParserError, self.parse, ("tag in ('(foo, bar, foobar)")) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/parsing/test_search.py b/test/parsing/test_search.py deleted file mode 100644 index 23801d0..0000000 --- a/test/parsing/test_search.py +++ /dev/null @@ -1,707 +0,0 @@ -""" - -Part of the tagit test suite. -A copy of the license is provided with the project. -Author: Matthias Baumgartner, 2022 -""" -# standard imports -import unittest -from datetime import datetime - -# external imports -from pyparsing import ParseException - -# tagit imports -from tagit.utils import errors -#from tagit.parsing.search import ast, predicates, PredicateScope # FIXME: mb/port/parsing - -# objects to test -from tagit.parsing.search import ast_from_string - - -## code ## - -class TestScope(PredicateScope): - _scope_order = ['major', 'minor', 'micro'] - _init_values = ['library'] - - -class TestParseContinuous(unittest.TestCase): - longMessage = True - - def setUp(self): - predicates.expose('mime', - TestScope('attribute', 'mime'), 'Categorical') - predicates.expose('iso', - TestScope('attribute', 'iso'), 'Continuous', 'Categorical') - predicates.expose('time', - TestScope('generic', 't_image_create_loc'), 'TimeRange', 'Datetime') - predicates.expose('tag', - TestScope('generic', 'tag'), 'Categorical') - - def _test(self, query, target): - predicate, condition = target - result = ast_from_string(query) - target = ast.AND([ast.Token(predicate, condition)]) - self.assertEqual(result, target, msg="in query '{}'".format(query)) - - def test_larger_than(self): - # larger than A (inclusive) - for editable in [ - # range - "{predicate} in [{num}:]", "{predicate} in [{num}:[", "{predicate} in [{num}:)", - "{predicate} : [{num}:]", "{predicate} : [{num}:[", "{predicate} : [{num}:)", - "{predicate} = [{num}:]", "{predicate} = [{num}:[", "{predicate} = [{num}:)", - ]: - # positive - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(1.23, float('inf'), True, False))) - # negative - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(-1.23, float('inf'), True, False))) - - for editable in [ - # range - "{predicate} in [{num}-]", "{predicate} in [{num}-[", "{predicate} in [{num}-)", - "{predicate} : [{num}-]", "{predicate} : [{num}-[", "{predicate} : [{num}-)", - "{predicate} = [{num}-]", "{predicate} = [{num}-[", "{predicate} = [{num}-)", - # equation - "{predicate} >= {num}", "{num} <= {predicate}", - ]: - # positive - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(1.23, float('inf'), True, False))) - # negative - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(-1.23, float('inf'), True, False))) - # date - self._test(editable.format(predicate='time', num="30.04.2012, 13:18"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime.max, True, False))) - - # larger than A (exclusive) - for editable in [ - # range / bracket - "{predicate} in ]{num}:]", "{predicate} in ]{num}:[", "{predicate} in ]{num}:)", - "{predicate} : ]{num}:]", "{predicate} : ]{num}:[", "{predicate} : ]{num}:)", - "{predicate} = ]{num}:]", "{predicate} = ]{num}:[", "{predicate} = ]{num}:)", - # range / parenthesis - "{predicate} in ({num}:]", "{predicate} in ({num}:[", "{predicate} in ({num}:)", - "{predicate} : ({num}:]", "{predicate} : ({num}:[", "{predicate} : ({num}:)", - "{predicate} = ({num}:]", "{predicate} = ({num}:[", "{predicate} = ({num}:)", - ]: - # positive - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(1.23, float('inf'), False, False))) - # negative - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(-1.23, float('inf'), False, False))) - - for editable in [ - # range / bracket - "{predicate} in ]{num}-]", "{predicate} in ]{num}-[", "{predicate} in ]{num}-)", - "{predicate} : ]{num}-]", "{predicate} : ]{num}-[", "{predicate} : ]{num}-)", - "{predicate} = ]{num}-]", "{predicate} = ]{num}-[", "{predicate} = ]{num}-)", - # range / parenthesis - "{predicate} in ({num}-]", "{predicate} in ({num}-[", "{predicate} in ({num}-)", - "{predicate} : ({num}-]", "{predicate} : ({num}-[", "{predicate} : ({num}-)", - "{predicate} = ({num}-]", "{predicate} = ({num}-[", "{predicate} = ({num}-)", - # equation - "{predicate} > {num}", "{num} < {predicate}", - ]: - # positive - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(1.23, float('inf'), False, False))) - # negative - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(-1.23, float('inf'), False, False))) - # date - self._test(editable.format(predicate='time', num="30.04.2012, 13:18"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime.max, True, False))) - - def test_smaller_than(self): - # smaller than B (inclusive) - for editable in [ - # range - "{predicate} in [:{num}]", "{predicate} in (:{num}]", "{predicate} in ]:{num}]", - "{predicate} : [:{num}]", "{predicate} : (:{num}]", "{predicate} : ]:{num}]", - "{predicate} = [:{num}]", "{predicate} = (:{num}]", "{predicate} = ]:{num}]", - ]: - # positives - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), 1.23, False, True))) - # negatives - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), -1.23, False, True))) - - for editable in [ - # range - "{predicate} in [-{num}]", "{predicate} in (-{num}]", "{predicate} in ]-{num}]", - "{predicate} : [-{num}]", "{predicate} : (-{num}]", "{predicate} : ]-{num}]", - "{predicate} = [-{num}]", "{predicate} = (-{num}]", "{predicate} = ]-{num}]", - # equation - "{predicate} <={num}", "{num} >= {predicate}", - ]: - # positives - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), 1.23, False, True))) - # negatives - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), -1.23, False, True))) - # date - self._test(editable.format(predicate='time', num="30.04.2012, 13:18"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 13, 19), False, False))) - - # smaller than B (exclusive) - for editable in [ - # range / bracket - "{predicate} in [:{num}[", "{predicate} in (:{num}[", "{predicate} in ]:{num}[", - "{predicate} : [:{num}[", "{predicate} : (:{num}[", "{predicate} : ]:{num}[", - "{predicate} = [:{num}[", "{predicate} = (:{num}[", "{predicate} = ]:{num}[", - # range / parenthesis - "{predicate} in [:{num})", "{predicate} in (:{num})", "{predicate} in ]:{num})", - "{predicate} : [:{num})", "{predicate} : (:{num})", "{predicate} : ]:{num})", - "{predicate} = [:{num})", "{predicate} = (:{num})", "{predicate} = ]:{num})", - ]: - # positives - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), 1.23, False, False))) - # negatives - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), -1.23, False, False))) - - for editable in [ - # range / bracket - "{predicate} in [-{num}[", "{predicate} in (-{num}[", "{predicate} in ]-{num}[", - "{predicate} : [-{num}[", "{predicate} : (-{num}[", "{predicate} : ]-{num}[", - "{predicate} = [-{num}[", "{predicate} = (-{num}[", "{predicate} = ]-{num}[", - # range / parenthesis - "{predicate} in [-{num})", "{predicate} in (-{num})", "{predicate} in ]-{num})", - "{predicate} : [-{num})", "{predicate} : (-{num})", "{predicate} : ]-{num})", - "{predicate} = [-{num})", "{predicate} = (-{num})", "{predicate} = ]-{num})", - # equation - "{predicate} <{num}", "{num} > {predicate}", - ]: - # positives - self._test(editable.format(num=1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), 1.23, False, False))) - # negatives - self._test(editable.format(num=-1.23, predicate='iso'), - ('iso', ast.Continuous(float('-inf'), -1.23, False, False))) - # date - self._test(editable.format(predicate='time', num="30.04.2012, 13:18"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 13, 18), False, False))) - - def test_between(self): - # between A and B (including A, including B) - for editable in [ - # range - "{predicate} in [{numA}:{numB}]", "{predicate} : [{numA}:{numB}]", "{predicate} = [{numA}:{numB}]", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, True, True))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, True, True))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, True, True))) - - for editable in [ - # range - "{predicate} in [{numA}-{numB}]", "{predicate} : [{numA}-{numB}]", "{predicate} = [{numA}-{numB}]", - # equation - "{numA} <= {predicate} <= {numB}" - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, True, True))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, True, True))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, True, True))) - # date - self._test(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2014, 6, 13, 18, 28), True, False))) - - # between A and B (including A, excluding B) - for editable in [ - # range - "{predicate} in [{numA}:{numB})", "{predicate} in [{numA}:{numB}[", - "{predicate} : [{numA}:{numB})", "{predicate} : [{numA}:{numB}[", - "{predicate} = [{numA}:{numB})", "{predicate} = [{numA}:{numB}[", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, True, False))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, True, False))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, True, False))) - - for editable in [ - # range - "{predicate} in [{numA}-{numB})", "{predicate} in [{numA}-{numB}[", - "{predicate} : [{numA}-{numB})", "{predicate} : [{numA}-{numB}[", - "{predicate} = [{numA}-{numB})", "{predicate} = [{numA}-{numB}[", - # equation - "{numA} <= {predicate} < {numB}", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, True, False))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, True, False))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, True, False))) - # date - self._test(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2014, 6, 13, 18, 27), True, False))) - - # between A and B (excluding A, including B) - for editable in [ - # range - "{predicate} in ({numA}:{numB}]", "{predicate} in ]{numA}:{numB}]", - "{predicate} : ({numA}:{numB}]", "{predicate} : ]{numA}:{numB}]", - "{predicate} = ({numA}:{numB}]", "{predicate} = ]{numA}:{numB}]", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, False, True))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, False, True))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, False, True))) - - for editable in [ - # range - "{predicate} in ({numA}-{numB}]", "{predicate} in ]{numA}-{numB}]", - "{predicate} : ({numA}-{numB}]", "{predicate} : ]{numA}-{numB}]", - "{predicate} = ({numA}-{numB}]", "{predicate} = ]{numA}-{numB}]", - # equation - "{numA} < {predicate} <= {numB}", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, False, True))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, False, True))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, False, True))) - # date - self._test(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime(2014, 6, 13, 18, 28), True, False))) - - # between A and B (excluding A, excluding B) - for editable in [ - "{predicate} in ({numA}:{numB})", "{predicate} in ]{numA}:{numB}[", - "{predicate} : ({numA}:{numB})", "{predicate} : ]{numA}:{numB}[", - "{predicate} = ({numA}:{numB})", "{predicate} = ]{numA}:{numB}[", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, False, False))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, False, False))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, False, False))) - - for editable in [ - "{predicate} in ({numA}-{numB})", "{predicate} in ]{numA}-{numB}[", - "{predicate} : ({numA}-{numB})", "{predicate} : ]{numA}-{numB}[", - "{predicate} = ({numA}-{numB})", "{predicate} = ]{numA}-{numB}[", - # equation - "{numA} < {predicate} < {numB}", - ]: - # positives - self._test(editable.format(predicate='iso', numA=1.23, numB=4.56), - ('iso', ast.Continuous(1.23, 4.56, False, False))) - # negatives - self._test(editable.format(predicate='iso', numA=-4.56, numB=-1.23), - ('iso', ast.Continuous(-4.56, -1.23, False, False))) - # mixed - self._test(editable.format(predicate='iso', numA=-1.23, numB=4.56), - ('iso', ast.Continuous(-1.23, 4.56, False, False))) - # date - self._test(editable.format(predicate='time', numA="30.04.2012, 13:18", numB="13.6.2014, 18:27"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 19), datetime(2014, 6, 13, 18, 27), True, False))) - - def test_equal(self): - # equal to A - for editable in [ - # range - "{predicate} in [{num}:{num}]", "{predicate} : [{num}:{num}]", "{predicate} = [{num}:{num}]", - ]: - # positives - self._test(editable.format(predicate='iso', num=1.23), - ('iso', ast.Continuous(1.23, 1.23, True, True))) - # negatives - self._test(editable.format(predicate='iso', num=-1.23), - ('iso', ast.Continuous(-1.23, -1.23, True, True))) - - for editable in [ - # range - "{predicate} in [{num}-{num}]", "{predicate} : [{num}-{num}]", "{predicate} = [{num}-{num}]", - # equation - "{predicate} = {num}", "{num} = {predicate}", - ]: - # positives - self._test(editable.format(predicate='iso', num=1.23), - ('iso', ast.Continuous(1.23, 1.23, True, True))) - # negatives - self._test(editable.format(predicate='iso', num=-1.23), - ('iso', ast.Continuous(-1.23, -1.23, True, True))) - # date - self._test(editable.format(predicate='time', num="30.04.2012, 13:18"), - ('time', ast.Datetime(datetime(2012, 4, 30, 13, 18), datetime(2012, 4, 30, 13, 19), True, False))) - - def test_dates(self): - self._test("{predicate} < {num}".format(predicate='time', num="2012"), - ('time', ast.Datetime(datetime.min, datetime(2012, 1, 1), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 1), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04.30"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04.30, 3 pm"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34:12"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12), False, False))) - self._test("{predicate} < {num}".format(predicate='time', num="2012.04.30, 15:34:12.98"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12, 980000), False, False))) - - self._test("{predicate} <= {num}".format(predicate='time', num="2012"), - ('time', ast.Datetime(datetime.min, datetime(2013, 1, 1), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04"), - ('time', ast.Datetime(datetime.min, datetime(2012, 5, 1), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04.30"), - ('time', ast.Datetime(datetime.min, datetime(2012, 5, 1), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 3 pm"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 16), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 35), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34:12"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 13), False, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="2012.04.30, 15:34:12.98"), - ('time', ast.Datetime(datetime.min, datetime(2012, 4, 30, 15, 34, 12, 980001), False, False))) - - def test_timerange(self): - self._test("{predicate} < {num}".format(predicate='time', num="15:34"), - ('time', ast.TimeRange(datetime.utcfromtimestamp(0.0), datetime(1970, 1, 1, 15, 34), True, False))) - self._test("{predicate} <= {num}".format(predicate='time', num="15:34"), - ('time', ast.TimeRange(datetime.utcfromtimestamp(0.0), datetime(1970, 1, 1, 15, 35), True, False))) - self._test("{predicate} = {num}".format(predicate='time', num="15:34"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 34), datetime(1970, 1, 1, 15, 35), True, False))) - self._test("{predicate} > {num}".format(predicate='time', num="15:34"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 35), datetime(1970, 1, 2), True, True))) - self._test("{predicate} >= {num}".format(predicate='time', num="15:34"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 15, 34), datetime(1970, 1, 2), True, True))) - - self._test("{numA} <= {predicate} <= {numB}".format(predicate='time', numA="12:34", numB="15:28"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 34), datetime(1970, 1, 1, 15, 29), True, False))) - self._test("{numA} <= {predicate} < {numB}".format(predicate='time', numA="12:34", numB="15:28"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 34), datetime(1970, 1, 1, 15, 28), True, False))) - self._test("{numA} < {predicate} <= {numB}".format(predicate='time', numA="12:34", numB="15:28"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 35), datetime(1970, 1, 1, 15, 29), True, False))) - self._test("{numA} < {predicate} < {numB}".format(predicate='time', numA="12:34", numB="15:28"), - ('time', ast.TimeRange(datetime(1970, 1, 1, 12, 35), datetime(1970, 1, 1, 15, 28), True, False))) - - def test_special(self): - # special cases: explicit plus sign - self._test("{predicate} in [+1.23-+4.56]".format(predicate='iso'), - ('iso', ast.Continuous(1.23, 4.56, True, True))) - self._test("{predicate} in [-+4.56]".format(predicate='iso'), - ('iso', ast.Continuous(float('-inf'), 4.56, False, True))) - - def test_errors(self): - # parse errors - for editable in [ - # equal with exclusive - "{predicate} in ({num}:{num})", "{predicate} in ({num}-{num})", - "{predicate} in ({num}:{num}[", "{predicate} in ({num}-{num}[", - "{predicate} in ]{num}:{num})", "{predicate} in ]{num}-{num})", - "{predicate} in ]{num}:{num}[", "{predicate} in ]{num}-{num}[", - # invalid parentesis - "{predicate} in ){num}:{num}(", - # misc errors - # FIXME: Currently all special characters are allowed as categorical value. - # If this changes, don't forget to enable the tests below. - #"{predicate} in [{num}{num}]", - #"{predicate} [{num}:{num}:{num}]", - #"{predicate} = ({num})", - #"{predicate} = {num})", - ]: - self.assertRaises(errors.ParserError, ast_from_string, - editable.format(predicate='iso', num=1.23)) - - for editable in [ - "{predicate} in [{numA}:{numB}]", "{predicate} : [{numA}:{numB}]", "{predicate} = [{numA}:{numB}]", - "{predicate} in ]{numA}:{numB}]", "{predicate} : ]{numA}:{numB}]", "{predicate} = ]{numA}:{numB}]", - "{predicate} in [{numA}:{numB}[", "{predicate} : [{numA}:{numB}[", "{predicate} = [{numA}:{numB}[", - "{predicate} in ({numA}:{numB}]", "{predicate} : ({numA}:{numB}]", "{predicate} = ({numA}:{numB}]", - "{predicate} in [{numA}:{numB})", "{predicate} : [{numA}:{numB})", "{predicate} = [{numA}:{numB})", - "{predicate} in ]{numA}:{numB}[", "{predicate} : ]{numA}:{numB}[", "{predicate} = ]{numA}:{numB}[", - "{predicate} in ]{numA}:{numB})", "{predicate} : ]{numA}:{numB})", "{predicate} = ]{numA}:{numB})", - "{predicate} in ({numA}:{numB}[", "{predicate} : ({numA}:{numB}[", "{predicate} = ({numA}:{numB}[", - "{predicate} in ({numA}:{numB})", "{predicate} : ({numA}:{numB})", "{predicate} = ({numA}:{numB})", - "{numA} < {predicate} < {numB}", - "{numA} <= {predicate} < {numB}", - "{numA} < {predicate} <= {numB}", - ]: - self.assertRaises(errors.ParserError, ast_from_string, - editable.format(predicate='iso', numA=4.56, numB=1.23)) - self.assertRaises(errors.ParserError, ast_from_string, - editable.format(predicate='time', numA="17:35", numB="10:55")) - self.assertRaises(errors.ParserError, ast_from_string, - editable.format(predicate='time', numA="18.12.2035", numB="5.7.1999")) - - # special cases: empty range with boundary - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} in [:]".format(predicate='iso')) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} in (:[".format(predicate='iso')) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} in ]:)".format(predicate='iso')) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} in ".format(predicate='iso')) - # misc - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} in [{num}{num}]".format(predicate='iso', num=1.23)) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} [{num}:{num}:{num}]".format(predicate='iso', num=1.23)) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} = ({num})".format(predicate='iso', num=1.23)) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} = ({num}".format(predicate='iso', num=1.23), dict(parseAll=True)) - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, - "{predicate} = {num})".format(predicate='iso', num=1.23), dict(parseAll=True)) - # range errors - self.assertRaises(errors.ParserError, ast_from_string, "100 >= iso < 200") - self.assertRaises(errors.ParserError, ast_from_string, "100 > iso < 200") - self.assertRaises(errors.ParserError, ast_from_string, "100 > iso <= 200") - self.assertRaises(errors.ParserError, ast_from_string, "100 >= iso <= 200") - self.assertRaises(errors.ParserError, ast_from_string, "100 = iso = 200") - # time/date mixture errors - self.assertRaises(errors.ParserError, ast_from_string, "12:45 < time < 17.5.2004") - self.assertRaises(errors.ParserError, ast_from_string, "17.5.2004 < time < 12:45") - # date/int mixture errors - self.assertRaises(errors.ParserError, ast_from_string, "17.5.2004 < time < 1245") - # 1245 is interpreted as the year - #self.assertRaises(errors.ParserError, ast_from_string, "1245 < time < 17.5.2004") - # time/int mixture errors - self.assertRaises(errors.ParserError, ast_from_string, "17:12 < time < 1245") - self.assertRaises(errors.ParserError, ast_from_string, "1712 < time < 12:45") - - # empty query - self.assertRaises(ParseException, ast_from_string.CONTINUOUS.parseString, "") - - -class TestParseSearch(unittest.TestCase): - def setUp(self): - predicates.expose('mime', - TestScope('attribute', 'mime'), 'Categorical') - predicates.expose('rank', - TestScope('attribute', 'rank'), 'Continuous') - predicates.expose('iso', - TestScope('attribute', 'iso'), 'Continuous', 'Categorical') - predicates.expose('time', - TestScope('generic', 't_image_create_loc'), 'TimeRange', 'Datetime') - predicates.expose('tag', - TestScope('generic', 'tag'), 'Categorical') - - def test_parse_existence(self): - self.assertEqual(ast_from_string("has mime"), - ast.AND([ast.Token('mime', ast.Existence())])) - self.assertEqual(ast_from_string("has no mime"), - ast.AND([ast.Token('mime', ast.Inexistence())])) - self.assertEqual(ast_from_string("has not mime"), - ast.AND([ast.Token('mime', ast.Inexistence())])) - - def test_parse_categorical(self): - # positive - self.assertEqual(ast_from_string("iso in 100, 200, 500"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200', '500']))])) - self.assertEqual(ast_from_string("iso in (100, 200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso = (100, 200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - # FIXME! - #self.assertEqual(ast_from_string("iso = 100, 200"), - # ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso : (100, 200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso : 100, 200"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso:(100,200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso in (100,200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso in 100,200"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso ~ (100,200)"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200'], approximate=True))])) - self.assertEqual(ast_from_string("iso ~ 100,200"), - ast.AND([ast.Token('iso', ast.SetInclude(['100', '200'], approximate=True))])) - - # negative - self.assertEqual(ast_from_string("iso not in 100,200"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso not in (100, 200)"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso != 100,200"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso != (100, 200)"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200']))])) - self.assertEqual(ast_from_string("iso !~ 100,200"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200'], approximate=True))])) - self.assertEqual(ast_from_string("iso !~ (100, 200)"), - ast.AND([ast.Token('iso', ast.SetExclude(['100', '200'], approximate=True))])) - - # one value - self.assertEqual(ast_from_string("mime : text"), - ast.AND([ast.Token('mime', ast.SetInclude(['text']))])) - self.assertEqual(ast_from_string("mime in text"), - ast.AND([ast.Token('mime', ast.SetInclude(['text']))])) - self.assertEqual(ast_from_string("mime = text"), - ast.AND([ast.Token('mime', ast.SetInclude(['text']))])) - self.assertEqual(ast_from_string("mime ~ text"), - ast.AND([ast.Token('mime', ast.SetInclude(['text'], approximate=True))])) - self.assertEqual(ast_from_string("mime != text"), - ast.AND([ast.Token('mime', ast.SetExclude(['text']))])) - self.assertEqual(ast_from_string("mime not in text"), - ast.AND([ast.Token('mime', ast.SetExclude(['text']))])) - self.assertEqual(ast_from_string("mime !~ text"), - ast.AND([ast.Token('mime', ast.SetExclude(['text'], approximate=True))])) - - # expressions with slash and comma - self.assertEqual(ast_from_string('mime : "text"'), - ast.AND([ast.Token('mime', ast.SetInclude(['text']))])) - self.assertEqual(ast_from_string('mime : "text", "plain"'), - ast.AND([ast.Token('mime', ast.SetInclude(['text', 'plain']))])) - self.assertEqual(ast_from_string('mime : "text, plain"'), - ast.AND([ast.Token('mime', ast.SetInclude(['text, plain']))])) - self.assertEqual(ast_from_string('mime ~ "text/plain"'), - ast.AND([ast.Token('mime', ast.SetInclude(['text/plain'], approximate=True))])) - self.assertEqual(ast_from_string('mime = ("text/plain", "image/jpeg")'), - ast.AND([ast.Token('mime', ast.SetInclude(['text/plain', 'image/jpeg']))])) - - def test_parse_tag(self): - - # only tag: tag, tags, (tag), (tags) - self.assertEqual(ast_from_string("foo"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo']))])) - self.assertEqual(ast_from_string("(foo)"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo']))])) - self.assertEqual(ast_from_string("foo, bar"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar']))])) - self.assertEqual(ast_from_string("foo,bar"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar']))])) - self.assertEqual(ast_from_string("(foo, bar,foobar)"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar', 'foobar']))])) - - # op and tag: !tag, ~tag, !~tag - self.assertEqual(ast_from_string("~foo"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo'], approximate=True))])) - self.assertEqual(ast_from_string("~ foo"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo'], approximate=True))])) - self.assertEqual(ast_from_string("!foo"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo']))])) - self.assertEqual(ast_from_string("! foo"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo']))])) - self.assertEqual(ast_from_string("!~foo"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo'], approximate=True))])) - self.assertEqual(ast_from_string("!~ foo"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo'], approximate=True))])) - - # op and list: ! (tags), ~tags, ... - self.assertEqual(ast_from_string("~ foo, bar"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar'], approximate=True))])) - self.assertEqual(ast_from_string("~foo, bar"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar'], approximate=True))])) - self.assertEqual(ast_from_string("~ (foo, bar)"), - ast.AND([ast.Token('tag', ast.SetInclude(['foo', 'bar'], approximate=True))])) - self.assertEqual(ast_from_string("! foo, bar"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar']))])) - self.assertEqual(ast_from_string("! (foo, bar)"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar']))])) - self.assertEqual(ast_from_string("! (foo,bar)"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar']))])) - self.assertEqual(ast_from_string("!~ foo, bar"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar'], approximate=True))])) - self.assertEqual(ast_from_string("!~ (foo, bar)"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar'], approximate=True))])) - self.assertEqual(ast_from_string("!~(foo,bar)"), - ast.AND([ast.Token('tag', ast.SetExclude(['foo', 'bar'], approximate=True))])) - - def test_parse_query(self): - # simple query - self.assertEqual(ast_from_string('foo / bar'), ast.AND([ - ast.Token('tag', ast.SetInclude('foo')), - ast.Token('tag', ast.SetInclude('bar'))])) - self.assertEqual(ast_from_string('iso in ("foo", "bar") / mime = plain'), ast.AND([ - ast.Token('iso', ast.SetInclude('foo', 'bar')), - ast.Token('mime', ast.SetInclude('plain'))])) - self.assertEqual(ast_from_string('iso in ("foo", "bar") / mime = plain'), ast.AND([ - ast.Token('iso', ast.SetInclude('foo', 'bar')), - ast.Token('mime', ast.SetInclude('plain'))])) - self.assertEqual(ast_from_string('iso = 1.23 / rank < 5'), ast.AND([ - ast.Token('iso', ast.Continuous(1.23, 1.23, True, True)), - ast.Token('rank', ast.Continuous(hi=5))])) - self.assertEqual(ast_from_string('time >= 12:50 / time < 13:50'), ast.AND([ - ast.Token('time', ast.TimeRange(lo=datetime(1970, 1, 1, 12, 50), lo_inc=True, hi_inc=True)), - ast.Token('time', ast.TimeRange(hi=datetime(1970, 1, 1, 13, 50), lo_inc=True, hi_inc=False))])) - self.assertEqual(ast_from_string('time >= 17.5.2001 / time < 18.4.2002'), ast.AND([ - ast.Token('time', ast.Datetime(lo=datetime(2001, 5, 17, 0, 0), lo_inc=True)), - ast.Token('time', ast.Datetime(hi=datetime(2002, 4, 18, 0, 0)))])) - # mixing expressions - self.assertEqual(ast_from_string('foo / iso in "bar" / mime ~ "text/plain" / iso < 100 / time >= 17.5.2001 / time < 13:50'), ast.AND([ - ast.Token('tag', ast.SetInclude('foo')), - ast.Token('iso', ast.SetInclude('bar')), - ast.Token('mime', ast.SetInclude('text/plain', approximate=True)), - ast.Token('iso', ast.Continuous(hi=100)), - ast.Token('time', ast.Datetime(lo=datetime(2001, 5, 17, 0, 0), lo_inc=True)), - ast.Token('time', ast.TimeRange(hi=datetime(1970, 1, 1, 13, 50), lo_inc=True))])) - - # leading/trailing slashes - self.assertRaises(errors.ParserError, ast_from_string, '/ foobar') - self.assertRaises(errors.ParserError, ast_from_string, 'foobar /') - self.assertRaises(errors.ParserError, ast_from_string, 'foobar / ') - self.assertRaises(errors.ParserError, ast_from_string, 'foo // bar') - self.assertRaises(errors.ParserError, ast_from_string, 'foo / / bar') - - def test_quoting(self): - self.assertEqual(ast_from_string("tag in ('(foo, bar)', foobar)"), - ast.AND([ast.Token('tag', ast.SetInclude(['(foo, bar)', 'foobar']))])) - self.assertEqual(ast_from_string('tag in ("(foo, bar)", foobar)'), - ast.AND([ast.Token('tag', ast.SetInclude(['(foo, bar)', 'foobar']))])) - self.assertEqual(ast_from_string('tag in ("(foo, \\"bar\\")", foobar)'), - ast.AND([ast.Token('tag', ast.SetInclude(['(foo, "bar")', 'foobar']))])) - self.assertEqual(ast_from_string('tag in ("(foo, bar)", "foobar")'), - ast.AND([ast.Token('tag', ast.SetInclude(['(foo, bar)', 'foobar']))])) - self.assertEqual(ast_from_string('tag in ("(foo, bar)", \'foobar\')'), - ast.AND([ast.Token('tag', ast.SetInclude(['(foo, bar)', 'foobar']))])) - - # error cases - self.assertRaises(errors.ParserError, ast_from_string, ('tag in ("(foo, bar, foobar)')) - self.assertRaises(errors.ParserError, ast_from_string, ("tag in ('(foo, bar, foobar)")) - - -## main ## - -if __name__ == '__main__': - unittest.main() - -## EOF ## |