[Apache v2] DualParserNode implementation 3/3 (#7376)

* DualParserNode, DualCommentNode and DualDirectiveNode implementations

* Add DualBlockNode

* DualBlockNode find_ methods

* Address review comments

* Address review comments

* Simplify isPass

* Add explanation to _create_matching_list pydoc

* Remove unnecessary conditional block

* Address review comments
This commit is contained in:
Joona Hoikkala
2019-09-23 16:27:48 -04:00
committed by ohemorange
parent c224340330
commit feacbe9671
4 changed files with 361 additions and 21 deletions
@@ -13,6 +13,11 @@ def assertEqual(first, second):
elif isinstance(first, interfaces.DirectiveNode):
assertEqualDirective(first, second)
# Do an extra interface implementation assertion, as the contents were
# already checked for BlockNode in the assertEqualDirective
if isinstance(first, interfaces.BlockNode):
assert isinstance(second, interfaces.BlockNode)
# Skip tests if filepath includes the pass value. This is done
# because filepath is variable of the base ParserNode interface, and
# unless the implementation is actually done, we cannot assume getting
@@ -55,6 +60,44 @@ def isPass(value): # pragma: no cover
"""Checks if the value is set to PASS"""
return PASS in value
def isPassDirective(block):
""" Checks if BlockNode or DirectiveNode should pass the assertion """
if isPass(block.name):
return True
if isPass(block.parameters): # pragma: no cover
return True
if isPass(block.filepath): # pragma: no cover
return True
return False
def isPassComment(comment):
""" Checks if CommentNode should pass the assertion """
if isPass(comment.comment):
return True
if isPass(comment.filepath): # pragma: no cover
return True
return False
def isPassNodeList(nodelist): # pragma: no cover
""" Checks if a ParserNode in the nodelist should pass the assertion,
this function is used for results of find_* methods. Unimplemented find_*
methods should return a sequence containing a single ParserNode instance
with assertion pass string."""
try:
node = nodelist[0]
except IndexError:
node = None
if not node: # pragma: no cover
return False
if isinstance(node, interfaces.DirectiveNode):
return isPassDirective(node)
return isPassComment(node)
def assertEqualSimple(first, second):
""" Simple assertion """
if not isPass(first) and not isPass(second):
+17 -12
View File
@@ -11,8 +11,7 @@ class AugeasParserNode(interfaces.ParserNode):
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
super(AugeasParserNode, self).__init__(**kwargs)
self.ancestor = ancestor
# self.filepath = filepath
self.filepath = assertions.PASS
self.filepath = filepath
self.dirty = dirty
self.metadata = metadata
@@ -27,9 +26,9 @@ class AugeasCommentNode(AugeasParserNode):
comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable
super(AugeasCommentNode, self).__init__(**kwargs)
# self.comment = comment
self.comment = assertions.PASS
self.comment = comment
def __eq__(self, other): # pragma: no cover
def __eq__(self, other):
if isinstance(other, self.__class__):
return (self.comment == other.comment and
self.filepath == other.filepath and
@@ -49,7 +48,7 @@ class AugeasDirectiveNode(AugeasParserNode):
self.parameters = parameters
self.enabled = enabled
def __eq__(self, other): # pragma: no cover
def __eq__(self, other):
if isinstance(other, self.__class__):
return (self.name == other.name and
self.filepath == other.filepath and
@@ -72,7 +71,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
super(AugeasBlockNode, self).__init__(**kwargs)
self.children = ()
def __eq__(self, other): # pragma: no cover
def __eq__(self, other):
if isinstance(other, self.__class__):
return (self.name == other.name and
self.filepath == other.filepath and
@@ -108,17 +107,23 @@ class AugeasBlockNode(AugeasDirectiveNode):
self.children += (new_comment,)
return new_comment
def find_blocks(self, name, exclude=True): # pragma: no cover
def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument
"""Recursive search of BlockNodes from the sequence of children"""
pass
return [AugeasBlockNode(name=assertions.PASS,
ancestor=self,
filepath=assertions.PASS)]
def find_directives(self, name, exclude=True): # pragma: no cover
def find_directives(self, name, exclude=True): # pylint: disable=unused-argument
"""Recursive search of DirectiveNodes from the sequence of children"""
pass
return [AugeasDirectiveNode(name=assertions.PASS,
ancestor=self,
filepath=assertions.PASS)]
def find_comments(self, comment, exact=False): # pragma: no cover
def find_comments(self, comment, exact=False): # pylint: disable=unused-argument
"""Recursive search of DirectiveNodes from the sequence of children"""
pass
return [AugeasCommentNode(comment=assertions.PASS,
ancestor=self,
filepath=assertions.PASS)]
def delete_child(self, child): # pragma: no cover
"""Deletes a ParserNode from the sequence of children"""
+54 -8
View File
@@ -94,6 +94,7 @@ class DualDirectiveNode(DualNodeBase):
self.secondary.set_parameters(parameters)
assertions.assertEqual(self.primary, self.secondary)
class DualBlockNode(DualNodeBase):
""" Dual parser implementation of BlockNode interface """
@@ -158,7 +159,7 @@ class DualBlockNode(DualNodeBase):
new_comment = DualCommentNode(primary=primary_new, secondary=secondary_new)
return new_comment
def _create_matching_list(self, primary_list, secondary_list): # pragma: no cover
def _create_matching_list(self, primary_list, secondary_list):
""" Matches the list of primary_list to a list of secondary_list and
returns a list of tuples. This is used to create results for find_
methods.
@@ -185,7 +186,7 @@ class DualBlockNode(DualNodeBase):
raise AssertionError("Could not find a matching node.")
return matched
def find_blocks(self, name, exclude=True): # pragma: no cover
def find_blocks(self, name, exclude=True):
"""
Performs a search for BlockNodes using both implementations and does simple
checks for results. This is built upon the assumption that unimplemented
@@ -193,9 +194,11 @@ class DualBlockNode(DualNodeBase):
After the assertion, it creates a list of newly created DualBlockNode
instances that encapsulate the pairs of returned BlockNode objects.
"""
pass
def find_directives(self, name, exclude=True): # pragma: no cover
return self._find_helper(DualBlockNode, "find_blocks", name,
exclude=exclude)
def find_directives(self, name, exclude=True):
"""
Performs a search for DirectiveNodes using both implementations and
checks the results. This is built upon the assumption that unimplemented
@@ -203,9 +206,11 @@ class DualBlockNode(DualNodeBase):
After the assertion, it creates a list of newly created DualDirectiveNode
instances that encapsulate the pairs of returned DirectiveNode objects.
"""
pass
def find_comments(self, comment, exact=False): # pragma: no cover
return self._find_helper(DualDirectiveNode, "find_directives", name,
exclude=exclude)
def find_comments(self, comment, exact=False):
"""
Performs a search for CommentNodes using both implementations and
checks the results. This is built upon the assumption that unimplemented
@@ -213,9 +218,50 @@ class DualBlockNode(DualNodeBase):
After the assertion, it creates a list of newly created DualCommentNode
instances that encapsulate the pairs of returned CommentNode objects.
"""
pass
def delete_child(self, child): # pragma: no cover
return self._find_helper(DualCommentNode, "find_comments", comment,
exact=exact)
def _find_helper(self, nodeclass, findfunc, search, **kwargs):
"""A helper for find_* functions. The function specific attributes should
be passed as keyword arguments.
:param interfaces.ParserNode nodeclass: The node class for results.
:param str findfunc: Name of the find function to call
:param str search: The search term
"""
primary_res = getattr(self.primary, findfunc)(search, **kwargs)
secondary_res = getattr(self.secondary, findfunc)(search, **kwargs)
# The order of search results for Augeas implementation cannot be
# assured.
pass_primary = assertions.isPassNodeList(primary_res)
pass_secondary = assertions.isPassNodeList(secondary_res)
new_nodes = list()
if pass_primary and pass_secondary:
# Both unimplemented
new_nodes.append(nodeclass(primary=primary_res[0],
secondary=secondary_res[0]))
elif pass_primary:
for c in secondary_res:
new_nodes.append(nodeclass(primary=primary_res[0],
secondary=c))
elif pass_secondary:
for c in primary_res:
new_nodes.append(nodeclass(primary=c,
secondary=secondary_res[0]))
else:
assert len(primary_res) == len(secondary_res)
matches = self._create_matching_list(primary_res, secondary_res)
for p, s in matches:
new_nodes.append(nodeclass(primary=p, secondary=s))
return new_nodes
def delete_child(self, child):
"""Deletes a child from the ParserNode implementations. The actual
ParserNode implementations are used here directly in order to be able
to match a child to the list of children."""
@@ -4,11 +4,12 @@ import unittest
import mock
from certbot_apache import assertions
from certbot_apache import augeasparser
from certbot_apache import dualparser
from certbot_apache import interfaces
class DualParserNodeTest(unittest.TestCase):
class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-methods
"""DualParserNode tests"""
def setUp(self): # pylint: disable=arguments-differ
@@ -153,3 +154,248 @@ class DualParserNodeTest(unittest.TestCase):
interfaces.CommentNode))
self.assertEqual(self.block.primary.children[0].ancestor,
self.block.primary)
def test_find_blocks(self):
dblks = self.block.find_blocks("block")
p_dblks = [d.primary for d in dblks]
s_dblks = [d.secondary for d in dblks]
p_blks = self.block.primary.find_blocks("block")
s_blks = self.block.secondary.find_blocks("block")
# Check that every block response is represented in the list of
# DualParserNode instances.
for p in p_dblks:
self.assertTrue(p in p_blks)
for s in s_dblks:
self.assertTrue(s in s_blks)
def test_find_directives(self):
ddirs = self.block.find_directives("directive")
p_ddirs = [d.primary for d in ddirs]
s_ddirs = [d.secondary for d in ddirs]
p_dirs = self.block.primary.find_directives("directive")
s_dirs = self.block.secondary.find_directives("directive")
# Check that every directive response is represented in the list of
# DualParserNode instances.
for p in p_ddirs:
self.assertTrue(p in p_dirs)
for s in s_ddirs:
self.assertTrue(s in s_dirs)
def test_find_comments(self):
dcoms = self.block.find_comments("comment")
p_dcoms = [d.primary for d in dcoms]
s_dcoms = [d.secondary for d in dcoms]
p_coms = self.block.primary.find_comments("comment")
s_coms = self.block.secondary.find_comments("comment")
# Check that every comment response is represented in the list of
# DualParserNode instances.
for p in p_dcoms:
self.assertTrue(p in p_coms)
for s in s_dcoms:
self.assertTrue(s in s_coms)
def test_find_blocks_first_passing(self):
youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_blocks_primary = mock.MagicMock(return_value=youshallpass)
find_blocks_secondary = mock.MagicMock(return_value=youshallnotpass)
self.block.primary.find_blocks = find_blocks_primary
self.block.secondary.find_blocks = find_blocks_secondary
blocks = self.block.find_blocks("something")
for block in blocks:
try:
assertions.assertEqual(block.primary, block.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertTrue(assertions.isPassDirective(block.primary))
self.assertFalse(assertions.isPassDirective(block.secondary))
def test_find_blocks_second_passing(self):
youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_blocks_primary = mock.MagicMock(return_value=youshallnotpass)
find_blocks_secondary = mock.MagicMock(return_value=youshallpass)
self.block.primary.find_blocks = find_blocks_primary
self.block.secondary.find_blocks = find_blocks_secondary
blocks = self.block.find_blocks("something")
for block in blocks:
try:
assertions.assertEqual(block.primary, block.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertFalse(assertions.isPassDirective(block.primary))
self.assertTrue(assertions.isPassDirective(block.secondary))
def test_find_dirs_first_passing(self):
notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_dirs_primary = mock.MagicMock(return_value=passing)
find_dirs_secondary = mock.MagicMock(return_value=notpassing)
self.block.primary.find_directives = find_dirs_primary
self.block.secondary.find_directives = find_dirs_secondary
directives = self.block.find_directives("something")
for directive in directives:
try:
assertions.assertEqual(directive.primary, directive.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertTrue(assertions.isPassDirective(directive.primary))
self.assertFalse(assertions.isPassDirective(directive.secondary))
def test_find_dirs_second_passing(self):
notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_dirs_primary = mock.MagicMock(return_value=notpassing)
find_dirs_secondary = mock.MagicMock(return_value=passing)
self.block.primary.find_directives = find_dirs_primary
self.block.secondary.find_directives = find_dirs_secondary
directives = self.block.find_directives("something")
for directive in directives:
try:
assertions.assertEqual(directive.primary, directive.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertFalse(assertions.isPassDirective(directive.primary))
self.assertTrue(assertions.isPassDirective(directive.secondary))
def test_find_coms_first_passing(self):
notpassing = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_coms_primary = mock.MagicMock(return_value=passing)
find_coms_secondary = mock.MagicMock(return_value=notpassing)
self.block.primary.find_comments = find_coms_primary
self.block.secondary.find_comments = find_coms_secondary
comments = self.block.find_comments("something")
for comment in comments:
try:
assertions.assertEqual(comment.primary, comment.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertTrue(assertions.isPassComment(comment.primary))
self.assertFalse(assertions.isPassComment(comment.secondary))
def test_find_coms_second_passing(self):
notpassing = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS,
ancestor=self.block,
filepath=assertions.PASS)]
find_coms_primary = mock.MagicMock(return_value=notpassing)
find_coms_secondary = mock.MagicMock(return_value=passing)
self.block.primary.find_comments = find_coms_primary
self.block.secondary.find_comments = find_coms_secondary
comments = self.block.find_comments("something")
for comment in comments:
try:
assertions.assertEqual(comment.primary, comment.secondary)
except AssertionError: # pragma: no cover
self.fail("Assertion should have passed")
self.assertFalse(assertions.isPassComment(comment.primary))
self.assertTrue(assertions.isPassComment(comment.secondary))
def test_find_blocks_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
notpassing2 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
find_blocks_primary = mock.MagicMock(return_value=notpassing1)
find_blocks_secondary = mock.MagicMock(return_value=notpassing2)
self.block.primary.find_blocks = find_blocks_primary
self.block.secondary.find_blocks = find_blocks_secondary
blocks = self.block.find_blocks("anything")
for block in blocks:
self.assertEqual(block.primary, block.secondary)
self.assertTrue(block.primary is not block.secondary)
def test_find_dirs_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
notpassing2 = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
find_dirs_primary = mock.MagicMock(return_value=notpassing1)
find_dirs_secondary = mock.MagicMock(return_value=notpassing2)
self.block.primary.find_directives = find_dirs_primary
self.block.secondary.find_directives = find_dirs_secondary
directives = self.block.find_directives("anything")
for directive in directives:
self.assertEqual(directive.primary, directive.secondary)
self.assertTrue(directive.primary is not directive.secondary)
def test_find_comments_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
notpassing2 = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
find_coms_primary = mock.MagicMock(return_value=notpassing1)
find_coms_secondary = mock.MagicMock(return_value=notpassing2)
self.block.primary.find_comments = find_coms_primary
self.block.secondary.find_comments = find_coms_secondary
comments = self.block.find_comments("anything")
for comment in comments:
self.assertEqual(comment.primary, comment.secondary)
self.assertTrue(comment.primary is not comment.secondary)
def test_find_blocks_no_pass_notequal(self):
notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
filepath="/path/to/whatever")]
notpassing2 = [augeasparser.AugeasBlockNode(name="different",
ancestor=self.block,
filepath="/path/to/whatever")]
find_blocks_primary = mock.MagicMock(return_value=notpassing1)
find_blocks_secondary = mock.MagicMock(return_value=notpassing2)
self.block.primary.find_blocks = find_blocks_primary
self.block.secondary.find_blocks = find_blocks_secondary
with self.assertRaises(AssertionError):
_ = self.block.find_blocks("anything")
def test_parsernode_notequal(self):
ne_block = augeasparser.AugeasBlockNode(name="different",
ancestor=self.block,
filepath="/path/to/whatever")
ne_directive = augeasparser.AugeasDirectiveNode(name="different",
ancestor=self.block,
filepath="/path/to/whatever")
ne_comment = augeasparser.AugeasCommentNode(comment="different",
ancestor=self.block,
filepath="/path/to/whatever")
self.assertFalse(self.block == ne_block)
self.assertFalse(self.directive == ne_directive)
self.assertFalse(self.comment == ne_comment)