Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tool linter: check for leaf nodes with unstripped text content #17656

Merged
merged 9 commits into from
May 14, 2024
17 changes: 17 additions & 0 deletions lib/galaxy/tool_util/linters/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,23 @@ def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
)


class TextSpaces(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
_, tool_node = _tool_xml_and_root(tool_source)
if not tool_node:
return
for node in tool_node.iter():
if len(node) > 0:
continue
if node.text and node.text != node.text.strip():
lint_ctx.warn(
f"XML node '{node.tag}' has text with leading or trailing spaces ('{node.text}'!='{node.text.strip()}').",
linter=cls.name(),
node=node,
)


class BioToolsValid(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
Expand Down
60 changes: 38 additions & 22 deletions test/unit/tool_util/test_tool_linters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from galaxy.tool_util.lint import (
lint_tool_source_with,
lint_tool_source_with_modules,
lint_xml_with,
LintContext,
Linter,
XMLLintMessageLine,
Expand All @@ -30,7 +29,6 @@
from galaxy.tool_util.parser.xml import XmlToolSource
from galaxy.util import (
ElementTree,
parse_xml,
submodules,
)
from galaxy.util.unittest_utils import skip_if_site_down
Expand Down Expand Up @@ -122,6 +120,16 @@
</tool>
"""

GENERAL_TEXT_SPACES = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="21.09">
<xrefs>
<xref type="bio.tools">
bwa
</xref>
</xrefs>
</tool>
"""

GENERAL_VALID_BIOTOOLS = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="23.0">
<xrefs>
Expand Down Expand Up @@ -917,12 +925,14 @@
"""

TOOL_WITH_COMMENTS = """
<tool>
<tool id="id" name="name" version="1" profile="22.01">
<stdio>
<!-- This is a comment -->
<!-- This is a comment -->
<regex match="low space" source="both" level="warning" description="Low space on device" />
</stdio>
<outputs>
<!-- This is a comment -->
<!-- This is a comment -->
<data format="pdf" name="out_file1" />
</outputs>
</tool>
"""
Expand Down Expand Up @@ -950,15 +960,6 @@ def get_xml_tool_source(xml_string: str) -> XmlToolSource:
return XmlToolSource(get_xml_tree(xml_string))


def get_tool_xml_exact(xml_string: str):
"""Returns the tool XML as it is, without stripping comments or anything else."""
with tempfile.NamedTemporaryFile(mode="w", suffix="tool.xml") as tmp:
tmp.write(xml_string)
tmp.flush()
tool_path = tmp.name
return parse_xml(tool_path, strip_whitespace=False, remove_comments=False)


def run_lint_module(lint_ctx, lint_module, lint_target):
lint_tool_source_with_modules(lint_ctx, lint_target, list({lint_module, xsd}))

Expand Down Expand Up @@ -1107,6 +1108,19 @@ def test_general_valid_new_profile_fmt(lint_ctx):
assert not lint_ctx.error_messages


def test_general_text_spaces(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_TEXT_SPACES)
run_lint_module(lint_ctx, general, tool_source)
assert (
"XML node 'xref' has text with leading or trailing spaces ('\n bwa\n '!='bwa')"
in lint_ctx.warn_messages
)
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 4
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages


@skip_if_site_down("https://bio.tools/")
def test_general_valid_biotools(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_VALID_BIOTOOLS)
Expand All @@ -1118,6 +1132,15 @@ def test_general_valid_biotools(lint_ctx):
assert not lint_ctx.error_messages


def test_general_text_spaces_comments(lint_ctx):
tool_source = get_xml_tool_source(TOOL_WITH_COMMENTS)
run_lint_module(lint_ctx, general, tool_source)
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 4
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages


def test_general_valid_edam(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_VALID_EDAM)
run_lint_module(lint_ctx, general, tool_source)
Expand Down Expand Up @@ -2118,13 +2141,6 @@ def test_linting_cwl_tool(lint_ctx):
assert not lint_ctx.error_messages


def test_xml_comments_are_ignored(lint_ctx: LintContext):
tool_xml = get_tool_xml_exact(TOOL_WITH_COMMENTS)
lint_xml_with(lint_ctx, tool_xml)
for lint_message in lint_ctx.message_list:
assert "Comment" not in lint_message.message


def test_skip_by_name(lint_ctx):
# add a linter class name to the skip list
lint_ctx.skip_types = ["CitationsMissing"]
Expand Down Expand Up @@ -2152,7 +2168,7 @@ def test_skip_by_module(lint_ctx):
def test_list_linters():
linter_names = Linter.list_listers()
# make sure to add/remove a test for new/removed linters if this number changes
assert len(linter_names) == 132
assert len(linter_names) == 133
assert "Linter" not in linter_names
# make sure that linters from all modules are available
for prefix in [
Expand Down
Loading