From 8a16c627b9cecac404e49afbe05a8298744dc629 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Sun, 26 Nov 2023 13:57:52 +0100 Subject: [PATCH] split command linter --- lib/galaxy/tool_util/linters/command.py | 130 ++++++++++++++--------- test/unit/tool_util/test_tool_linters.py | 44 ++++---- 2 files changed, 106 insertions(+), 68 deletions(-) diff --git a/lib/galaxy/tool_util/linters/command.py b/lib/galaxy/tool_util/linters/command.py index 9d2dadd1b844..ff12866f604c 100644 --- a/lib/galaxy/tool_util/linters/command.py +++ b/lib/galaxy/tool_util/linters/command.py @@ -1,54 +1,88 @@ -"""This module contains a linting function for a tool's command description. +"""This module contains linters for a tool's command description. A command description describes how to build the command-line to execute from supplied inputs. """ +from typing import TYPE_CHECKING +from galaxy.tool_util.lint import Linter -def lint_command(tool_xml, lint_ctx): - """Ensure tool contains exactly one command and check attributes.""" - root = tool_xml.find("./command") - if root is None: - root = tool_xml.getroot() - - commands = tool_xml.findall("./command") - if len(commands) > 1: - lint_ctx.error("More than one command tag found, behavior undefined.", node=commands[1]) - return - - if len(commands) == 0: - lint_ctx.error("No command tag found, must specify a command template to execute.", node=root) - return - - command = get_command(tool_xml) - if command.text is None: - lint_ctx.error("Command is empty.", node=root) - elif "TODO" in command.text: - lint_ctx.warn("Command template contains TODO text.", node=command) - - command_attrib = command.attrib - interpreter_type = None - for key, value in command_attrib.items(): - if key == "interpreter": - interpreter_type = value - elif key == "detect_errors": - detect_errors = value - if detect_errors not in ["default", "exit_code", "aggressive"]: - lint_ctx.warn(f"Unknown detect_errors attribute [{detect_errors}]", node=command) - - interpreter_info = "" - if interpreter_type: - interpreter_info = f" with interpreter of type [{interpreter_type}]" - if interpreter_type: - lint_ctx.warn("Command uses deprecated 'interpreter' attribute.", node=command) - lint_ctx.info(f"Tool contains a command{interpreter_info}.", node=command) - - -def get_command(tool_xml): - """Get command XML element from supplied XML root.""" - root = tool_xml.getroot() - commands = root.findall("command") - command = None - if len(commands) == 1: - command = commands[0] - return command +if TYPE_CHECKING: + from galaxy.tool_util.lint import LintContext + from galaxy.tool_util.parser.interface import ToolSource + + +class CommandMultiple(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + commands = tool_xml.findall("./command") + if len(commands) > 1: + lint_ctx.error("More than one command tag found, behavior undefined.", node=commands[1]) + + +class CommandMissing(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + root = tool_xml.find("./command") or tool_xml.getroot() + command = tool_xml.find("./command") + if command is None: + lint_ctx.error("No command tag found, must specify a command template to execute.", node=root) + + +class CommandEmpty(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + root = tool_xml.find("./command") or tool_xml.getroot() + command = tool_xml.find("./command") + if command is not None and command.text is None: + lint_ctx.error("Command is empty.", node=root) + + +class CommandTODO(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + command = tool_xml.find("./command") + if command is not None and command.text is not None and "TODO" in command.text: + lint_ctx.warn("Command template contains TODO text.", node=command) + + +class CommandInterpreterDeprecated(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + command = tool_xml.find("./command") + if command is None: + return + interpreter_type = command.attrib.get("interpreter", None) + if interpreter_type is not None: + lint_ctx.warn("Command uses deprecated 'interpreter' attribute.", node=command) + + +class CommandInfo(Linter): + @classmethod + def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"): + tool_xml = getattr(tool_source, "xml_tree", None) + if not tool_xml: + return + command = tool_xml.find("./command") + if command is None: + return + interpreter_type = command.attrib.get("interpreter", None) + interpreter_info = "" + if interpreter_type: + interpreter_info = f" with interpreter of type [{interpreter_type}]" + lint_ctx.info(f"Tool contains a command{interpreter_info}.", node=command) diff --git a/test/unit/tool_util/test_tool_linters.py b/test/unit/tool_util/test_tool_linters.py index 239e8f0d094e..5026a5a63a39 100644 --- a/test/unit/tool_util/test_tool_linters.py +++ b/test/unit/tool_util/test_tool_linters.py @@ -62,8 +62,8 @@ # tests tool xml for command linter COMMAND_MULTIPLE = """ - - + ls + df """ COMMAND_MISSING = """ @@ -957,35 +957,39 @@ def test_citations_valid(lint_ctx): def test_command_multiple(lint_ctx): - tool_xml_tree = get_xml_tree(COMMAND_MULTIPLE) - run_lint_module(lint_ctx, command, tool_xml_tree) - assert "More than one command tag found, behavior undefined." in lint_ctx.error_messages - assert len(lint_ctx.error_messages) == 1 - assert not lint_ctx.info_messages + tool_source = get_xml_tool_source(COMMAND_MULTIPLE) + run_lint_module(lint_ctx, command, tool_source) + assert lint_ctx.error_messages == ["More than one command tag found, behavior undefined."] + assert lint_ctx.info_messages == ["Tool contains a command."] assert not lint_ctx.valid_messages assert not lint_ctx.warn_messages def test_command_missing(lint_ctx): - tool_xml_tree = get_xml_tree(COMMAND_MISSING) - run_lint_module(lint_ctx, command, tool_xml_tree) - assert "No command tag found, must specify a command template to execute." in lint_ctx.error_messages + tool_source = get_xml_tool_source(COMMAND_MISSING) + run_lint_module(lint_ctx, command, tool_source) + assert lint_ctx.error_messages == ["No command tag found, must specify a command template to execute."] + assert not lint_ctx.info_messages + assert not lint_ctx.valid_messages + assert not lint_ctx.warn_messages def test_command_todo(lint_ctx): - tool_xml_tree = get_xml_tree(COMMAND_TODO) - run_lint_module(lint_ctx, command, tool_xml_tree) - assert "Tool contains a command." in lint_ctx.info_messages - assert "Command template contains TODO text." in lint_ctx.warn_messages + tool_source = get_xml_tool_source(COMMAND_TODO) + run_lint_module(lint_ctx, command, tool_source) + assert not lint_ctx.error_messages + assert lint_ctx.info_messages == ["Tool contains a command."] + assert lint_ctx.warn_messages == ["Command template contains TODO text."] + assert not lint_ctx.valid_messages def test_command_detect_errors_interpreter(lint_ctx): - tool_xml_tree = get_xml_tree(COMMAND_DETECT_ERRORS_INTERPRETER) - run_lint_module(lint_ctx, command, tool_xml_tree) - assert "Command uses deprecated 'interpreter' attribute." in lint_ctx.warn_messages - assert "Tool contains a command with interpreter of type [python]." in lint_ctx.info_messages - assert "Unknown detect_errors attribute [nonsense]" in lint_ctx.warn_messages - assert "Command is empty." in lint_ctx.error_messages + tool_source = get_xml_tool_source(COMMAND_DETECT_ERRORS_INTERPRETER) + run_lint_module(lint_ctx, command, tool_source) + assert lint_ctx.error_messages == ["Command is empty."] + assert lint_ctx.warn_messages == ["Command uses deprecated 'interpreter' attribute."] + assert lint_ctx.info_messages == ["Tool contains a command with interpreter of type [python]."] + assert not lint_ctx.valid_messages def test_general_missing_tool_id_name_version(lint_ctx):