From 5e8318096f7d66cb109c332f6d00bffa7bac446c Mon Sep 17 00:00:00 2001 From: Mohamed El Mouctar HAIDARA Date: Sun, 22 Dec 2024 16:10:44 +0100 Subject: [PATCH] add a new flag to show the handlers --- README.md | 22 ++-- ansibleplaybookgrapher/cli.py | 11 ++ ansibleplaybookgrapher/renderer/__init__.py | 51 +++++--- .../renderer/graphviz/__init__.py | 29 +++-- ansibleplaybookgrapher/renderer/json.py | 6 +- ansibleplaybookgrapher/renderer/mermaid.py | 14 ++- tests/test_cli.py | 24 ++++ tests/test_graphviz_renderer.py | 119 ++++++++++-------- 8 files changed, 184 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index e58f796c..614d73a3 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ JavaScript: Lastly, you can provide your own protocol formats with `--open-protocol-handler custom --open-protocol-custom-formats '{}'`. See the help and [an example.](https://github.com/haidaraM/ansible-playbook-grapher/blob/34e0aef74b82808dceb6ccfbeb333c0b531eac12/ansibleplaybookgrapher/renderer/__init__.py#L32-L41) + To not confuse with [Ansible Handlers.](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html) - Export the dot file used to generate the graph with Graphviz. ## Prerequisites @@ -397,11 +398,14 @@ regarding the blocks. The available options: ``` -usage: ansible-playbook-grapher [-h] [-v] [-i INVENTORY] [--include-role-tasks] [-s] [--view] [-o OUTPUT_FILENAME] - [--open-protocol-handler {default,vscode,custom}] [--open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS] - [--group-roles-by-name] [--renderer {graphviz,mermaid-flowchart,json}] [--renderer-mermaid-directive RENDERER_MERMAID_DIRECTIVE] - [--renderer-mermaid-orientation {TD,RL,BT,RL,LR}] [--version] [--hide-plays-without-roles] [--hide-empty-plays] [-t TAGS] - [--skip-tags SKIP_TAGS] [--vault-id VAULT_IDS] [-J | --vault-password-file VAULT_PASSWORD_FILES] [-e EXTRA_VARS] +usage: ansible-playbook-grapher [-h] [-v] [--exclude-roles EXCLUDE_ROLES] [--only-roles] [-i INVENTORY] [--include-role-tasks] [-s] + [--view] [-o OUTPUT_FILENAME] [--open-protocol-handler {default,vscode,custom}] + [--open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS] [--group-roles-by-name] + [--renderer {graphviz,mermaid-flowchart,json}] + [--renderer-mermaid-directive RENDERER_MERMAID_DIRECTIVE] + [--renderer-mermaid-orientation {TD,RL,BT,RL,LR}] [--version] [--hide-plays-without-roles] + [--hide-empty-plays] [--show-handlers] [-t TAGS] [--skip-tags SKIP_TAGS] [--vault-id VAULT_IDS] [-J | + --vault-password-file VAULT_PASSWORD_FILES] [-e EXTRA_VARS] playbooks [playbooks ...] Make graphs from your Ansible Playbooks. @@ -419,7 +423,7 @@ options: Hide the plays that end up with no roles in the graph (after applying the tags filter). Only roles at the play level and include_role as tasks are considered (no import_role). --include-role-tasks Include the tasks of the roles in the graph. Applied when parsing the playbooks. - --only-roles Ignore all tasks when rendering graphs. + --only-roles Only display the roles in the graph (ignoring the tasks) --open-protocol-custom-formats OPEN_PROTOCOL_CUSTOM_FORMATS The custom formats to use as URLs for the nodes in the graph. Required if --open-protocol-handler is set to custom. You should provide a JSON formatted string like: {"file": "", "folder": ""}. Example: If you want to open folders (roles) inside the browser and files @@ -438,12 +442,13 @@ options: https://mermaid.js.org/config/directives.html. Default: '%%{ init: { "flowchart": { "curve": "bumpX" } } }%%' --renderer-mermaid-orientation {TD,RL,BT,RL,LR} The orientation of the flow chart. Default: 'LR' + --show-handlers Show the handlers in the graph. See the limitations in the project README on GitHub. --skip-tags SKIP_TAGS only run plays and tasks whose tags do not match these values. This argument may be specified multiple times. --vault-id VAULT_IDS the vault identity to use. This argument may be specified multiple times. --vault-password-file VAULT_PASSWORD_FILES, --vault-pass-file VAULT_PASSWORD_FILES vault password file - --version + --version show program's version number and exit --view Automatically open the resulting SVG file with your system's default viewer application for the file type -J, --ask-vault-password, --ask-vault-pass ask for vault password @@ -481,6 +486,9 @@ More information [here](https://docs.ansible.com/ansible/latest/reference_append Always check the edge label to know the task order. - The label of the edges may overlap with each other. They are positioned so that they are as close as possible to the target nodes. If the same role is used in multiple plays or playbooks, the labels can overlap. +- **Ansible Handlers**: The handlers are partially supported for the moment. Their position in the graph doesn't entirely + reflect their real order of execution in the playbook. They are displayed at the end of the play and roles, but they + might be executed before that. ## Contribution diff --git a/ansibleplaybookgrapher/cli.py b/ansibleplaybookgrapher/cli.py index 24bd3a03..280a5106 100644 --- a/ansibleplaybookgrapher/cli.py +++ b/ansibleplaybookgrapher/cli.py @@ -97,6 +97,7 @@ def run(self): save_dot_file=self.options.save_dot_file, hide_empty_plays=self.options.hide_empty_plays, hide_plays_without_roles=self.options.hide_plays_without_roles, + show_handlers=self.options.show_handlers, ) case "mermaid-flowchart": @@ -113,6 +114,7 @@ def run(self): orientation=self.options.renderer_mermaid_orientation, hide_empty_plays=self.options.hide_empty_plays, hide_plays_without_roles=self.options.hide_plays_without_roles, + show_handlers=self.options.show_handlers, ) case "json": @@ -124,6 +126,7 @@ def run(self): view=self.options.view, hide_empty_plays=self.options.hide_empty_plays, hide_plays_without_roles=self.options.hide_plays_without_roles, + show_handlers=self.options.show_handlers, ) case _: @@ -303,6 +306,14 @@ def _add_my_options(self) -> None: help="Hide the plays that end up with no tasks in the graph (after applying the tags filter).", ) + self.parser.add_argument( + "--show-handlers", + dest="show_handlers", + action="store_true", + default=False, + help="Show the handlers in the graph. See the limitations in the project README on GitHub.", + ) + self.parser.add_argument( "playbooks", help="Playbook(s) to graph. You can specify multiple playbooks, separated by spaces and reference playbooks in collections.", diff --git a/ansibleplaybookgrapher/renderer/__init__.py b/ansibleplaybookgrapher/renderer/__init__.py index aba80ace..19e03de7 100644 --- a/ansibleplaybookgrapher/renderer/__init__.py +++ b/ansibleplaybookgrapher/renderer/__init__.py @@ -136,26 +136,35 @@ def build_playbook( self, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, **kwargs, ) -> str: """Build the whole playbook :param hide_empty_plays: Whether to hide empty plays or not - :param hide_plays_without_roles: + :param hide_plays_without_roles: Whether to hide plays without roles or not + :param show_handlers: Whether to show the handlers or not. :param kwargs: :return: The rendered playbook as a string. """ @abstractmethod - def build_play(self, play_node: PlayNode, **kwargs) -> None: + def build_play( + self, play_node: PlayNode, show_handlers: bool = False, **kwargs + ) -> None: """Build a single play to be rendered - :param play_node: + + :param play_node: The play to render + :param show_handlers: Whether to show the handlers or not. :param kwargs: :return: """ - def traverse_play(self, play_node: PlayNode, **kwargs) -> None: + def traverse_play( + self, play_node: PlayNode, show_handlers: bool = False, **kwargs + ) -> None: """Traverse a play to build the graph: pre_tasks, roles, tasks, post_tasks :param play_node: + :param show_handlers: Whether to show the handlers or not. :param kwargs: :return: """ @@ -178,13 +187,14 @@ def traverse_play(self, play_node: PlayNode, **kwargs) -> None: **kwargs, ) - for r_handler in role.handlers: - self.build_node( - node=r_handler, - color=color, - fontcolor=play_font_color, - **kwargs, - ) + if show_handlers: + for r_handler in role.handlers: + self.build_node( + node=r_handler, + color=color, + fontcolor=play_font_color, + **kwargs, + ) # tasks for task in play_node.tasks: @@ -204,15 +214,16 @@ def traverse_play(self, play_node: PlayNode, **kwargs) -> None: **kwargs, ) - # play handlers - for p_handler in play_node.handlers: - self.build_node( - node=p_handler, - color=color, - fontcolor=play_font_color, - node_label_prefix="[handler] ", - **kwargs, - ) + if show_handlers: + # play handlers + for p_handler in play_node.handlers: + self.build_node( + node=p_handler, + color=color, + fontcolor=play_font_color, + node_label_prefix="[handler] ", + **kwargs, + ) @abstractmethod def build_task( diff --git a/ansibleplaybookgrapher/renderer/graphviz/__init__.py b/ansibleplaybookgrapher/renderer/graphviz/__init__.py index c914089e..70127b24 100644 --- a/ansibleplaybookgrapher/renderer/graphviz/__init__.py +++ b/ansibleplaybookgrapher/renderer/graphviz/__init__.py @@ -54,14 +54,17 @@ def render( view: bool = False, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, **kwargs, ) -> str: - """:param open_protocol_handler: The protocol handler name to use + """ + :param open_protocol_handler: The protocol handler name to use :param open_protocol_custom_formats: The custom formats to use when the protocol handler is set to custom :param output_filename: The output filename without any extension :param view: Whether to open the rendered file in the default viewer :param hide_empty_plays: Whether to hide empty plays or not when rendering the graph :param hide_plays_without_roles: Whether to hide plays without any roles or not + :param show_handlers: :return: The path of the rendered file """ save_dot_file = kwargs.get("save_dot_file", False) @@ -85,6 +88,7 @@ def render( builder.build_playbook( hide_empty_plays=hide_empty_plays, hide_plays_without_roles=hide_plays_without_roles, + show_handlers=show_handlers ) roles_built.update(builder.roles_built) @@ -244,6 +248,7 @@ def build_role( **kwargs, ) -> None: """Render a role in the graph + :return: """ digraph = kwargs["digraph"] @@ -267,10 +272,7 @@ def build_role( self.roles_built.add(role_node) - if role_node.include_role: # For include_role, we point to a file - url = self.get_node_url(role_node) - else: # For normal role invocation, we point to the folder - url = self.get_node_url(role_node) + url = self.get_node_url(role_node) plays_using_this_role = self.roles_usage[role_node] if len(plays_using_this_role) > 1: @@ -304,11 +306,13 @@ def build_playbook( self, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, **kwargs, ) -> str: """Convert the PlaybookNode to the graphviz dot format :param hide_empty_plays: Whether to hide empty plays or not when rendering the graph :param hide_plays_without_roles: Whether to hide plays without any roles or not + :param show_handlers: Whether to show the handlers or not. :return: The text representation of the graphviz dot format for the playbook. """ display.vvv("Converting the graph to the dot format for graphviz") @@ -326,12 +330,19 @@ def build_playbook( exclude_without_roles=hide_plays_without_roles, ): with self.digraph.subgraph(name=play.name) as play_subgraph: - self.build_play(play, digraph=play_subgraph, **kwargs) + self.build_play( + play, digraph=play_subgraph, show_handlers=show_handlers, **kwargs + ) return self.digraph.source - def build_play(self, play_node: PlayNode, **kwargs) -> None: - """:param play_node: + def build_play( + self, play_node: PlayNode, show_handlers: bool = False, **kwargs + ) -> None: + """ + + :param show_handlers: + :param play_node: :param kwargs: :return: """ @@ -369,4 +380,4 @@ def build_play(self, play_node: PlayNode, **kwargs) -> None: ) # traverse the play - self.traverse_play(play_node, **kwargs) + self.traverse_play(play_node, show_handlers=show_handlers, **kwargs) diff --git a/ansibleplaybookgrapher/renderer/json.py b/ansibleplaybookgrapher/renderer/json.py index 08ea66a6..76c3dd37 100644 --- a/ansibleplaybookgrapher/renderer/json.py +++ b/ansibleplaybookgrapher/renderer/json.py @@ -44,6 +44,7 @@ def render( view: bool = False, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, **kwargs, ) -> str: playbooks = [] @@ -110,9 +111,12 @@ def build_playbook( return json.dumps(self.json_output) - def build_play(self, play_node: PlayNode, **kwargs) -> None: + def build_play( + self, play_node: PlayNode, show_handlers: bool = False, **kwargs + ) -> None: """Not needed. + :param show_handlers: :param play_node: :param kwargs: :return: diff --git a/ansibleplaybookgrapher/renderer/mermaid.py b/ansibleplaybookgrapher/renderer/mermaid.py index d840fcfd..ef21eea1 100644 --- a/ansibleplaybookgrapher/renderer/mermaid.py +++ b/ansibleplaybookgrapher/renderer/mermaid.py @@ -47,6 +47,7 @@ def render( view: bool = False, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, directive: str = DEFAULT_DIRECTIVE, orientation: str = DEFAULT_ORIENTATION, **kwargs, @@ -59,6 +60,7 @@ def render( :param view: Not supported for the moment. :param hide_empty_plays: Whether to hide empty plays or not when rendering the graph. :param hide_plays_without_roles: Whether to hide plays without any roles or not. + :param show_handlers: Whether to show handlers or not. :param directive: Mermaid directive. :param orientation: Mermaid graph orientation. :param kwargs: @@ -95,6 +97,7 @@ def render( mermaid_code += playbook_builder.build_playbook( hide_empty_plays=hide_empty_plays, hide_plays_without_roles=hide_plays_without_roles, + show_handlers=show_handlers ) link_order = playbook_builder.link_order roles_built.update(playbook_builder.roles_built) @@ -173,6 +176,7 @@ def build_playbook( self, hide_empty_plays: bool = False, hide_plays_without_roles: bool = False, + show_handlers: bool = False, **kwargs, ) -> str: """Build a playbook. @@ -180,6 +184,7 @@ def build_playbook( :param hide_plays_without_roles: Whether to hide plays without any roles or not :param hide_empty_plays: Whether to hide empty plays or not :param hide_plays_without_roles: Whether to hide plays without any roles or not + :param show_handlers: Whether to show handlers or not :param kwargs: :return: """ @@ -200,16 +205,19 @@ def build_playbook( exclude_empty=hide_empty_plays, exclude_without_roles=hide_plays_without_roles, ): - self.build_play(play_node) + self.build_play(play_node, show_handlers=show_handlers, **kwargs) self._indentation_level -= 1 self.add_comment(f"End of the playbook '{self.playbook_node.display_name()}'\n") return self.mermaid_code - def build_play(self, play_node: PlayNode, **kwargs) -> None: + def build_play( + self, play_node: PlayNode, show_handlers: bool = False, **kwargs + ) -> None: """Build a play. + :param show_handlers: :param play_node: :param kwargs: :return: @@ -235,7 +243,7 @@ def build_play(self, play_node: PlayNode, **kwargs) -> None: # traverse the play self._indentation_level += 1 - self.traverse_play(play_node) + self.traverse_play(play_node, show_handlers, **kwargs) self._indentation_level -= 1 self.add_comment(f"End of the play '{play_node.display_name()}'") diff --git a/tests/test_cli.py b/tests/test_cli.py index aeceb2fe..ef3e0988 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -241,6 +241,30 @@ def test_cli_include_role_tasks( assert cli.options.include_role_tasks == expected +@pytest.mark.parametrize( + ("show_handlers_option", "expected"), + [(["--"], False), (["--show-handlers"], True)], + ids=["default", "include"], +) +def test_cli_show_handlers( + show_handlers_option: list[str], + expected: bool, +) -> None: + """Test for show handlers options: --show-handlers + + :param show_handlers_option: + :param expected: + :return: + """ + args = [__prog__, *show_handlers_option, "playboook.yml"] + + cli = PlaybookGrapherCLI(args) + + cli.parse() + + assert cli.options.show_handlers == expected + + @pytest.mark.parametrize( ("tags_option", "expected"), [ diff --git a/tests/test_graphviz_renderer.py b/tests/test_graphviz_renderer.py index d51a2c32..e6ff43b6 100644 --- a/tests/test_graphviz_renderer.py +++ b/tests/test_graphviz_renderer.py @@ -15,9 +15,9 @@ def run_grapher( - playbooks: list[str], - output_filename: str, - additional_args: list[str] | None = None, + playbooks: list[str], + output_filename: str, + additional_args: list[str] | None = None, ) -> tuple[str, list[str]]: """Utility function to run the grapher :param output_filename: @@ -71,16 +71,16 @@ def run_grapher( def _common_tests( - svg_filename: str, - playbook_paths: list[str], - playbooks_number: int = 1, - plays_number: int = 0, - tasks_number: int = 0, - post_tasks_number: int = 0, - roles_number: int = 0, - pre_tasks_number: int = 0, - blocks_number: int = 0, - handlers_number: int = 0, + svg_filename: str, + playbook_paths: list[str], + playbooks_number: int = 1, + plays_number: int = 0, + tasks_number: int = 0, + post_tasks_number: int = 0, + roles_number: int = 0, + pre_tasks_number: int = 0, + blocks_number: int = 0, + handlers_number: int = 0, ) -> dict[str, list[Element]]: """Perform some common tests on the generated svg file: - Existence of svg file @@ -112,40 +112,40 @@ def _common_tests( playbooks_file_names = [e.text for e in playbooks.find("text")] assert ( - playbooks_file_names == playbook_paths + playbooks_file_names == playbook_paths ), "The playbook file names should be in the svg file" assert ( - len(playbooks) == playbooks_number + len(playbooks) == playbooks_number ), f"The graph '{svg_filename}' should contains {playbooks_number} playbook(s) but we found {len(playbooks)} play(s)" assert ( - len(plays) == plays_number + len(plays) == plays_number ), f"The graph '{svg_filename}' should contains {plays_number} play(s) but we found {len(plays)} play(s)" assert ( - len(pre_tasks) == pre_tasks_number + len(pre_tasks) == pre_tasks_number ), f"The graph '{svg_filename}' should contains {pre_tasks_number} pre tasks(s) but we found {len(pre_tasks)} pre tasks" assert ( - len(roles) == roles_number + len(roles) == roles_number ), f"The graph '{svg_filename}' should contains {roles_number} role(s) but we found {len(roles)} role(s)" assert ( - len(tasks) == tasks_number + len(tasks) == tasks_number ), f"The graph '{svg_filename}' should contains {tasks_number} tasks(s) but we found {len(tasks)} tasks" assert ( - len(post_tasks) == post_tasks_number + len(post_tasks) == post_tasks_number ), f"The graph '{svg_filename}' should contains {post_tasks_number} post tasks(s) but we found {len(post_tasks)} post tasks" assert ( - len(blocks) == blocks_number + len(blocks) == blocks_number ), f"The graph '{svg_filename}' should contains {blocks_number} blocks(s) but we found {len(blocks)} blocks" assert ( - len(handlers) == handlers_number - ), f"The graph '{svg_filename}' should contains {blocks_number} handlers(s) but we found {len(handlers)} handlers " + len(handlers) == handlers_number + ), f"The graph '{svg_filename}' should contains {handlers_number} handlers(s) but we found {len(handlers)} handlers " return { "tasks": tasks, @@ -238,9 +238,9 @@ def test_import_tasks(request: pytest.FixtureRequest) -> None: ids=["no_include_role_tasks_option", "include_role_tasks_option"], ) def test_with_roles( - request: pytest.FixtureRequest, - include_role_tasks_option: str, - expected_tasks_number: int, + request: pytest.FixtureRequest, + include_role_tasks_option: str, + expected_tasks_number: int, ) -> None: """Test with_roles.yml, an example with roles.""" svg_path, playbook_paths = run_grapher( @@ -266,9 +266,9 @@ def test_with_roles( ids=["no_include_role_tasks_option", "include_role_tasks_option"], ) def test_include_role( - request: pytest.FixtureRequest, - include_role_tasks_option: str, - expected_tasks_number: int, + request: pytest.FixtureRequest, + include_role_tasks_option: str, + expected_tasks_number: int, ) -> None: """Test include_role.yml, an example with include_role.""" svg_path, playbook_paths = run_grapher( @@ -328,9 +328,9 @@ def test_nested_include_tasks(request: pytest.FixtureRequest) -> None: ids=["no_include_role_tasks_option", "include_role_tasks_option"], ) def test_import_role( - request: pytest.FixtureRequest, - include_role_tasks_option: str, - expected_tasks_number: int, + request: pytest.FixtureRequest, + include_role_tasks_option: str, + expected_tasks_number: int, ) -> None: """Test import_role.yml, an example with import role. Import role is special because the tasks imported from role are treated as "normal tasks" when the playbook is parsed. @@ -372,9 +372,9 @@ def test_import_playbook(request: pytest.FixtureRequest) -> None: ids=["no_include_role_tasks_option", "include_role_tasks_option"], ) def test_nested_import_playbook( - request: pytest.FixtureRequest, - include_role_tasks_option: str, - expected_tasks_number: int, + request: pytest.FixtureRequest, + include_role_tasks_option: str, + expected_tasks_number: int, ) -> None: """Test nested import playbook with an import_role and include_tasks.""" svg_path, playbook_paths = run_grapher( @@ -405,10 +405,10 @@ def test_relative_var_files(request: pytest.FixtureRequest) -> None: # check if the plays title contains the interpolated variables assert ( - "Cristiano Ronaldo" in res["tasks"][0].find("g/a/text").text + "Cristiano Ronaldo" in res["tasks"][0].find("g/a/text").text ), "The title should contain player name" assert ( - "Lionel Messi" in res["tasks"][1].find("g/a/text").text + "Lionel Messi" in res["tasks"][1].find("g/a/text").text ), "The title should contain player name" @@ -482,7 +482,7 @@ def test_multi_playbooks(request: pytest.FixtureRequest) -> None: def test_with_roles_with_custom_protocol_handlers( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, ) -> None: """Test with_roles.yml with a custom protocol handlers.""" formats_str = '{"file": "vscode://file/{path}:{line}", "folder": "{path}"}' @@ -520,7 +520,7 @@ def test_with_roles_with_custom_protocol_handlers( def test_community_download_roles_and_collection( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, ) -> None: """Test if the grapher is able to find some downloaded roles and collections when graphing the playbook :return: @@ -538,11 +538,11 @@ def test_community_download_roles_and_collection( ids=["no_group", "group"], ) def test_group_roles_by_name( - request: pytest.FixtureRequest, - flag: str, - roles_number: int, - tasks_number: int, - post_tasks_number: int, + request: pytest.FixtureRequest, + flag: str, + roles_number: int, + tasks_number: int, + post_tasks_number: int, ) -> None: """Test group roles by name :return: @@ -607,7 +607,7 @@ def test_hiding_empty_plays_with_tags_filter(request: pytest.FixtureRequest) -> def test_hiding_empty_plays_with_tags_filter_all( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, ) -> None: """Test hiding plays with the flag --hide-empty-plays. @@ -652,7 +652,7 @@ def test_hiding_plays_without_roles(request: pytest.FixtureRequest) -> None: def test_hiding_plays_without_roles_with_tags_filtering( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, ) -> None: """Test hiding plays with the flag --hide-plays-without-roles. @@ -688,7 +688,7 @@ def test_hiding_plays_without_roles_with_tags_filtering( ], ) def test_graphing_a_playbook_in_a_collection( - request: pytest.FixtureRequest, playbook: str + request: pytest.FixtureRequest, playbook: str ) -> None: """Test graphing a playbook in a collection @@ -712,8 +712,15 @@ def test_graphing_a_playbook_in_a_collection( ) +@pytest.mark.parametrize( + ("flag", "handlers_number"), + [("--", 0), ("--show-handlers", 6)], + ids=["no_handlers", "show_handlers"], +) def test_handlers( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, + flag: str, + handlers_number: int, ) -> None: """Test graphing a playbook with handlers @@ -721,7 +728,7 @@ def test_handlers( :return: """ svg_path, playbook_paths = run_grapher( - ["handlers.yml"], output_filename=request.node.name + ["handlers.yml"], output_filename=request.node.name, additional_args=[flag] ) _common_tests( @@ -730,12 +737,19 @@ def test_handlers( pre_tasks_number=1, plays_number=2, tasks_number=6, - handlers_number=6, + handlers_number=handlers_number, ) +@pytest.mark.parametrize( + ("flag", "handlers_number"), + [("--", 0), ("--show-handlers", 2)], + ids=["no_handlers", "show_handlers"], +) def test_handlers_in_role( - request: pytest.FixtureRequest, + request: pytest.FixtureRequest, + flag: str, + handlers_number: int, ) -> None: """Test graphing a playbook with handlers @@ -747,6 +761,7 @@ def test_handlers_in_role( output_filename=request.node.name, additional_args=[ "--include-role-tasks", + flag, ], ) @@ -758,5 +773,5 @@ def test_handlers_in_role( tasks_number=1, post_tasks_number=1, roles_number=1, - handlers_number=2, + handlers_number=handlers_number, )