Skip to content

Commit

Permalink
Add tests for numerical operators with various types
Browse files Browse the repository at this point in the history
  • Loading branch information
brunato committed Jan 9, 2021
1 parent f69e95d commit a929150
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 41 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
CHANGELOG
*********

`v2.1.2`_ (2021-01-xx)
======================
* Extend tests for XPath 1.0/2.0 with minor fixes

`v2.1.1`_ (2021-01-06)
======================
* Fix for issue #32 (test failure on missing locale setting)
Expand Down Expand Up @@ -273,3 +277,4 @@ CHANGELOG
.. _v2.0.5: https://github.com/sissaschool/elementpath/compare/v2.0.4...v2.0.5
.. _v2.1.0: https://github.com/sissaschool/elementpath/compare/v2.0.5...v2.1.0
.. _v2.1.1: https://github.com/sissaschool/elementpath/compare/v2.1.0...v2.1.1
.. _v2.1.2: https://github.com/sissaschool/elementpath/compare/v2.1.1...v2.1.2
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
# The short X.Y version
version = '2.1'
# The full version, including alpha/beta/rc tags
release = '2.1.1'
release = '2.1.2'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion elementpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# @author Davide Brunato <brunato@sissa.it>
#
__version__ = '2.1.1'
__version__ = '2.1.2'
__author__ = "Davide Brunato"
__contact__ = "brunato@sissa.it"
__copyright__ = "Copyright 2018-2021, SISSA"
Expand Down
39 changes: 14 additions & 25 deletions elementpath/xpath1/xpath1_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,6 @@ def led(self, left):

self[:] = left, self.parser.expression(90)
self.value = '{}:{}'.format(self[0].value, self[1].value)

if self[1].symbol == ':':
raise self.wrong_syntax('{!r} is not a QName'.format(self.source))
return self


Expand All @@ -547,13 +544,7 @@ def select(self, context=None):
if self[0].value == '*':
name = '*:%s' % self[1].value
else:
try:
namespace = self.get_namespace(self[0].value)
except ElementPathKeyError:
msg = "prefix {!r} has not been declared".format(self[0].value)
raise self.error('XPST0081', msg) from None
else:
name = '{%s}%s' % (namespace, self[1].value)
name = '{%s}%s' % (self.get_namespace(self[0].value), self[1].value)

if context is None:
yield name
Expand Down Expand Up @@ -798,17 +789,15 @@ def evaluate(self, context=None):
if op1 is not None:
try:
return op1 + op2
except ValueError as err:
raise self.error('FORG0001', err) from None
except TypeError as err:
raise self.error('XPTY0004', err)
raise self.error('XPTY0004', err) from None
except OverflowError as err:
if isinstance(op1, AbstractDateTime):
raise self.error('FODT0001', err)
raise self.error('FODT0001', err) from None
elif isinstance(op1, Duration):
raise self.error('FODT0002', err)
raise self.error('FODT0002', err) from None
else:
raise self.error('FOAR0002', err)
raise self.error('FOAR0002', err) from None


@method(infix('-', bp=40))
Expand All @@ -826,11 +815,11 @@ def evaluate(self, context=None):
raise self.error('XPTY0004', err) from None
except OverflowError as err:
if isinstance(op1, AbstractDateTime):
raise self.error('FODT0001', err)
raise self.error('FODT0001', err) from None
elif isinstance(op1, Duration):
raise self.error('FODT0002', err)
raise self.error('FODT0002', err) from None
else:
raise self.error('FOAR0002', err)
raise self.error('FOAR0002', err) from None


@method(infix('*', bp=45))
Expand All @@ -843,17 +832,17 @@ def evaluate(self, context=None):
return op2 * op1
return op1 * op2
except TypeError as err:
if isinstance(op1, float):
if isinstance(op1, (float, decimal.Decimal)):
if math.isnan(op1):
raise self.error('FOCA0005', err) from None
raise self.error('FOCA0005') from None
elif math.isinf(op1):
raise self.error('FODT0002', err) from None
raise self.error('FODT0002') from None

if isinstance(op2, float):
if isinstance(op2, (float, decimal.Decimal)):
if math.isnan(op2):
raise self.error('FOCA0005', err) from None
raise self.error('FOCA0005') from None
elif math.isinf(op2):
raise self.error('FODT0002', err) from None
raise self.error('FODT0002') from None

raise self.error('XPTY0004', err) from None
except ValueError as err:
Expand Down
4 changes: 2 additions & 2 deletions publiccode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2'
name: elementpath
url: 'https://github.com/sissaschool/elementpath'
landingURL: 'https://github.com/sissaschool/elementpath'
releaseDate: '2020-01-06'
softwareVersion: v2.1.1
releaseDate: '2020-01-xx'
softwareVersion: v2.1.2
developmentStatus: stable
platforms:
- linux
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

