Account for the case that there are multiple ancestors

In the diamond-include case, where a file is included by multiple other
files in the configuration tree, nodes can have multiple ancestors.

Change interfaces and existing implementations to account for this case.
This commit is contained in:
sydneyli 2019-12-10 12:03:45 -08:00
parent 5c588a6f8d
commit 6a5f013cfe
8 changed files with 83 additions and 79 deletions

View file

@ -13,9 +13,9 @@ class ApacheParserNode(interfaces.ParserNode):
"""
def __init__(self, **kwargs):
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
ancestors, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
super(ApacheParserNode, self).__init__(**kwargs)
self.ancestor = ancestor
self.ancestors = ancestors
self.filepath = filepath
self.dirty = dirty
self.metadata = metadata
@ -28,7 +28,7 @@ class ApacheParserNode(interfaces.ParserNode):
"""Find ancestor BlockNodes with a given name"""
return [ApacheBlockNode(name=assertions.PASS,
parameters=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)]
@ -45,7 +45,7 @@ class ApacheCommentNode(ApacheParserNode):
if isinstance(other, self.__class__):
return (self.comment == other.comment and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata and
self.filepath == other.filepath)
return False
@ -69,7 +69,7 @@ class ApacheDirectiveNode(ApacheParserNode):
self.parameters == other.parameters and
self.enabled == other.enabled and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata)
return False
@ -93,7 +93,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
self.children == other.children and
self.enabled == other.enabled and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata)
return False
@ -101,7 +101,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
"""Adds a new BlockNode to the sequence of children"""
new_block = ApacheBlockNode(name=assertions.PASS,
parameters=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)
self.children += (new_block,)
@ -111,7 +111,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
"""Adds a new DirectiveNode to the sequence of children"""
new_dir = ApacheDirectiveNode(name=assertions.PASS,
parameters=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)
self.children += (new_dir,)
@ -122,7 +122,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
"""Adds a new CommentNode to the sequence of children"""
new_comment = ApacheCommentNode(comment=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)
self.children += (new_comment,)
@ -132,7 +132,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
"""Recursive search of BlockNodes from the sequence of children"""
return [ApacheBlockNode(name=assertions.PASS,
parameters=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)]
@ -140,14 +140,14 @@ class ApacheBlockNode(ApacheDirectiveNode):
"""Recursive search of DirectiveNodes from the sequence of children"""
return [ApacheDirectiveNode(name=assertions.PASS,
parameters=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)]
def find_comments(self, comment, exact=False): # pylint: disable=unused-argument
"""Recursive search of DirectiveNodes from the sequence of children"""
return [ApacheCommentNode(comment=assertions.PASS,
ancestor=self,
ancestors=self,
filepath=assertions.PASS,
metadata=self.metadata)]

View file

@ -79,9 +79,9 @@ class AugeasParserNode(interfaces.ParserNode):
""" Augeas implementation of ParserNode interface """
def __init__(self, **kwargs):
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
ancestors, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
super(AugeasParserNode, self).__init__(**kwargs)
self.ancestor = ancestor
self.ancestors = ancestors
self.filepath = filepath
self.dirty = dirty
self.metadata = metadata
@ -135,7 +135,7 @@ class AugeasParserNode(interfaces.ParserNode):
metadata = {"augeasparser": self.parser, "augeaspath": path}
return AugeasBlockNode(name=name,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(path),
metadata=metadata)
@ -171,7 +171,7 @@ class AugeasCommentNode(AugeasParserNode):
return (self.comment == other.comment and
self.filepath == other.filepath and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata)
return False
@ -194,7 +194,7 @@ class AugeasDirectiveNode(AugeasParserNode):
self.parameters == other.parameters and
self.enabled == other.enabled and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata)
return False
@ -249,7 +249,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
self.children == other.children and
self.enabled == other.enabled and
self.dirty == other.dirty and
self.ancestor == other.ancestor and
self.ancestors == other.ancestors and
self.metadata == other.metadata)
return False
@ -269,7 +269,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
# Parameters will be set at the initialization of the new object
new_block = AugeasBlockNode(name=name,
parameters=parameters,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(realpath),
metadata=new_metadata)
return new_block
@ -294,7 +294,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
new_dir = AugeasDirectiveNode(name=name,
parameters=parameters,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(realpath),
metadata=new_metadata)
return new_dir
@ -314,7 +314,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
self.parser.aug.set(realpath, comment)
new_comment = AugeasCommentNode(comment=comment,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(realpath),
metadata=new_metadata)
return new_comment
@ -406,7 +406,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
# Because of the dynamic nature of AugeasParser and the fact that we're
# not populating the complete node tree, the ancestor has a dummy value
return AugeasCommentNode(comment=comment,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(path),
metadata=metadata)
@ -419,7 +419,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
# Because of the dynamic nature, and the fact that we're not populating
# the complete ParserNode tree, we use the search parent as ancestor
return AugeasDirectiveNode(name=name,
ancestor=assertions.PASS,
ancestors=assertions.PASS,
filepath=apache_util.get_file_path(path),
metadata=metadata)

View file

@ -368,7 +368,7 @@ class ApacheConfigurator(common.Installer):
return dualparser.DualBlockNode(
name=assertions.PASS,
ancestor=None,
ancestors=tuple(),
filepath=self.parser.loc["root"],
metadata=metadata
)

View file

@ -131,9 +131,10 @@ class ParserNode(object):
ParserNode objects should have the following attributes:
# Reference to ancestor node, or None if the node is the root node of the
# configuration tree.
ancestor: Optional[ParserNode]
# Reference to ancestor node(s), empty if the node is the root node of the
# configuration tree. This node can have multiple ancestors in the case
# that it is the root of a file included by multiple files.
ancestors: Tuple[ParserNode]
# True if this node has been modified since last save.
dirty: bool
@ -156,8 +157,8 @@ class ParserNode(object):
instance variables. This is not meant to be used directly, but through
specific classes implementing ParserNode interface.
:param ancestor: BlockNode ancestor for this CommentNode. Required.
:type ancestor: BlockNode or None
:param ancestors: BlockNode ancestors for this CommentNode. Required.
:type ancestors: tuple of BlockNodes
:param filepath: Filesystem path for the file where this CommentNode
does or should exist in the filesystem. Required.
@ -198,6 +199,9 @@ class ParserNode(object):
Traverses the ancestor tree up, searching for BlockNodes with a specific
name.
When a node has multiple lineages (when it is included by multiple files),
this method should search through all possible ancestor paths.
:param str name: Name of the ancestor BlockNode to search for
:returns: A list of ancestor BlockNodes that match the name
@ -233,8 +237,8 @@ class CommentNode(ParserNode):
:param comment: Contents of the comment. Required.
:type comment: str
:param ancestor: BlockNode ancestor for this CommentNode. Required.
:type ancestor: BlockNode or None
:param ancestors: BlockNode ancestors for this CommentNode. Required.
:type ancestors: tuple of BlockNodes
:param filepath: Filesystem path for the file where this CommentNode
does or should exist in the filesystem. Required.
@ -244,7 +248,7 @@ class CommentNode(ParserNode):
created or changed after the last save. Default: False.
:type dirty: bool
"""
super(CommentNode, self).__init__(ancestor=kwargs['ancestor'],
super(CommentNode, self).__init__(ancestors=kwargs['ancestors'],
dirty=kwargs.get('dirty', False),
filepath=kwargs['filepath'],
metadata=kwargs.get('metadata', {})) # pragma: no cover
@ -290,9 +294,9 @@ class DirectiveNode(ParserNode):
Default: ().
:type parameters: tuple
:param ancestor: BlockNode ancestor for this DirectiveNode, or None for
:param ancestors: BlockNode ancestors for this DirectiveNode. Empty for
root configuration node. Required.
:type ancestor: BlockNode or None
:type ancestor: tuple of BlockNodes
:param filepath: Filesystem path for the file where this DirectiveNode
does or should exist in the filesystem, or None for directives introduced
@ -309,7 +313,7 @@ class DirectiveNode(ParserNode):
:type enabled: bool
"""
super(DirectiveNode, self).__init__(ancestor=kwargs['ancestor'],
super(DirectiveNode, self).__init__(ancestors=kwargs['ancestors'],
dirty=kwargs.get('dirty', False),
filepath=kwargs['filepath'],
metadata=kwargs.get('metadata', {})) # pragma: no cover

View file

@ -51,8 +51,8 @@ def parsernode_kwargs(kwargs):
kwargs.setdefault("dirty", False)
kwargs.setdefault("metadata", {})
kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "metadata"])
return kwargs["ancestor"], kwargs["dirty"], kwargs["filepath"], kwargs["metadata"]
kwargs = validate_kwargs(kwargs, ["ancestors", "dirty", "filepath", "metadata"])
return kwargs["ancestors"], kwargs["dirty"], kwargs["filepath"], kwargs["metadata"]
def commentnode_kwargs(kwargs):
@ -83,7 +83,7 @@ def commentnode_kwargs(kwargs):
kwargs.setdefault("dirty", False)
kwargs.setdefault("metadata", {})
kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "comment",
kwargs = validate_kwargs(kwargs, ["ancestors", "dirty", "filepath", "comment",
"metadata"])
comment = kwargs.pop("comment")
@ -120,7 +120,7 @@ def directivenode_kwargs(kwargs):
kwargs.setdefault("parameters", ())
kwargs.setdefault("metadata", {})
kwargs = validate_kwargs(kwargs, ["ancestor", "dirty", "filepath", "name",
kwargs = validate_kwargs(kwargs, ["ancestors", "dirty", "filepath", "name",
"parameters", "enabled", "metadata"])
name = kwargs.pop("name")

View file

@ -36,7 +36,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
from certbot_apache.augeasparser import AugeasBlockNode
block = AugeasBlockNode(
name=assertions.PASS,
ancestor=None,
ancestors=tuple(),
filepath=assertions.PASS,
metadata={"augeasparser": mock.Mock(), "augeaspath": "/files/anything"}
)
@ -109,7 +109,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
AugeasDirectiveNode(
name=servernames[0].name,
parameters=["test", "setting", "these"],
ancestor=assertions.PASS,
ancestors=assertions.PASS,
metadata=servernames[0].primary.metadata
)
self.assertTrue(mock_set.called)
@ -244,7 +244,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
from certbot_apache.augeasparser import AugeasBlockNode
parameters = {
"name": assertions.PASS,
"ancestor": None,
"ancestors": tuple(),
"filepath": assertions.PASS,
"metadata": {
"augeasparser": mock.Mock(),
@ -261,7 +261,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
from certbot_apache.augeasparser import AugeasBlockNode
parameters = {
"name": assertions.PASS,
"ancestor": None,
"ancestors": tuple(),
"filepath": assertions.PASS,
"metadata": {
"augeasparser": mock.Mock(),

View file

@ -17,35 +17,35 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
parser_mock.get_arg.return_value = []
self.metadata = {"augeasparser": parser_mock, "augeaspath": "/invalid", "ac_ast": None}
self.block = dualparser.DualBlockNode(name="block",
ancestor=None,
ancestors=tuple(),
filepath="/tmp/something",
metadata=self.metadata)
self.block_two = dualparser.DualBlockNode(name="block",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
metadata=self.metadata)
self.directive = dualparser.DualDirectiveNode(name="directive",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
metadata=self.metadata)
self.comment = dualparser.DualCommentNode(comment="comment",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
metadata=self.metadata)
def test_create_with_precreated(self):
cnode = dualparser.DualCommentNode(comment="comment",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
primary=self.comment.secondary,
secondary=self.comment.primary)
dnode = dualparser.DualDirectiveNode(name="directive",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
primary=self.directive.secondary,
secondary=self.directive.primary)
bnode = dualparser.DualBlockNode(name="block",
ancestor=self.block,
ancestors=(self.block),
filepath="/tmp/something",
primary=self.block.secondary,
secondary=self.block.primary)
@ -160,11 +160,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_comments(self):
pri_comments = [augeasparser.AugeasCommentNode(comment="some comment",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
sec_comments = [augeasparser.AugeasCommentNode(comment=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_coms_primary = mock.MagicMock(return_value=pri_comments)
@ -186,11 +186,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_blocks_first_passing(self):
youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_blocks_primary = mock.MagicMock(return_value=youshallpass)
@ -209,11 +209,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_blocks_second_passing(self):
youshallnotpass = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
youshallpass = [augeasparser.AugeasBlockNode(name=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_blocks_primary = mock.MagicMock(return_value=youshallnotpass)
@ -232,11 +232,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_dirs_first_passing(self):
notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_dirs_primary = mock.MagicMock(return_value=passing)
@ -255,11 +255,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_dirs_second_passing(self):
notpassing = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
passing = [augeasparser.AugeasDirectiveNode(name=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_dirs_primary = mock.MagicMock(return_value=notpassing)
@ -278,11 +278,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_coms_first_passing(self):
notpassing = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_coms_primary = mock.MagicMock(return_value=passing)
@ -301,11 +301,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_coms_second_passing(self):
notpassing = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
passing = [augeasparser.AugeasCommentNode(comment=assertions.PASS,
ancestor=self.block,
ancestors=(self.block),
filepath=assertions.PASS,
metadata=self.metadata)]
find_coms_primary = mock.MagicMock(return_value=notpassing)
@ -324,11 +324,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_blocks_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
notpassing2 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
find_blocks_primary = mock.MagicMock(return_value=notpassing1)
@ -343,11 +343,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_dirs_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
notpassing2 = [augeasparser.AugeasDirectiveNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
find_dirs_primary = mock.MagicMock(return_value=notpassing1)
@ -362,11 +362,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_comments_no_pass_equal(self):
notpassing1 = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
notpassing2 = [augeasparser.AugeasCommentNode(comment="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
find_coms_primary = mock.MagicMock(return_value=notpassing1)
@ -381,11 +381,11 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_find_blocks_no_pass_notequal(self):
notpassing1 = [augeasparser.AugeasBlockNode(name="notpassing",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
notpassing2 = [augeasparser.AugeasBlockNode(name="different",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)]
find_blocks_primary = mock.MagicMock(return_value=notpassing1)
@ -398,15 +398,15 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
def test_parsernode_notequal(self):
ne_block = augeasparser.AugeasBlockNode(name="different",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)
ne_directive = augeasparser.AugeasDirectiveNode(name="different",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)
ne_comment = augeasparser.AugeasCommentNode(comment="different",
ancestor=self.block,
ancestors=(self.block),
filepath="/path/to/whatever",
metadata=self.metadata)
self.assertFalse(self.block == ne_block)

View file

@ -10,7 +10,7 @@ class ParserNodeUtilTest(unittest.TestCase):
def _setup_parsernode(self):
""" Sets up kwargs dict for ParserNode """
return {
"ancestor": None,
"ancestors": tuple(),
"dirty": False,
"filepath": "/tmp",
}
@ -48,8 +48,8 @@ class ParserNodeUtilTest(unittest.TestCase):
params = self._setup_parsernode()
ctrl = self._setup_parsernode()
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params)
self.assertEqual(ancestor, ctrl["ancestor"])
ancestors, dirty, filepath, metadata = util.parsernode_kwargs(params)
self.assertEqual(ancestors, ctrl["ancestors"])
self.assertEqual(dirty, ctrl["dirty"])
self.assertEqual(filepath, ctrl["filepath"])
self.assertEqual(metadata, {})
@ -103,7 +103,7 @@ class ParserNodeUtilTest(unittest.TestCase):
self.assertRaises(TypeError, util.commentnode_kwargs, c_params)
d_params = self._setup_directivenode()
d_params.pop("ancestor")
d_params.pop("ancestors")
self.assertRaises(TypeError, util.directivenode_kwargs, d_params)
p_params = self._setup_parsernode()