diff --git a/certbot-apache/certbot_apache/assertions.py b/certbot-apache/certbot_apache/assertions.py index 659efee28..fc2b35e14 100644 --- a/certbot-apache/certbot_apache/assertions.py +++ b/certbot-apache/certbot_apache/assertions.py @@ -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): diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index f396ab76a..9325de5de 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -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""" diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 5f00d59f9..5fa337a45 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -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.""" diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index 97f58d6c9..eeea7d151 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -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)