setup(
name='elementpath',
version='2.1.1',
version='2.1.2',
packages=find_packages(include=['elementpath', 'elementpath.*']),
author='Davide Brunato',
author_email='brunato@sissa.it',
Expand Down
45 changes: 39 additions & 6 deletions tests/test_xpath1_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import math
import pickle
from decimal import Decimal
from typing import Optional, List, Tuple
from xml.etree import ElementTree

try:
Expand Down Expand Up @@ -303,9 +304,23 @@ def test_is_instance_method(self):
self.parser.schema = None

def test_check_variables_method(self):
self.assertIsNone(self.parser.check_variables(
{'values': [1, 2, -1], 'myaddress': 'info@example.com', 'word': ''}
))
self.assertIsNone(self.parser.check_variables({
'values': [1, 2, -1],
'myaddress': 'info@example.com',
'word': ''
}))

with self.assertRaises(TypeError) as ctx:
self.parser.check_variables({'values': [None, 2, -1]})
error_message = str(ctx.exception)
self.assertIn('XPDY0050', error_message)
self.assertIn('Unmatched sequence type', error_message)

with self.assertRaises(TypeError) as ctx:
self.parser.check_variables({'other': None})
error_message = str(ctx.exception)
self.assertIn('XPDY0050', error_message)
self.assertIn('Unmatched sequence type', error_message)

# XPath expression tests
def test_node_selection(self):
Expand Down Expand Up @@ -366,6 +381,7 @@ def test_qname_uri_references(self):
self.wrong_syntax("{%s" % XSD_NAMESPACE)
self.wrong_syntax("{%s}1" % XSD_NAMESPACE)
self.check_value("{%s}true()" % XPATH_FUNCTIONS_NAMESPACE, True)
self.check_value("string({%s}true())" % XPATH_FUNCTIONS_NAMESPACE, 'true')

name = '{%s}alpha' % XPATH_FUNCTIONS_NAMESPACE
self.check_value(name, name) # it's not an error to use 'fn' namespace for a name
Expand Down Expand Up @@ -436,6 +452,9 @@ def test_node_types(self):
context.item = 1
self.check_value("self::node()", expected=[], context=context)

def test_unknown_function(self):
self.wrong_type("unknown('5')", 'XPST0017', 'unknown function')

def test_node_set_id_function(self):
# XPath 1.0 id() function: https://www.w3.org/TR/1999/REC-xpath-19991116/#function-id
root = self.etree.XML('<A><B1 xml:id="foo"/><B2/><B3 xml:id="bar"/><B4 xml:id="baz"/></A>')
Expand Down Expand Up @@ -918,13 +937,16 @@ def test_comparison_operators(self):
self.check_value("5 > 3", True)
self.check_value("5 < 20.0", True)
self.check_value("2 * 2 = 4", True)
self.wrong_syntax("5 > 3 < 4", "unexpected '<' operator")

if self.parser.version == '1.0':
self.check_value("false() = 1", False)
self.check_value("0 = false()", True)
else:
self.wrong_type("false() = 1")
self.wrong_type("0 = false()")
self.wrong_value('xs:untypedAtomic("1") = xs:dayTimeDuration("PT1S")',
'FORG0001', "'1' is not an xs:duration value")

def test_comparison_of_sequences(self):
root = self.etree.XML('<table>'
Expand Down Expand Up @@ -979,6 +1001,10 @@ def test_numerical_add_operator(self):
self.check_value("/values/b + 2", ValueError, context=XPathContext(root))
self.wrong_type("+'alpha'")
self.wrong_type("3 + 'alpha'")
self.check_value("() + 81")
self.check_value("72 + ()")
self.check_value("+()")
self.wrong_type('xs:dayTimeDuration("P1D") + xs:duration("P6M")', 'XPTY0004')

self.check_selector("/values/d + 3", root, 47)

Expand All @@ -999,6 +1025,10 @@ def test_numerical_sub_operator(self):
self.check_value("/values/b - 2", ValueError, context=XPathContext(root))
self.wrong_type("-'alpha'")
self.wrong_type("3 - 'alpha'")
self.check_value("() - 6")
self.check_value("19 - ()")
self.check_value("-()")
self.wrong_type('xs:duration("P3Y") - xs:yearMonthDuration("P2Y3M")', 'XPTY0004')

self.check_selector("/values/d - 3", root, 41)

Expand Down Expand Up @@ -1331,6 +1361,8 @@ def test_parent_shortcut_and_axis(self):
self.check_selector('/A/*/C2/parent::node()', root, [root[2]])
self.check_selector('/A/*/*/parent::node()', root, [root[0], root[2], root[3]])
self.check_selector('//C2/parent::node()', root, [root[2]])

self.check_selector('..', self.etree.ElementTree(root), [])
self.check_value('..', MissingContextError)
self.check_value('parent::*', MissingContextError)

Expand Down Expand Up @@ -1540,11 +1572,12 @@ def check_selector(self, path, root, expected, namespaces=None, **kwargs):

def test_namespace_axis(self):
root = self.etree.XML('<A xmlns:tst="http://xpath.test/ns"><tst:B1/></A>')
namespaces = list(self.parser.DEFAULT_NAMESPACES.items()) \
+ [('tst', 'http://xpath.test/ns')]
namespaces: List[Tuple[Optional[str], str]] = []
namespaces.extend(self.parser.DEFAULT_NAMESPACES.items())
namespaces += [('tst', 'http://xpath.test/ns')]

self.check_selector('/A/namespace::*', root, expected=set(namespaces),
namespaces=namespaces[-1:])

self.check_selector('/A/namespace::*', root, expected=set(namespaces))

root = self.etree.XML('<tst:A xmlns:tst="http://xpath.test/ns" '
Expand Down
3 changes: 3 additions & 0 deletions tests/test_xpath2_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@

class XPath2ConstructorsTest(xpath_test_class.XPathTestCase):

def test_unknown_constructor(self):
self.wrong_type("xs:unknown('5')", 'XPST0017', 'unknown constructor function')

def test_string_constructor(self):
self.check_value("xs:string(5.0)", '5')
self.check_value("xs:string(5.2)", '5.2')
Expand Down
1 change: 1 addition & 0 deletions tests/test_xpath2_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ def test_empty_function(self):
self.check_value('fn:empty(("hello", "world"))', False)
self.check_value('fn:empty(fn:remove(("hello", "world"), 1))', False)
self.check_value('fn:empty(())', True)
self.check_value("empty(() * ())", True)
self.check_value('fn:empty(fn:remove(("hello"), 1))', True)
self.check_value('fn:empty((xs:double("0")))', False)

Expand Down
20 changes: 15 additions & 5 deletions tests/test_xpath2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,6 @@ def test_for_expressions(self):
root[2][0], root[2][2], root[2][0], root[2][3], root[2][0]]
)

def test_numerical_add_operator(self):
super(XPath2ParserTest, self).test_numerical_add_operator()
self.check_value("() + 81")
self.check_value("72 + ()")

def test_idiv_operator(self):
self.check_value("5 idiv 2", 2)
self.check_value("-3.5 idiv -2", 1)
Expand All @@ -369,6 +364,8 @@ def test_comparison_operators(self):
self.check_value("false() eq 1", False)
self.check_value("0 eq false()", True)
self.check_value("2 * 2 eq 4", True)
self.check_value("() * 7")
self.check_value("() * ()")

self.check_value("() le 4")
self.check_value("4 gt ()")
Expand Down Expand Up @@ -607,6 +604,14 @@ def test_subtract_day_time_duration_to_time(self):
self.check_value('xs:time("08:20:00-05:00") - xs:dayTimeDuration("P23DT10H10M")',
Time.fromstring('22:10:00-05:00'))

def test_duration_with_arithmetical_operators(self):
self.wrong_type('xs:duration("P1Y") * 3', 'XPTY0004', 'unsupported operand type(s)')
self.wrong_value('xs:duration("P1Y") * xs:float("NaN")', 'FOCA0005')
self.check_value('xs:duration("P1Y") * xs:float("INF")', OverflowError)
self.wrong_value('xs:float("NaN") * xs:duration("P1Y")', 'FOCA0005')
self.check_value('xs:float("INF") * xs:duration("P1Y")', OverflowError)
self.wrong_type('xs:duration("P3Y") div 3', 'XPTY0004', 'unsupported operand type(s)')

def test_year_month_duration_operators(self):
self.check_value('xs:yearMonthDuration("P2Y11M") + xs:yearMonthDuration("P3Y3M")',
YearMonthDuration(months=74))
Expand All @@ -617,6 +622,9 @@ def test_year_month_duration_operators(self):
self.check_value('xs:yearMonthDuration("P2Y11M") div 1.5',
YearMonthDuration.fromstring('P1Y11M'))
self.check_value('xs:yearMonthDuration("P3Y4M") div xs:yearMonthDuration("-P1Y4M")', -2.5)
self.wrong_value('xs:double("NaN") * xs:yearMonthDuration("P2Y")', 'FOCA0005')
self.check_value('xs:yearMonthDuration("P1Y") * xs:double("INF")', OverflowError)
self.wrong_value('xs:yearMonthDuration("P3Y") div xs:double("NaN")', 'FOCA0005')

def test_day_time_duration_operators(self):
self.check_value('xs:dayTimeDuration("P2DT12H5M") + xs:dayTimeDuration("P5DT12H")',
Expand All @@ -627,6 +635,8 @@ def test_day_time_duration_operators(self):
DayTimeDuration.fromstring('PT4H33M'))
self.check_value('xs:dayTimeDuration("P1DT2H30M10.5S") div 1.5',
DayTimeDuration.fromstring('PT17H40M7S'))
self.check_value('3 * xs:dayTimeDuration("P1D")',
DayTimeDuration.fromstring('P3D'))
self.check_value(
'xs:dayTimeDuration("P2DT53M11S") div xs:dayTimeDuration("P1DT10H")',
Decimal('1.437834967320261437908496732')
Expand Down

0 comments on commit a929150

Please sign in to comment.