Skip to content

Commit

Permalink
Add tests for XPathContext to increase coverage
Browse files Browse the repository at this point in the history
  - Removed circularity checks that not works with ElementTree and
    is not necessary for lxml.etree
  • Loading branch information
brunato committed May 22, 2019
1 parent 2935eac commit fea7c0d
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 47 deletions.
37 changes: 19 additions & 18 deletions elementpath/xpath_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
#
import datetime

from .exceptions import ElementPathTypeError, ElementPathValueError
from .xpath_helpers import AttributeNode, is_etree_element, is_element_node, is_document_node, is_attribute_node
from .exceptions import ElementPathTypeError
from .xpath_helpers import AttributeNode, is_etree_element, is_element_node, \
is_document_node, is_attribute_node


class XPathContext(object):
Expand All @@ -38,7 +39,7 @@ def __init__(self, root, item=None, position=0, size=1, axis=None, variables=Non
current_dt=None, timezone=None):
if not is_element_node(root) and not is_document_node(root):
raise ElementPathTypeError("argument 'root' must be an Element: %r" % root)
self.root = root
self._root = root
if item is not None:
self.item = item
elif is_element_node(root):
Expand All @@ -56,12 +57,12 @@ def __init__(self, root, item=None, position=0, size=1, axis=None, variables=Non

def __repr__(self):
return '%s(root=%r, item=%r, position=%r, size=%r, axis=%r)' % (
self.__class__.__name__, self.root, self.item, self.position, self.size, self.axis
self.__class__.__name__, self._root, self.item, self.position, self.size, self.axis
)

def copy(self, clear_axis=True):
obj = type(self)(
root=self.root,
root=self._root,
item=self.item,
position=self.position,
size=self.size,
Expand All @@ -73,10 +74,14 @@ def copy(self, clear_axis=True):
obj._parent_map = self._parent_map
return obj

@property
def root(self):
return self._root

@property
def parent_map(self):
if self._parent_map is None:
self._parent_map = {child: elem for elem in self.root.iter() for child in elem}
self._parent_map = {child: elem for elem in self._root.iter() for child in elem}
return self._parent_map

def is_principal_node_kind(self):
Expand Down Expand Up @@ -118,7 +123,7 @@ def iter_children_or_self(self, item=None, child_axis=False):

if self.item is None:
self.size, self.position = 1, 0
self.item = self.root.getroot() if is_document_node(self.root) else self.root
self.item = self._root.getroot() if is_document_node(self._root) else self._root
yield self.item
elif is_element_node(self.item):
elem = self.item
Expand Down Expand Up @@ -151,8 +156,8 @@ def iter_descendants(self, item=None, axis=None):

if self.item is None:
self.size, self.position = 1, 0
yield self.root
self.item = self.root.getroot() if is_document_node(self.root) else self.root
yield self._root
self.item = self._root.getroot() if is_document_node(self._root) else self._root
elif not is_etree_element(self.item):
return

Expand All @@ -179,21 +184,17 @@ def iter_ancestors(self, item=None, axis=None):

if item is not None:
self.item = item

if not is_etree_element(self.item):
return
elem = self.item
parent_map = self.parent_map

while True:
try:
parent = parent_map[self.item]
parent = self.parent_map[self.item]
except KeyError:
break
else:
if parent is elem:
raise ElementPathValueError("not an Element tree, circularity found for %r." % elem)
self.item = parent
yield self.item
yield parent

self.item, self.size, self.position, self.axis = status

Expand All @@ -203,8 +204,8 @@ def iter(self, axis=None):

if self.item is None:
self.size, self.position = 1, 0
yield self.root
self.item = self.root.getroot() if is_document_node(self.root) else self.root
yield self._root
self.item = self._root.getroot() if is_document_node(self._root) else self._root
elif not is_etree_element(self.item):
return

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
author='Davide Brunato',
author_email='brunato@sissa.it',
url='https://github.com/brunato/elementpath',
keywords=['XPath', 'XPath2', 'Pratt-parser', 'ElementTree', 'lxml'],
license='MIT',
description='XPath 1.0/2.0 parsers and selectors for ElementTree and lxml',
long_description=long_description,
Expand Down
83 changes: 83 additions & 0 deletions tests/test_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c), 2018-2019, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
import unittest
import xml.etree.ElementTree as ElementTree

from elementpath import *


class XPathContextTest(unittest.TestCase):
root = ElementTree.XML('<author>Dickens</author>')

def test_initialization(self):
self.assertRaises(TypeError, XPathContext, None)

def test_repr(self):
self.assertEqual(
repr(XPathContext(self.root)),
"XPathContext(root={0}, item={0}, position=0, size=1, axis=None)".format(self.root)
)

def test_parent_map(self):
root = ElementTree.XML('<A><B1/><B2/></A>')
context = XPathContext(root)
self.assertEqual(context.parent_map, {root[0]: root, root[1]: root})

root = ElementTree.XML('<A><B1><C1/></B1><B2/><B3><C1/><C2/></B3></A>')
context = XPathContext(root)
self.assertEqual(context.parent_map, {
root[0]: root, root[0][0]: root[0], root[1]: root,
root[2]: root, root[2][0]: root[2], root[2][1]: root[2]
})

def test_iter_attributes(self):
root = ElementTree.XML('<A a1="10" a2="20"/>')
context = XPathContext(root)
self.assertListEqual(
sorted(list(context.iter_attributes()), key=lambda x: x[0]),
[AttributeNode(name='a1', value='10'), AttributeNode(name='a2', value='20')]
)

context.item = None
self.assertListEqual(list(context.iter_attributes()), [])

def test_iter_parent(self):
root = ElementTree.XML('<A a1="10" a2="20"/>')
context = XPathContext(root, item=None)
self.assertListEqual(list(context.iter_parent()), [])

def test_iter_descendants(self):
root = ElementTree.XML('<A a1="10" a2="20"><B1/><B2/></A>')
attr = AttributeNode('a1', '10')
self.assertListEqual(list(XPathContext(root).iter_descendants()), [root, root[0], root[1]])
self.assertListEqual(list(XPathContext(root, item=attr).iter_descendants()), [])

def test_iter_ancestors(self):
root = ElementTree.XML('<A a1="10" a2="20"><B1/><B2/></A>')
attr = AttributeNode('a1', '10')
self.assertListEqual(list(XPathContext(root).iter_ancestors()), [])
self.assertListEqual(list(XPathContext(root, item=root[1]).iter_ancestors()), [root])
self.assertListEqual(list(XPathContext(root).iter_ancestors(item=root[1])), [root])
self.assertListEqual(list(XPathContext(root, item=attr).iter_ancestors()), [])

def test_iter(self):
root = ElementTree.XML('<A><B1><C1/></B1><B2/><B3><C1/><C2/></B3></A>')
context = XPathContext(root)
self.assertListEqual(list(context.iter()), list(root.iter()))
context.item = None
self.assertListEqual(list(context.iter()), [root] + list(root.iter()))
context.item = AttributeNode('a1', '10')
self.assertListEqual(list(context.iter()), [])


if __name__ == '__main__':
unittest.main()
26 changes: 0 additions & 26 deletions tests/test_contexts.py

This file was deleted.

6 changes: 3 additions & 3 deletions tests/test_elementpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
try:
from tests.test_helpers import ExceptionHelpersTest, NamespaceHelpersTest, XPathHelpersTest
from tests.test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
from tests.test_contexts import XPathContextTest
from tests.test_context import XPathContextTest
from tests.test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
from tests.test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
from tests.test_schema_proxy import XPath2ParserXMLSchemaTest, LxmlXPath2ParserXMLSchemaTest
Expand All @@ -36,11 +36,11 @@
# Python 2 fallback
from test_helpers import ExceptionHelpersTest, NamespaceHelpersTest, XPathHelpersTest
from test_datatypes import UntypedAtomicTest, DateTimeTypesTest, DurationTypesTest, TimezoneTypeTest
from test_contexts import XPathContextTest
from test_context import XPathContextTest
from test_xpath1_parser import XPath1ParserTest, LxmlXPath1ParserTest
from test_xpath2_parser import XPath2ParserTest, LxmlXPath2ParserTest
from test_schema_proxy import XPath2ParserXMLSchemaTest, LxmlXPath2ParserXMLSchemaTest
from test_selectors import SelectorTest
from test_selectors import XPathSelectorsTest
from test_package import PackageTest

unittest.main()

0 comments on commit fea7c0d

Please sign in to comment.