Files
galaxy/test/unit/tool_util/test_tool_linters.py
T
Matthias Bernt c201daa7a8 remove linter for unstripped text content for tool xml leaves
reverts #17656

we have to many xml nodes where this is allowed (e.g. command)
and limiting the linter to a statically defined list of node types
seemed difficult to maintain

replaced this with a change in the xsd that should lead to the
automatic removal of surplus white spaces (leading/trailing are removed,
multiple whitespaces are replaced by a single space) for some of the
node types that were the original motivation for the linter
(if this proves useful this could be used for more node types).
2024-05-29 15:29:00 +02:00

2207 lines
82 KiB
Python

import inspect
import os
import tempfile
import pytest
import galaxy.tool_util.linters
from galaxy.tool_util.lint import (
lint_tool_source_with,
lint_tool_source_with_modules,
LintContext,
Linter,
XMLLintMessageLine,
XMLLintMessageXPath,
)
from galaxy.tool_util.linters import (
citations,
command,
general,
help,
inputs,
output,
stdio,
tests,
xml_order,
xsd,
)
from galaxy.tool_util.loader_directory import load_tool_sources_from_path
from galaxy.tool_util.parser.xml import XmlToolSource
from galaxy.util import (
ElementTree,
submodules,
)
from galaxy.util.unittest_utils import skip_if_site_down
from galaxy.util.xml_macros import load_with_references
# TODO tests tool xml for general linter
# tests tool xml for citations linter
CITATIONS_MULTIPLE = """
<tool id="id" name="name">
<citations/>
<citations/>
</tool>
"""
CITATIONS_ABSENT = """
<tool id="id" name="name"/>
"""
CITATIONS_ERRORS = """
<tool id="id" name="name">
<citations>
<citation type="doi"> </citation>
</citations>
</tool>
"""
CITATIONS_VALID = """
<tool id="id" name="name">
<citations>
<citation type="doi">DOI</citation>
</citations>
</tool>
"""
# tests tool xml for command linter
COMMAND_MULTIPLE = """
<tool id="id" name="name">
<command>ls</command>
<command>df</command>
</tool>
"""
COMMAND_MISSING = """
<tool id="id" name="name"/>
"""
COMMAND_TODO = """
<tool id="id" name="name">
<command>
## TODO
</command>
</tool>
"""
COMMAND_DETECT_ERRORS_INTERPRETER = """
<tool id="id" name="name">
<command detect_errors="nonsense" interpreter="python"/>
</tool>
"""
# tests tool xml for general linter
GENERAL_MISSING_TOOL_ID_NAME_VERSION = """
<tool profile="2109">
</tool>
"""
GENERAL_WHITESPACE_IN_VERSIONS_AND_NAMES = """
<tool name=" BWA Mapper " id="bwa tool" version=" 1.0.1 " display_interface="true" require_login="true" hidden="true">
<requirements>
<requirement type="package" version=" 1.2.5 "> bwa </requirement>
</requirements>
</tool>
"""
GENERAL_REQUIREMENT_WO_VERSION = """
<tool name="BWA Mapper" id="bwa_tool" version="1.0.1blah" display_interface="true" require_login="true" hidden="true" profile="20.09">
<requirements>
<requirement type="package">bwa</requirement>
<requirement type="package" version="1.2.5"></requirement>
</requirements>
</tool>
"""
GENERAL_VALID = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="21.09">
</tool>
"""
GENERAL_VALID_NEW_PROFILE_FMT = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="23.0">
</tool>
"""
GENERAL_VALID_BIOTOOLS = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="23.0">
<xrefs>
<xref type="bio.tools">bwa</xref>
<xref type="bio.tools">johnscoolbowtie</xref>
</xrefs>
</tool>
"""
GENERAL_VALID_EDAM = """
<tool name="valid name" id="valid_id" version="1.0+galaxy1" profile="23.0">
<edam_topics>
<edam_topic>topic_2269</edam_topic>
<edam_topic>topic_000</edam_topic>
</edam_topics>
<edam_operations>
<edam_operation>operation_3434</edam_operation>
<edam_operation>operation_000</edam_operation>
</edam_operations>
</tool>
"""
# test tool xml for help linter
HELP_MULTIPLE = """
<tool id="id" name="name">
<help>Help</help>
<help>More help</help>
</tool>
"""
HELP_ABSENT = """
<tool id="id" name="name">
</tool>
"""
HELP_EMPTY = """
<tool id="id" name="name">
<help> </help>
</tool>
"""
HELP_TODO = """
<tool id="id" name="name">
<help>TODO</help>
</tool>
"""
HELP_INVALID_RST = """
<tool id="id" name="name">
<help>
**xxl__
</help>
</tool>
"""
# test tool xml for inputs linter
INPUTS_NO_INPUTS = """
<tool id="id" name="name">
</tool>
"""
INPUTS_NO_INPUTS_DATASOURCE = """
<tool tool_type="data_source">
<inputs/>
</tool>
"""
INPUTS_VALID = """
<tool id="id" name="name">
<inputs>
<param name="txt_param" type="text"/>
<param name="int_param" type="integer"/>
</inputs>
</tool>
"""
INPUTS_PARAM_NAME = """
<tool id="id" name="name">
<inputs>
<param type="text"/>
<param name="" type="text"/>
<param name="2" type="text"/>
<param argument="--valid" type="text"/>
<param name="param_name" argument="--param-name" type="text"/>
</inputs>
</tool>
"""
INPUTS_PARAM_TYPE = """
<tool id="id" name="name">
<inputs>
<param name="valid_name"/>
<param argument="--another-valid-name" type=""/>
</inputs>
</tool>
"""
INPUTS_DATA_PARAM = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="data"/>
</inputs>
</tool>
"""
INPUTS_DATA_PARAM_OPTIONS = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="data" format="txt">
<options>
<filter type="data_meta" key="dbkey" ref="input"/>
</options>
</param>
</inputs>
</tool>
"""
INPUTS_DATA_PARAM_OPTIONS_FILTER_ATTRIBUTE = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="data" format="txt">
<options options_filter_attribute="metadata.foo">
<filter type="data_meta" key="foo" ref="input"/>
</options>
</param>
</inputs>
</tool>
"""
INPUTS_DATA_PARAM_INVALIDOPTIONS = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="data" format="txt">
<options/>
<options from_file="blah">
<filter type="regexp"/>
</options>
</param>
</inputs>
</tool>
"""
INPUTS_BOOLEAN_PARAM_SWAPPED_LABELS = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="boolean" truevalue="false" falsevalue="true" />
</inputs>
</tool>
"""
INPUTS_BOOLEAN_PARAM_DUPLICATE_LABELS = """
<tool id="id" name="name">
<inputs>
<param name="valid_name" type="boolean" truevalue="--foo" falsevalue="--foo" />
</inputs>
</tool>
"""
INPUTS_CONDITIONAL = """
<tool id="id" name="name">
<inputs>
<conditional>
<param name="select" type="select"/>
</conditional>
<conditional name="cond_wo_param">
</conditional>
<conditional name="cond_w_mult_param">
<param name="select3" type="select"><option value="A">A</option><option value="B">B</option></param>
<param name="select4" type="select"><option value="A">A</option><option value="B">B</option></param>
<when value="A"/>
<when value="B"/>
</conditional>
<conditional name="cond_boolean">
<param name="bool" type="boolean"/>
<when value="true"/>
<when value="false"/>
<when value="False"/>
</conditional>
<conditional name="cond_text">
<param name="text" type="text"/>
</conditional>
<conditional name="cond_w_optional_select">
<param name="optionalselect" type="select" optional="true"><option value="A">A</option><option value="B">B</option></param>
<when value="A"/>
<when value="B"/>
</conditional>
<conditional name="cond_w_multiple_select">
<param name="multipleselect" type="select" multiple="true"><option value="A">A</option><option value="B">B</option></param>
<when value="A"/>
<when value="B"/>
</conditional>
<conditional name="when_wo_value">
<param name="select3" type="select"><option value="A">A</option><option value="B">B</option></param>
<when/>
<when value="A"/>
<when value="B"/>
</conditional>
<conditional name="missing_when">
<param name="label_select" type="select">
<option value="none" selected="True">None</option>
</param>
</conditional>
<conditional name="missing_option">
<param name="missing_option" type="select">
<option value="none" selected="True">None</option>
</param>
<when value="none"/>
<when value="absent"/>
</conditional>
</inputs>
</tool>
"""
INPUTS_SELECT_INCOMPATIBLE_DISPLAY = """
<tool id="id" name="name">
<inputs>
<param name="radio_select" type="select" display="radio" optional="true" multiple="true">
<option value="1">1</option>
<option value="2">2</option>
</param>
<param name="checkboxes_select" type="select" display="checkboxes" optional="false" multiple="false">
<option value="1">1</option>
<option value="2">2</option>
</param>
<!-- this must not raise any warning/error since multiple=true implies true as default for optional -->
<param name="checkboxes_select_correct" type="select" display="checkboxes" multiple="true">
<option value="1">1</option>
<option value="2">2</option>
</param>
</inputs>
</tool>
"""
INPUTS_SELECT_DUPLICATED_OPTIONS = """
<tool id="id" name="name">
<inputs>
<param name="select" type="select" optional="true" multiple="true">
<option value="v">x</option>
<option value="v">x</option>
</param>
</inputs>
</tool>
"""
SELECT_DUPLICATED_OPTIONS_WITH_DIFF_SELECTED = """
<tool id="id" name="name">
<inputs>
<param name="select" type="select" optional="true" multiple="true">
<option value="v">x</option>
<option value="v" selected="true">x</option>
</param>
</inputs>
</tool>
"""
INPUTS_SELECT_DEPRECATIONS = """
<tool id="id" name="name">
<inputs>
<param name="select_do" type="select" dynamic_options="blah()"/>
<param name="select_ff" type="select">
<options from_file="file.tsv" transform_lines="narf()"/>
</param>
<param name="select_fp" type="select">
<options from_parameter="select_do" options_filter_attribute="fasel"/>
</param>
</inputs>
</tool>
"""
INPUTS_SELECT_OPTION_DEFINITIONS = """
<tool id="id" name="name">
<inputs>
<param name="select_noopt" type="select"/>
<param name="select_noopts" type="select">
<options/>
</param>
<param name="select_fd_op" type="select">
<options from_dataset="xyz"/>
<options from_data_table="xyz"/>
<option value="x">x</option>
</param>
<param name="select_fd_fdt" type="select">
<options from_dataset="xyz" from_data_table="xyz"/>
</param>
<param name="select_noval_notext" type="select">
<option>option wo value</option>
<option value="value"/>
</param>
<param name="select_meta_file_key_incomp" type="select">
<options from_data_table="xyz" meta_file_key="dbkey"/>
</param>
</inputs>
</tool>
"""
INPUTS_SELECT_FILTER = """
<tool id="id" name="name">
<inputs>
<param name="select_filter_types" type="select">
<options from_data_table="xyz">
<filter/>
<filter type="unknown_filter_type"/>
</options>
</param>
</inputs>
</tool>
"""
INPUTS_VALIDATOR_INCOMPATIBILITIES = """
<tool id="id" name="name">
<inputs>
<param name="param_name" type="text">
<validator type="in_range">TEXT</validator>
<validator type="regex" filename="blah"/>
<validator type="expression"/>
<validator type="regex">[</validator>
<validator type="expression">(</validator>
<validator type="expression">value and "," not in value</validator>
<validator type="value_in_data_table"/>
</param>
<param name="another_param_name" type="data" format="bed">
<validator type="metadata" value="value"/>
<validator type="dataset_metadata_equal" value="value" value_json="['1', '2']" metadata_name="columns"/>
</param>
</inputs>
</tool>
"""
INPUTS_VALIDATOR_CORRECT = """
<tool id="id" name="name">
<inputs>
<param name="data_param" type="data" format="data">
<validator type="metadata" check="md1,md2" skip="md3,md4" message="custom validation message" negate="true"/>
<validator type="dataset_metadata_equal" metadata_name="columns" value="xyz" message="custom validation message %s" negate="true"/>
<validator type="unspecified_build" message="custom validation message" negate="true"/>
<validator type="dataset_ok_validator" message="custom validation message" negate="true"/>
<validator type="dataset_metadata_equal" metadata_name="columns" value="8"/>
<validator type="dataset_metadata_in_range" metadata_name="sequences" min="0" max="100" exclude_min="true" exclude_max="true" message="custom validation message" negate="true"/>
<validator type="dataset_metadata_in_file" filename="file.tsv" metadata_column="3" split="," metadata_name="dbkey" message="custom validation message" negate="true"/>
<validator type="dataset_metadata_in_data_table" table_name="datatable_name" metadata_column="3" metadata_name="dbkey" message="custom validation message" negate="true"/>
</param>
<param name="collection_param" type="data_collection">
<validator type="metadata" check="md1,md2" skip="md3,md4" message="custom validation message"/>
<validator type="unspecified_build" message="custom validation message"/>
<validator type="dataset_ok_validator" message="custom validation message"/>
<validator type="dataset_metadata_in_range" metadata_name="sequences" min="0" max="100" exclude_min="true" exclude_max="true" message="custom validation message"/>
<validator type="dataset_metadata_in_file" filename="file.tsv" metadata_column="3" split="," metadata_name="dbkey" message="custom validation message"/>
<validator type="dataset_metadata_in_data_table" table_name="datatable_name" metadata_column="3" metadata_name="dbkey" message="custom validation message"/>
</param>
<param name="text_param" type="text">
<validator type="regex">reg.xp</validator>
<validator type="length" min="0" max="100" message="custom validation message"/>
<validator type="empty_field" message="custom validation message"/>
<validator type="value_in_data_table" table_name="datatable_name" metadata_column="3" message="custom validation message"/>
<validator type="expression" message="custom validation message">somepythonexpression</validator>
</param>
<param name="select_param" type="select">
<options from_data_table="bowtie2_indexes"/>
<validator type="no_options" negate="true"/>
<validator type="regex" negate="true">reg.xp</validator>
<validator type="length" min="0" max="100" message="custom validation message" negate="true"/>
<validator type="empty_field" message="custom validation message" negate="true"/>
<validator type="value_in_data_table" table_name="datatable_name" metadata_column="3" message="custom validation message" negate="true"/>
<validator type="expression" message="custom validation message" negate="true">somepythonexpression</validator>
</param>
<param name="int_param" type="integer">
<validator type="in_range" min="0" max="100" exclude_min="true" exclude_max="true" negate="true"/>
<validator type="expression" message="custom validation message">somepythonexpression</validator>
</param>
</inputs>
</tool>
"""
INPUTS_TYPE_CHILD_COMBINATIONS = """
<tool id="id" name="name">
<inputs>
<param name="text_param" type="text">
<options/>
</param>
<param name="select_param" type="select">
<options from_data_table="data_table">
<option name="x" value="y"/>
</options>
</param>
<param name="data_param" type="data" format="tabular">
<options>
<column name="x" index="0"/>
</options>
</param>
<param name="int_param" type="integer">
<options>
<column name="x" index="0"/>
</options>
</param>
</inputs>
</tool>
"""
INPUTS_DUPLICATE_NAMES = """
<tool id="id" name="name">
<inputs>
<param name="dup" type="text"/>
<param name="dup" type="text"/>
<param name="dup_in_section" type="text"/>
<section name="sec" title="">
<param name="dup_in_section" type="text"/>
</section>
<conditional name="cond">
<param name="dup_in_cond" type="select">
<option value="a">a</option>
<option value="b">b</option>
</param>
<when value="a">
<param name="dup_in_cond" type="text"/>
</when>
<when value="b">
<param name="dup_in_cond" type="text"/>
</when>
</conditional>
<param name="dup_in_output" type="text"/>
</inputs>
<outputs>
<data name="dup_in_output"/>
</outputs>
</tool>
"""
# test tool xml for outputs linter
OUTPUTS_MISSING = """
<tool id="id" name="name"/>
"""
OUTPUTS_MULTIPLE = """
<tool id="id" name="name">
<outputs/>
<outputs/>
</tool>
"""
OUTPUTS_UNKNOWN_TAG = """
<tool id="id" name="name">
<outputs>
<output name="name"/>
</outputs>
</tool>
"""
OUTPUTS_UNNAMED_INVALID_NAME = """
<tool id="id" name="name">
<outputs>
<data label="data out"/>
<collection type="list" format="txt" label="collection out"/>
<collection name="2output" label="coll out"/>
</outputs>
</tool>
"""
OUTPUTS_FORMAT_INPUT_LEGACY = """
<tool id="id" name="name">
<outputs>
<data name="valid_name" format="input"/>
</outputs>
</tool>
"""
OUTPUTS_FORMAT_INPUT = """
<tool id="id" name="name" profile="16.04">
<outputs>
<data name="valid_name" format="input"/>
</outputs>
</tool>
"""
# check that linter accepts format source for collection elements as means to specify format
# and that the linter warns if format and format_source are used
OUTPUTS_COLLECTION_FORMAT_SOURCE = """
<tool id="id" name="name">
<outputs>
<collection name="output_collection" type="paired">
<data name="forward" format_source="input_readpair" />
<data name="reverse" format_source="input_readpair" format="fastq"/>
</collection>
</outputs>
</tool>
"""
# check that setting format with actions is supported
OUTPUTS_FORMAT_ACTION = """
<tool id="id" name="name">
<outputs>
<data name="output">
<actions>
<conditional name="library.type">
<when value="paired">
<action type="format">
<option type="from_param" name="library.input_2" param_attribute="ext" />
</action>
</when>
</conditional>
</actions>
</data>
</outputs>
</tool>
"""
# check that linter does not complain about missing format if from_tool_provided_metadata is used
OUTPUTS_DISCOVER_TOOL_PROVIDED_METADATA = """
<tool id="id" name="name">
<command>galaxy.json</command>
<outputs>
<data name="output">
<discover_datasets/>
</data>
</outputs>
</tool>
"""
OUTPUTS_DUPLICATED_NAME_LABEL = """
<tool id="id" name="name">
<outputs>
<data name="valid_name" format="fasta"/>
<data name="valid_name" format="fasta"/>
<data name="another_valid_name" format="fasta" label="same label may be OK if there is a filter">
<filter>a condition</filter>
</data>
<data name="yet_another_valid_name" format="fasta" label="same label may be OK if there is a filter">
<filter>another condition</filter>
</data>
</outputs>
</tool>
"""
# tool xml for repeats linter
REPEATS = """
<tool id="id" name="name">
<inputs>
<repeat>
<param name="another_param_name" type="data" format="bed"/>
</repeat>
</inputs>
</tool>
"""
# tool xml for stdio linter
STDIO_DEFAULT_FOR_DEFAULT_PROFILE = """
<tool id="id" name="name">
</tool>
"""
STDIO_DEFAULT_FOR_NONLEGACY_PROFILE = """
<tool id="id" name="name" profile="21.09">
</tool>
"""
STDIO_MULTIPLE_STDIO = """
<tool id="id" name="name">
<stdio/>
<stdio/>
</tool>
"""
STDIO_INVALID_CHILD_OR_ATTRIB = """
<tool id="id" name="name">
<stdio>
<regex descriptio="blah" level="fatal" match="error" source="stdio"/>
<exit_code descriptio="blah" level="fatal" range="1:"/>
<reqex/>
</stdio>
</tool>
"""
STDIO_INVALID_MATCH = """
<tool id="id" name="name">
<stdio>
<regex match="["/>
</stdio>
</tool>
"""
# check that linter does complain about tests wo assumptions
TESTS_ABSENT = """
<tool id="id" name="name"/>
"""
TESTS_ABSENT_DATA_SOURCE = """
<tool id="id" name="name" tool_type="data_source"/>
"""
TESTS_WO_EXPECTATIONS = """
<tool id="id" name="name">
<tests>
<test>
</test>
</tests>
</tool>
"""
TESTS_PARAM_OUTPUT_NAMES = """
<tool id="id" name="name">
<inputs>
<param argument="--existent-test-name" type="text"/>
<conditional name="cond">
<param name="cond_select" type="select"/>
<when value="blah">
<param name="another_existent_test_name" type="text"/>
</when>
</conditional>
</inputs>
<outputs>
<data name="existent_output"/>
<collection name="existent_collection"/>
</outputs>
<tests>
<test expect_num_outputs="1">
<param/>
<param name="existent_test_name"/>
<param name="cond_name|another_existent_test_name"/>
<param name="non_existent_test_name"/>
<output/>
<output name="existent_output"/>
<output name="nonexistent_output"/>
<output_collection/>
<output_collection name="existent_collection"/>
<output_collection name="nonexistent_collection"/>
</test>
</tests>
</tool>
"""
TESTS_EXPECT_FAILURE_OUTPUT = """
<tool id="id" name="name">
<outputs>
<data name="test"/>
</outputs>
<tests>
<test expect_failure="true">
<output name="test"/>
</test>
<test expect_num_outputs="1" expect_failure="true"/>
</tests>
</tool>
"""
ASSERTS = """
<tool id="id" name="name">
<outputs>
<data name="out_archive"/>
<data name="out_tabular"/>
</outputs>
<tests>
<test>
<assert_stdout>
<has_text text="blah" n="1"/>
<has_line line="blah"/>
</assert_stdout>
<assert_stderr>
<invalid/>
</assert_stderr>
<assert_command>
<has_size value="500k" size="50"/>
<has_text invalid_attrib="blah"/>
</assert_command>
<output name="out_archive">
<assert_contents>
<has_size value="500k" delta="1X"/>
<has_archive_member path=".*/my-file.txt">
<not_has_text invalid_attrib_also_checked_in_nested_asserts="Blah" text="EDK72998.1" />
</has_archive_member>
</assert_contents>
</output>
<output name="out_tabular">
<assert_contents>
<has_size/>
<has_n_columns/>
<has_n_lines/>
</assert_contents>
</output>
</test>
</tests>
</tool>
"""
TESTS_VALID = """
<tool id="id" name="name">
<outputs>
<data name="test"/>
</outputs>
<tests>
<test>
<output name="test"/>
</test>
</tests>
</tool>
"""
TESTS_OUTPUT_TYPE_MISMATCH = """
<tool id="id" name="name">
<outputs>
<data name="data_name"/>
<collection name="collection_name" type="list:list"/>
</outputs>
<tests>
<test>
<output_collection name="data_name"/>
<output name="collection_name"/>
</test>
</tests>
</tool>
"""
TESTS_DISCOVER_OUTPUTS = """
<tool id="id" name="name">
<outputs>
<data name="data_name">
<discover_datasets/>
</data>
<collection name="collection_name" type="list:list">
<discover_datasets/>
</collection>
</outputs>
<tests>
<!-- this should be fine -->
<test>
<output name="data_name">
<discovered_dataset/>
</output>
<output_collection name="collection_name">
<element count="2"/>
</output_collection>
</test>
<!-- this should be fine as well -->
<test>
<output name="data_name" count="2"/>
<output_collection name="collection_name">
<element>
<element/>
</element>
</output_collection>
</test>
<!-- no count or discovered_dataset/element -->
<test>
<output name="data_name"/>
<output_collection name="collection_name"/>
</test>
<!-- no count or discovered_dataset/element
- no outputs can be given
- consequently also counts and expect_num_output need not to be given -->
<test expect_failure="true"/>
<!-- no nested element and count at element -->
<test>
<output name="data_name" count="1"/>
<output_collection name="collection_name" count="1">
<element/>
</output_collection>
</test>
</tests>
</tool>
"""
TESTS_EXPECT_NUM_OUTPUTS_FILTER = """
<tool id="id" name="name">
<outputs>
<data name="name">
<filter/>
</data>
</outputs>
<tests>
<test expect_failure="false">
</test>
</tests>
</tool>
"""
TESTS_COMPARE_ATTRIB_INCOMPATIBILITY = """
<tool id="id" name="name">
<outputs>
<data name="data_name"/>
<collection name="collection_name" type="list:list"/>
</outputs>
<tests>
<test>
<output name="data_name" compare="re_match" decompress="true"/>
<output_collection name="collection_name">
<element compare="contains" sort="true" />
</output_collection>
</test>
<test>
<output name="data_name" compare="diff" lines_diff="2"/>
<output_collection name="collection_name">
<element compare="contains" lines_diff="2" />
</output_collection>
</test>
</tests>
</tool>"""
# tool xml for xml_order linter
XML_ORDER = """
<tool id="id" name="name">
<wrong_tag/>
<command/>
<stdio/>
</tool>
"""
TOOL_WITH_COMMENTS = """
<tool id="id" name="name" version="1" profile="22.01">
<stdio>
<!-- This is a comment -->
<regex match="low space" source="both" level="warning" description="Low space on device" />
</stdio>
<outputs>
<!-- This is a comment -->
<data format="pdf" name="out_file1" />
</outputs>
</tool>
"""
@pytest.fixture()
def lint_ctx():
return LintContext("all", lint_message_class=XMLLintMessageLine)
@pytest.fixture()
def lint_ctx_xpath():
return LintContext("all", lint_message_class=XMLLintMessageXPath)
def get_xml_tree(xml_string: str) -> ElementTree:
with tempfile.NamedTemporaryFile(mode="w", suffix="tool.xml") as tmp:
tmp.write(xml_string)
tmp.flush()
tool_path = tmp.name
return load_with_references(tool_path)[0]
def get_xml_tool_source(xml_string: str) -> XmlToolSource:
return XmlToolSource(get_xml_tree(xml_string))
def run_lint_module(lint_ctx, lint_module, lint_target):
lint_tool_source_with_modules(lint_ctx, lint_target, list({lint_module, xsd}))
def run_lint(lint_ctx, lint_func, lint_target):
lint_ctx.lint(name="test_lint", lint_func=lint_func, lint_target=lint_target)
# check if the lint messages have the line
for message in lint_ctx.message_list:
assert message.line is not None, f"No context found for message: {message.message}"
def test_citations_multiple(lint_ctx):
tool_source = get_xml_tool_source(CITATIONS_MULTIPLE)
run_lint_module(lint_ctx, citations, tool_source)
assert lint_ctx.error_messages == ["Invalid XML: Element 'citations': This element is not expected."]
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
def test_citations_absent(lint_ctx):
tool_source = get_xml_tool_source(CITATIONS_ABSENT)
run_lint_module(lint_ctx, citations, tool_source)
assert lint_ctx.warn_messages == ["No citations found, consider adding citations to your tool."]
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.error_messages
def test_citations_errors(lint_ctx):
tool_source = get_xml_tool_source(CITATIONS_ERRORS)
run_lint_module(lint_ctx, citations, tool_source)
assert lint_ctx.error_messages == ["Empty doi citation."]
assert not lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 1
def test_citations_valid(lint_ctx):
tool_source = get_xml_tool_source(CITATIONS_VALID)
run_lint_module(lint_ctx, citations, tool_source)
assert "Found 1 citations." in lint_ctx.valid_messages
assert len(lint_ctx.valid_messages) == 1
assert not lint_ctx.info_messages
assert not lint_ctx.error_messages
def test_command_multiple(lint_ctx):
tool_source = get_xml_tool_source(COMMAND_MULTIPLE)
run_lint_module(lint_ctx, command, tool_source)
assert lint_ctx.error_messages == ["Invalid XML: Element 'command': This element is not expected."]
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_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_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_source = get_xml_tool_source(COMMAND_DETECT_ERRORS_INTERPRETER)
run_lint_module(lint_ctx, command, tool_source)
assert "Command is empty." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'command', attribute 'detect_errors': [facet 'enumeration'] The value 'nonsense' is not an element of the set {'default', 'exit_code', 'aggressive'}."
in lint_ctx.error_messages
)
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
assert len(lint_ctx.error_messages) == 2
def test_general_missing_tool_id_name_version(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_MISSING_TOOL_ID_NAME_VERSION)
run_lint_module(lint_ctx, general, tool_source)
assert "Tool version is missing or empty." in lint_ctx.error_messages
assert "Tool name is missing or empty." in lint_ctx.error_messages
assert "Tool does not define an id attribute." in lint_ctx.error_messages
assert "Tool specifies an invalid profile version [2109]." in lint_ctx.error_messages
def test_general_whitespace_in_versions_and_names(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_WHITESPACE_IN_VERSIONS_AND_NAMES)
run_lint_module(lint_ctx, general, tool_source)
assert "Tool version is pre/suffixed by whitespace, this may cause errors: [ 1.0.1 ]." in lint_ctx.warn_messages
assert "Tool name is pre/suffixed by whitespace, this may cause errors: [ BWA Mapper ]." in lint_ctx.warn_messages
assert "Requirement version contains whitespace, this may cause errors: [ 1.2.5 ]." in lint_ctx.warn_messages
assert "Tool ID contains whitespace - this is discouraged: [bwa tool]." in lint_ctx.warn_messages
assert "Tool targets 16.01 Galaxy profile." in lint_ctx.valid_messages
def test_general_requirement_without_version(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_REQUIREMENT_WO_VERSION)
run_lint_module(lint_ctx, general, tool_source)
assert "Tool version [1.0.1blah] is not compliant with PEP 440." in lint_ctx.warn_messages
assert "Requirement bwa defines no version" in lint_ctx.warn_messages
assert "Requirement without name found" in lint_ctx.error_messages
assert "Tool specifies profile version [20.09]." in lint_ctx.valid_messages
assert "Tool defines an id [bwa_tool]." in lint_ctx.valid_messages
assert "Tool defines a name [BWA Mapper]." in lint_ctx.valid_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 3
assert len(lint_ctx.warn_messages) == 2
assert len(lint_ctx.error_messages) == 1
def test_general_valid(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_VALID)
run_lint_module(lint_ctx, general, tool_source)
assert "Tool defines a version [1.0+galaxy1]." in lint_ctx.valid_messages
assert "Tool specifies profile version [21.09]." in lint_ctx.valid_messages
assert "Tool defines an id [valid_id]." in lint_ctx.valid_messages
assert "Tool defines a name [valid name]." in lint_ctx.valid_messages
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_new_profile_fmt(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_VALID_NEW_PROFILE_FMT)
run_lint_module(lint_ctx, general, tool_source)
assert "Tool defines a version [1.0+galaxy1]." in lint_ctx.valid_messages
assert "Tool specifies profile version [23.0]." in lint_ctx.valid_messages
assert "Tool defines an id [valid_id]." in lint_ctx.valid_messages
assert "Tool defines a name [valid name]." in lint_ctx.valid_messages
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
@skip_if_site_down("https://bio.tools/")
def test_general_valid_biotools(lint_ctx):
tool_source = get_xml_tool_source(GENERAL_VALID_BIOTOOLS)
run_lint_module(lint_ctx, general, tool_source)
assert "No entry johnscoolbowtie in bio.tools." 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
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)
assert "No entry 'operation_000' in EDAM." in lint_ctx.warn_messages
assert "No entry 'topic_000' in EDAM." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 4
assert len(lint_ctx.warn_messages) == 2
# accept 2 xsd errors due to malformed topic/operation ID
# which should make sure that the id never becomes valid
assert len(lint_ctx.error_messages) == 2
def test_help_multiple(lint_ctx):
tool_source = get_xml_tool_source(HELP_MULTIPLE)
run_lint_module(lint_ctx, help, tool_source)
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 2 # has help and valid rst
assert not lint_ctx.warn_messages
assert lint_ctx.error_messages == ["Invalid XML: Element 'help': This element is not expected."]
def test_help_absent(lint_ctx):
tool_source = get_xml_tool_source(HELP_ABSENT)
run_lint_module(lint_ctx, help, tool_source)
assert "No help section found, consider adding a help section to your tool." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_help_empty(lint_ctx):
tool_source = get_xml_tool_source(HELP_EMPTY)
run_lint_module(lint_ctx, help, tool_source)
assert "Help section appears to be empty." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_help_todo(lint_ctx):
tool_source = get_xml_tool_source(HELP_TODO)
run_lint_module(lint_ctx, help, tool_source)
assert "Tool contains help section." in lint_ctx.valid_messages
assert "Help contains valid reStructuredText." in lint_ctx.valid_messages
assert "Help contains TODO text." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 2
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_help_invalid_rst(lint_ctx):
tool_source = get_xml_tool_source(HELP_INVALID_RST)
run_lint_module(lint_ctx, help, tool_source)
assert "Tool contains help section." in lint_ctx.valid_messages
assert (
"Invalid reStructuredText found in help - [<string>:2: (WARNING/2) Inline strong start-string without end-string.\n]."
in lint_ctx.warn_messages
)
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 1
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_inputs_no_inputs(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_NO_INPUTS)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found no input parameters." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_inputs_no_inputs_datasource(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_NO_INPUTS_DATASOURCE)
run_lint_module(lint_ctx, inputs, tool_source)
assert "No input parameters, OK for data sources" in lint_ctx.info_messages
assert "display tag usually present in data sources" in lint_ctx.info_messages
assert "uihints tag usually present in data sources" in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 3
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_valid(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_VALID)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 2 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_param_name(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_PARAM_NAME)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 5 input parameters." in lint_ctx.info_messages
assert "Param input [2] is not a valid Cheetah placeholder." in lint_ctx.warn_messages
assert "Found param input with no name specified." in lint_ctx.error_messages
assert "Param input with empty name." in lint_ctx.error_messages
assert (
"Param input [param_name] 'name' attribute is redundant if argument implies the same name."
in lint_ctx.warn_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 2
assert len(lint_ctx.error_messages) == 2
def test_inputs_param_type(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_PARAM_TYPE)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 2 input parameters." in lint_ctx.info_messages
assert "Invalid XML: Element 'param': The attribute 'type' is required but missing." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'param', attribute 'type': [facet 'enumeration'] The value '' is not an element of the set {'text', 'integer', 'float', 'color', 'boolean', 'genomebuild', 'select', 'data_column', 'hidden', 'hidden_data', 'baseurl', 'file', 'data', 'drill_down', 'group_tag', 'data_collection', 'directory_uri'}."
in lint_ctx.error_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 2
def test_inputs_data_param(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_DATA_PARAM)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 1 input parameters." in lint_ctx.info_messages
assert (
"Param input [valid_name] with no format specified - 'data' format will be assumed." in lint_ctx.warn_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_inputs_boolean_param(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_BOOLEAN_PARAM_DUPLICATE_LABELS)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 1 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_inputs_data_param_options(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_DATA_PARAM_OPTIONS)
run_lint_module(lint_ctx, inputs, tool_source)
assert not lint_ctx.valid_messages
assert "Found 1 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_data_param_options_filter_attribute(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_DATA_PARAM_OPTIONS_FILTER_ATTRIBUTE)
run_lint_module(lint_ctx, inputs, tool_source)
assert not lint_ctx.valid_messages
assert "Found 1 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_data_param_invalid_options(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_DATA_PARAM_INVALIDOPTIONS)
run_lint_module(lint_ctx, inputs, tool_source)
assert not lint_ctx.valid_messages
assert "Found 1 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.warn_messages
assert "Data parameter [valid_name] contains multiple options elements." in lint_ctx.error_messages
assert "Data parameter [valid_name] filter needs to define a ref attribute" in lint_ctx.error_messages
assert (
'Data parameter [valid_name] for filters only type="data_meta" and key="dbkey" are allowed, found type="regexp" and key="None"'
in lint_ctx.error_messages
)
assert len(lint_ctx.error_messages) == 3
def test_inputs_conditional(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_CONDITIONAL)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 10 input parameters." in lint_ctx.info_messages
assert (
"Invalid XML: Element 'conditional': The attribute 'name' is required but missing." in lint_ctx.error_messages
)
assert (
"Select parameter of a conditional [select] options have to be defined by 'option' children elements."
in lint_ctx.error_messages
)
assert (
"Invalid XML: Element 'param': This element is not expected. Expected is ( when )." in lint_ctx.error_messages
) # 2x param
assert (
"Invalid XML: Element 'conditional': Missing child element(s). Expected is ( param )."
in lint_ctx.error_messages
)
assert 'Conditional [cond_text] first param should have type="select"' in lint_ctx.error_messages
assert (
'Conditional [cond_boolean] first param of type="boolean" is discouraged, use a select'
in lint_ctx.warn_messages
)
assert "Conditional [cond_boolean] no truevalue/falsevalue found for when block 'False'" in lint_ctx.warn_messages
assert 'Conditional [cond_w_optional_select] test parameter cannot be optional="true"' in lint_ctx.warn_messages
assert 'Conditional [cond_w_multiple_select] test parameter cannot be multiple="true"' in lint_ctx.warn_messages
assert "Invalid XML: Element 'when': The attribute 'value' is required but missing." in lint_ctx.error_messages
assert "Conditional [missing_when] no <when /> block found for select option 'none'" in lint_ctx.warn_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 6
assert len(lint_ctx.error_messages) == 6
def test_inputs_select_incompatible_display(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_SELECT_INCOMPATIBLE_DISPLAY)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 3 input parameters." in lint_ctx.info_messages
assert 'Select [radio_select] display="radio" is incompatible with optional="true"' in lint_ctx.error_messages
assert 'Select [radio_select] display="radio" is incompatible with multiple="true"' in lint_ctx.error_messages
assert (
'Select [checkboxes_select] `display="checkboxes"` is incompatible with `optional="false"`, remove the `display` attribute'
in lint_ctx.error_messages
)
assert (
'Select [checkboxes_select] `display="checkboxes"` is incompatible with `multiple="false"`, remove the `display` attribute'
in lint_ctx.error_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 4
def test_inputs_duplicated_options(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_SELECT_DUPLICATED_OPTIONS)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 1 input parameters." in lint_ctx.info_messages
assert "Select parameter [select] has multiple options with the same text content" in lint_ctx.error_messages
assert "Select parameter [select] has multiple options with the same value" in lint_ctx.error_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 2
def test_inputs_duplicated_options_with_different_select(lint_ctx):
tool_source = get_xml_tool_source(SELECT_DUPLICATED_OPTIONS_WITH_DIFF_SELECTED)
run_lint_module(lint_ctx, inputs, tool_source)
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_select_deprecations(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_SELECT_DEPRECATIONS)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 3 input parameters." in lint_ctx.info_messages
assert "Select parameter [select_do] uses deprecated 'dynamic_options' attribute." in lint_ctx.warn_messages
assert "Select parameter [select_ff] options uses deprecated 'from_file' attribute." in lint_ctx.warn_messages
assert "Select parameter [select_fp] options uses deprecated 'from_parameter' attribute." in lint_ctx.warn_messages
assert "Select parameter [select_ff] options uses deprecated 'transform_lines' attribute." in lint_ctx.warn_messages
assert (
"Select parameter [select_fp] options uses deprecated 'options_filter_attribute' attribute."
in lint_ctx.warn_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 5
assert not lint_ctx.error_messages
def test_inputs_select_option_definitions(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_SELECT_OPTION_DEFINITIONS)
run_lint_module(lint_ctx, inputs, tool_source)
run_lint_module(lint_ctx, xsd, tool_source)
assert "Found 6 input parameters." in lint_ctx.info_messages
assert (
"Select parameter [select_noopt] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute."
in lint_ctx.error_messages
)
assert (
"Select parameter [select_noopts] options tag defines no options. Use 'from_dataset', 'from_data_table', or a filter that adds values."
in lint_ctx.error_messages
)
assert (
"Select parameter [select_fd_op] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute."
in lint_ctx.error_messages
)
assert "Select parameter [select_fd_op] contains multiple options elements." in lint_ctx.error_messages
assert (
"Select parameter [select_fd_fdt] options uses 'from_dataset' and 'from_data_table' attribute."
in lint_ctx.error_messages
)
assert "Select parameter [select_noval_notext] has option without value" in lint_ctx.error_messages
assert (
"Select parameter [select_meta_file_key_incomp] 'meta_file_key' is only compatible with 'from_dataset'."
in lint_ctx.error_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 7
def test_inputs_select_filter(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_SELECT_FILTER)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 1 input parameters." in lint_ctx.info_messages
assert "Invalid XML: Element 'filter': The attribute 'type' is required but missing." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'filter', attribute 'type': [facet 'enumeration'] The value 'unknown_filter_type' is not an element of the set {'data_meta', 'param_value', 'static_value', 'regexp', 'unique_value', 'multiple_splitter', 'attribute_value_splitter', 'add_value', 'remove_value', 'sort_by'}."
in lint_ctx.error_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 2
def test_inputs_validator_incompatibilities(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_VALIDATOR_INCOMPATIBILITIES)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 2 input parameters." in lint_ctx.info_messages
assert (
"Parameter [param_name]: 'in_range' validators are not expected to contain text (found 'TEXT')"
in lint_ctx.warn_messages
)
assert "Parameter [param_name]: validator with an incompatible type 'in_range'" in lint_ctx.error_messages
assert (
"Parameter [param_name]: 'in_range' validators need to define the 'min' or 'max' attribute(s)"
in lint_ctx.error_messages
)
assert (
"Parameter [param_name]: attribute 'filename' is incompatible with validator of type 'regex'"
in lint_ctx.error_messages
)
assert "Parameter [param_name]: expression validators are expected to contain text" in lint_ctx.error_messages
assert "Parameter [param_name]: regex validators are expected to contain text" in lint_ctx.error_messages
assert (
"Parameter [param_name]: '[' is no valid regex: unterminated character set at position 0"
in lint_ctx.error_messages
)
assert "Parameter [param_name]: '(' is no valid expression" in lint_ctx.error_messages
assert (
"Parameter [another_param_name]: 'metadata' validators need to define the 'check' or 'skip' attribute(s)"
in lint_ctx.error_messages
)
assert (
"Parameter [param_name]: 'value_in_data_table' validators need to define the 'table_name' attribute"
in lint_ctx.error_messages
)
assert (
"Parameter [another_param_name]: attribute 'value' is incompatible with validator of type 'metadata'"
in lint_ctx.error_messages
)
assert (
"Parameter [another_param_name]: 'dataset_metadata_equal' validators must not define the 'value' and the 'value_json' attributes"
in lint_ctx.error_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert len(lint_ctx.error_messages) == 11
def test_inputs_validator_correct(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_VALIDATOR_CORRECT)
run_lint_module(lint_ctx, inputs, tool_source)
assert "Found 5 input parameters." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_inputs_type_child_combinations(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_TYPE_CHILD_COMBINATIONS)
run_lint_module(lint_ctx, inputs, tool_source)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert (
"Parameter [text_param] './options' tags are only allowed for parameters of type ['data', 'select', 'drill_down']"
in lint_ctx.error_messages
)
assert (
"Parameter [select_param] './options/option' tags are only allowed for parameters of type ['drill_down']"
in lint_ctx.error_messages
)
assert (
"Parameter [int_param] './options' tags are only allowed for parameters of type ['data', 'select', 'drill_down']"
in lint_ctx.error_messages
)
assert (
"Parameter [int_param] './options/column' tags are only allowed for parameters of type ['data', 'select']"
in lint_ctx.error_messages
)
assert len(lint_ctx.error_messages) == 4
def test_inputs_duplicate_names(lint_ctx):
tool_source = get_xml_tool_source(INPUTS_DUPLICATE_NAMES)
run_lint_module(lint_ctx, inputs, tool_source)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert "Tool defines multiple parameters with the same name: 'dup'" in lint_ctx.error_messages
assert (
"Tool defines an output with a name equal to the name of an input: 'dup_in_output'" in lint_ctx.error_messages
)
assert len(lint_ctx.error_messages) == 2
def test_inputs_repeats(lint_ctx):
"""
this was previously covered by a linter in inputs
"""
tool_source = get_xml_tool_source(REPEATS)
run_lint_module(lint_ctx, inputs, tool_source)
assert lint_ctx.info_messages == ["Found 1 input parameters."]
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert "Invalid XML: Element 'repeat': The attribute 'name' is required but missing." in lint_ctx.error_messages
assert "Invalid XML: Element 'repeat': The attribute 'title' is required but missing." in lint_ctx.error_messages
assert len(lint_ctx.error_messages) == 2
def test_outputs_missing(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_MISSING)
run_lint_module(lint_ctx, output, tool_source)
assert "Tool contains no outputs section, most tools should produce outputs." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_outputs_multiple(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_MULTIPLE)
run_lint_module(lint_ctx, output, tool_source)
assert "0 outputs found." in lint_ctx.info_messages
assert "Invalid XML: Element 'outputs': This element is not expected." in lint_ctx.error_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 1
def test_outputs_unknown_tag(lint_ctx):
"""
In the past it has been assumed that output tags are not allowed, but only data.
But, actually they are covered by xsd which is also tested here.
Still output tags are not covered in the user facing xml schema docs and should
probably be avoided.
"""
tool_source = get_xml_tool_source(OUTPUTS_UNKNOWN_TAG)
lint_tool_source_with_modules(lint_ctx, tool_source, [output, xsd])
assert "0 outputs found." in lint_ctx.info_messages
assert "Avoid the use of 'output' and replace by 'data' or 'collection'" in lint_ctx.warn_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_outputs_unnamed_invalid_name(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_UNNAMED_INVALID_NAME)
run_lint_module(lint_ctx, output, tool_source)
assert "3 outputs found." in lint_ctx.info_messages
assert "Invalid XML: Element 'data': The attribute 'name' is required but missing." in lint_ctx.error_messages
assert "Invalid XML: Element 'collection': The attribute 'name' is required but missing." in lint_ctx.error_messages
assert "Tool data output with missing name doesn't define an output format." in lint_ctx.warn_messages
assert "Tool output name [2output] is not a valid Cheetah placeholder." in lint_ctx.warn_messages
assert "Collection output with undefined 'type' found." in lint_ctx.warn_messages
assert "Tool collection output 2output doesn't define an output format." in lint_ctx.warn_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 4
assert len(lint_ctx.error_messages) == 2
def test_outputs_format_input_legacy(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_FORMAT_INPUT_LEGACY)
run_lint_module(lint_ctx, output, tool_source)
assert "1 outputs found." in lint_ctx.info_messages
assert "Using format='input' on data is deprecated. Use the format_source attribute." in lint_ctx.warn_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_outputs_format_input(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_FORMAT_INPUT)
run_lint_module(lint_ctx, output, tool_source)
assert "1 outputs found." in lint_ctx.info_messages
assert "Using format='input' on data is deprecated. Use the format_source attribute." in lint_ctx.error_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 1
def test_outputs_collection_format_source(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_COLLECTION_FORMAT_SOURCE)
run_lint_module(lint_ctx, output, tool_source)
assert "Tool data output 'reverse' should use either format_source or format/ext" in lint_ctx.warn_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_outputs_format_action(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_FORMAT_ACTION)
run_lint_module(lint_ctx, output, tool_source)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_outputs_discover_tool_provided_metadata(lint_ctx):
tool_source = get_xml_tool_source(OUTPUTS_DISCOVER_TOOL_PROVIDED_METADATA)
run_lint_module(lint_ctx, output, tool_source)
assert "1 outputs found." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_outputs_duplicated_name_label(lint_ctx):
""" """
tool_source = get_xml_tool_source(OUTPUTS_DUPLICATED_NAME_LABEL)
run_lint_module(lint_ctx, output, tool_source)
assert "4 outputs found." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 2
assert "Tool output [valid_name] uses duplicated label '${tool.name} on ${on_string}'" in lint_ctx.warn_messages
assert (
"Tool output [yet_another_valid_name] uses duplicated label 'same label may be OK if there is a filter', double check if filters imply disjoint cases"
in lint_ctx.warn_messages
)
assert "Tool output [valid_name] has duplicated name" in lint_ctx.error_messages
assert len(lint_ctx.error_messages) == 1
def test_stdio_default_for_default_profile(lint_ctx):
tool_source = get_xml_tool_source(STDIO_DEFAULT_FOR_DEFAULT_PROFILE)
run_lint_module(lint_ctx, stdio, tool_source)
assert (
"No stdio definition found, tool indicates error conditions with output written to stderr."
in lint_ctx.info_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_stdio_default_for_nonlegacy_profile(lint_ctx):
tool_source = get_xml_tool_source(STDIO_DEFAULT_FOR_NONLEGACY_PROFILE)
run_lint_module(lint_ctx, stdio, tool_source)
assert (
"No stdio definition found, tool indicates error conditions with non-zero exit codes." in lint_ctx.info_messages
)
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_stdio_multiple_stdio(lint_ctx):
tool_source = get_xml_tool_source(STDIO_MULTIPLE_STDIO)
run_lint_module(lint_ctx, stdio, tool_source)
assert "Invalid XML: Element 'stdio': This element is not expected." in lint_ctx.error_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 1
def test_stdio_invalid_child_or_attrib(lint_ctx):
"""
note the test name is due to that this was previously covered by linting code in stdio
"""
tool_source = get_xml_tool_source(STDIO_INVALID_CHILD_OR_ATTRIB)
run_lint_module(lint_ctx, xsd, tool_source)
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not len(lint_ctx.warn_messages) == 3
assert "Invalid XML: Element 'reqex': This element is not expected." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'regex', attribute 'source': [facet 'enumeration'] The value 'stdio' is not an element of the set {'stdout', 'stderr', 'both'}."
in lint_ctx.error_messages
)
assert (
"Invalid XML: Element 'regex', attribute 'descriptio': The attribute 'descriptio' is not allowed."
in lint_ctx.error_messages
)
assert (
"Invalid XML: Element 'exit_code', attribute 'descriptio': The attribute 'descriptio' is not allowed."
in lint_ctx.error_messages
)
assert len(lint_ctx.error_messages) == 4
def test_stdio_invalid_match(lint_ctx):
tool_source = get_xml_tool_source(STDIO_INVALID_MATCH)
run_lint_module(lint_ctx, stdio, tool_source)
assert (
"Match '[' is no valid regular expression: unterminated character set at position 0" in lint_ctx.error_messages
)
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 1
def test_tests_absent(lint_ctx):
tool_source = get_xml_tool_source(TESTS_ABSENT)
run_lint_module(lint_ctx, tests, tool_source)
assert "No tests found, most tools should define test cases." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert not lint_ctx.error_messages
def test_tests_data_source(lint_ctx):
tool_source = get_xml_tool_source(TESTS_ABSENT_DATA_SOURCE)
run_lint_module(lint_ctx, tests, tool_source)
assert "No tests found, that should be OK for data_sources." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 1
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_tests_param_output_names(lint_ctx):
tool_source = get_xml_tool_source(TESTS_PARAM_OUTPUT_NAMES)
run_lint_module(lint_ctx, tests, tool_source)
assert "1 test(s) found." in lint_ctx.valid_messages
assert "Invalid XML: Element 'param': The attribute 'name' is required but missing." in lint_ctx.error_messages
assert "Test 1: Test param non_existent_test_name not found in the inputs" in lint_ctx.error_messages
assert "Test 1: Found output tag without a name defined." in lint_ctx.error_messages
assert (
"Test 1: Found output tag with unknown name [nonexistent_output], valid names ['existent_output', 'existent_collection']"
in lint_ctx.error_messages
)
assert (
"Invalid XML: Element 'output_collection': The attribute 'name' is required but missing."
in lint_ctx.error_messages
)
assert (
"Test 1: Found output_collection tag with unknown name [nonexistent_collection], valid names ['existent_output', 'existent_collection']"
in lint_ctx.error_messages
)
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 1
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 6
def test_tests_expect_failure_output(lint_ctx):
tool_source = get_xml_tool_source(TESTS_EXPECT_FAILURE_OUTPUT)
run_lint_module(lint_ctx, tests, tool_source)
assert "No valid test(s) found." in lint_ctx.warn_messages
assert "Test 1: Cannot specify outputs in a test expecting failure." in lint_ctx.error_messages
assert (
"Test 2: Cannot make assumptions on the number of outputs in a test expecting failure."
in lint_ctx.error_messages
)
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 1
assert len(lint_ctx.error_messages) == 2
def test_tests_without_expectations(lint_ctx):
tool_source = get_xml_tool_source(TESTS_WO_EXPECTATIONS)
run_lint_module(lint_ctx, tests, tool_source)
assert (
"Test 1: No outputs or expectations defined for tests, this test is likely invalid." in lint_ctx.warn_messages
)
assert "No valid test(s) found." in lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 2
assert not lint_ctx.error_messages
def test_tests_valid(lint_ctx):
tool_source = get_xml_tool_source(TESTS_VALID)
run_lint_module(lint_ctx, tests, tool_source)
assert "1 test(s) found." in lint_ctx.valid_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 1
assert not lint_ctx.warn_messages
assert not lint_ctx.error_messages
def test_tests_asserts(lint_ctx):
tool_source = get_xml_tool_source(ASSERTS)
run_lint_module(lint_ctx, tests, tool_source)
assert "Invalid XML: Element 'invalid': This element is not expected." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'has_text', attribute 'invalid_attrib': The attribute 'invalid_attrib' is not allowed."
in lint_ctx.error_messages
)
assert "Invalid XML: Element 'has_text': The attribute 'text' is required but missing." in lint_ctx.error_messages
assert (
"Invalid XML: Element 'has_size', attribute 'delta': '1X' is not a valid value of the union type 'Bytes'."
in lint_ctx.error_messages
)
assert (
"Invalid XML: Element 'not_has_text', attribute 'invalid_attrib_also_checked_in_nested_asserts': The attribute 'invalid_attrib_also_checked_in_nested_asserts' is not allowed."
in lint_ctx.error_messages
)
assert "Test 1: 'has_size' needs to specify 'size', 'min', or 'max'" in lint_ctx.error_messages
assert "Test 1: 'has_size' must not specify 'value' and 'size'" in lint_ctx.error_messages
assert "Test 1: 'has_n_columns' needs to specify 'n', 'min', or 'max'" in lint_ctx.error_messages
assert "Test 1: 'has_n_lines' needs to specify 'n', 'min', or 'max'" in lint_ctx.error_messages
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 9
def test_tests_output_type_mismatch(lint_ctx):
tool_source = get_xml_tool_source(TESTS_OUTPUT_TYPE_MISMATCH)
run_lint_module(lint_ctx, tests, tool_source)
assert (
"Test 1: test output collection_name does not correspond to a 'data' output, but a 'collection'"
in lint_ctx.error_messages
)
assert (
"Test 1: test collection output 'data_name' does not correspond to a 'output_collection' output, but a 'data'"
in lint_ctx.error_messages
)
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 2
def test_tests_discover_outputs(lint_ctx):
tool_source = get_xml_tool_source(TESTS_DISCOVER_OUTPUTS)
run_lint_module(lint_ctx, tests, tool_source)
assert (
"Test 3: test output 'data_name' must have a 'count' attribute and/or 'discovered_dataset' children"
in lint_ctx.error_messages
)
assert (
"Test 3: test collection 'collection_name' must have a 'count' attribute or 'element' children"
in lint_ctx.error_messages
)
assert (
"Test 3: test collection 'collection_name' must contain nested 'element' tags and/or element children with a 'count' attribute"
in lint_ctx.error_messages
)
assert (
"Test 5: test collection 'collection_name' must contain nested 'element' tags and/or element children with a 'count' attribute"
in lint_ctx.error_messages
)
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 4
def test_tests_expect_num_outputs_filter(lint_ctx):
tool_source = get_xml_tool_source(TESTS_EXPECT_NUM_OUTPUTS_FILTER)
run_lint_module(lint_ctx, tests, tool_source)
assert "Test 1: should specify 'expect_num_outputs' if outputs have filters" in lint_ctx.warn_messages
assert len(lint_ctx.warn_messages) == 1
assert len(lint_ctx.error_messages) == 0
def test_tests_compare_attrib_incompatibility(lint_ctx):
tool_source = get_xml_tool_source(TESTS_COMPARE_ATTRIB_INCOMPATIBILITY)
run_lint_module(lint_ctx, tests, tool_source)
assert 'Test 1: Attribute decompress is incompatible with compare="re_match".' in lint_ctx.error_messages
assert 'Test 1: Attribute sort is incompatible with compare="contains".' in lint_ctx.error_messages
assert not lint_ctx.info_messages
assert len(lint_ctx.valid_messages) == 1
assert not lint_ctx.warn_messages
assert len(lint_ctx.error_messages) == 2
def test_xml_order(lint_ctx):
tool_source = get_xml_tool_source(XML_ORDER)
run_lint_module(lint_ctx, xml_order, tool_source)
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert lint_ctx.warn_messages == ["Best practice violation [stdio] elements should come before [command]"]
assert lint_ctx.error_messages == ["Invalid XML: Element 'wrong_tag': This element is not expected."]
DATA_MANAGER = """<tool id="test_dm" name="test dm" version="1" tool_type="manage_data">
<inputs>
<param name="select" type="select">
<option value="a">a</option>
<option value="a">a</option>
</param>
</inputs>
</tool>
"""
def test_data_manager(lint_ctx_xpath, lint_ctx):
"""
test that all (not really testing 'all', but more than the general linter
which was the only one applied to data managers until 23.0) linters are applied
"""
tool_source = get_xml_tool_source(DATA_MANAGER)
lint_tool_source_with(lint_ctx, tool_source)
assert "No tests found, most tools should define test cases." in lint_ctx.warn_messages
assert "Tool contains no outputs section, most tools should produce outputs." in lint_ctx.warn_messages
assert "No help section found, consider adding a help section to your tool." in lint_ctx.warn_messages
assert "No citations found, consider adding citations to your tool." in lint_ctx.warn_messages
assert "Select parameter [select] has multiple options with the same text content" in lint_ctx.error_messages
assert "Select parameter [select] has multiple options with the same value" in lint_ctx.error_messages
assert "No command tag found, must specify a command template to execute." in lint_ctx.error_messages
assert lint_ctx.valid_messages
assert len(lint_ctx.warn_messages) == 4
assert len(lint_ctx.error_messages) == 3
COMPLETE = """<tool id="id" name="name">
<macros>
<import>macros.xml</import>
<xml name="test_macro">
<param name="select" type="select">
<option value="a">a</option>
<option value="a">a</option>
</param>
</xml>
<template name="a_template">blah fasel</template>
</macros>
<inputs>
<expand macro="test_macro"/>
<param type="text"/>
<expand macro="test_macro2"/>
</inputs>
</tool>
"""
COMPLETE_MACROS = """<macros>
<xml name="test_macro2">
<param name="No_type"/>
</xml>
</macros>
"""
def test_tool_and_macro_xml(lint_ctx_xpath, lint_ctx):
"""
test linters (all of them via lint_tool_source_with) on a tool and macro xml file
checking a list of asserts, where each assert is a 4-tuple:
- expected message
- filename suffix (tool.xml or macro.xml)
- line number (1 based)
- xpath
"""
with tempfile.TemporaryDirectory() as tmp:
tool_path = os.path.join(tmp, "tool.xml")
macros_path = os.path.join(tmp, "macros.xml")
with open(tool_path, "w") as tmpf:
tmpf.write(COMPLETE)
with open(macros_path, "w") as tmpf:
tmpf.write(COMPLETE_MACROS)
tool_xml, _ = load_with_references(tool_path)
tool_source = XmlToolSource(tool_xml)
# lint once with the lint context using XMLLintMessageXPath and XMLLintMessageLine
lint_tool_source_with(lint_ctx_xpath, tool_source)
lint_tool_source_with(lint_ctx, tool_source)
asserts = (
(
"Select parameter [select] has multiple options with the same value",
5,
"/tool/inputs/param[1]",
"InputsSelectOptionDuplicateValue",
),
("Found param input with no name specified.", 14, "/tool/inputs/param[2]", "InputsName"),
(
"Invalid XML: Element 'param': The attribute 'type' is required but missing.",
3,
"/tool/inputs/param[3]",
"XSD",
),
)
for a in asserts:
message, line, xpath, linter = a
# check message + path combinations
# found = set([(lint_message.message, lint_message.xpath) for lint_message in lint_ctx_xpath.message_list])
# path_asserts = set([(a[0], a[2]) for a in asserts])
found = False
for lint_message in lint_ctx_xpath.message_list:
if lint_message.message != message:
continue
found = True
assert (
lint_message.xpath == xpath
), f"Assumed xpath {xpath}; found xpath {lint_message.xpath} for: {message}"
assert lint_message.linter == linter
assert found, f"Did not find {message}"
for lint_message in lint_ctx.message_list:
if lint_message.message != message:
continue
found = True
assert lint_message.line == line, f"Assumed line {line}; found line {lint_message.line} for: {message}"
assert lint_message.linter == linter
assert found, f"Did not find {message}"
# TODO COPIED from test/unit/app/tools/test_tool_deserialization.py
CWL_TOOL = """
cwlVersion: v1.0
class: CommandLineTool
baseCommand: echo
inputs:
message:
type: string
inputBinding:
position: 1
outputs: []
"""
YAML_TOOL = """
class: GalaxyTool
id: simple_constructs_y
name: simple_constructs_y
version: 1.0
command:
>
echo "$booltest" >> $out_file1;
inputs:
- name: booltest
type: boolean
truevalue: booltrue
falsevalue: boolfalse
checked: false
outputs:
out_file1:
format: txt
"""
def test_linting_yml_tool(lint_ctx):
with tempfile.TemporaryDirectory() as tmp:
tool_path = os.path.join(tmp, "tool.yml")
with open(tool_path, "w") as tmpf:
tmpf.write(YAML_TOOL)
tool_sources = load_tool_sources_from_path(tmp)
assert len(tool_sources) == 1, "Expected 1 tool source"
tool_source = tool_sources[0][1]
lint_tool_source_with(lint_ctx, tool_source)
assert "Tool defines a version [1.0]." in lint_ctx.valid_messages
assert "Tool defines a name [simple_constructs_y]." in lint_ctx.valid_messages
assert "Tool defines an id [simple_constructs_y]." in lint_ctx.valid_messages
assert "Tool specifies profile version [16.04]." in lint_ctx.valid_messages
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_linting_cwl_tool(lint_ctx):
with tempfile.TemporaryDirectory() as tmp:
tool_path = os.path.join(tmp, "tool.cwl")
with open(tool_path, "w") as tmpf:
tmpf.write(CWL_TOOL)
tool_sources = load_tool_sources_from_path(tmp)
assert len(tool_sources) == 1, "Expected 1 tool source"
tool_source = tool_sources[0][1]
lint_tool_source_with(lint_ctx, tool_source)
assert "Tool defines a version [0.0.1]." in lint_ctx.valid_messages
assert "Tool defines a name [tool.cwl]." in lint_ctx.valid_messages
assert "Tool defines an id [tool.cwl]." in lint_ctx.valid_messages
assert "Tool specifies profile version [17.09]." in lint_ctx.valid_messages
assert "CWL appears to be valid." in lint_ctx.info_messages
assert "Description of tool is empty or absent." in lint_ctx.warn_messages
assert "Tool does not specify a DockerPull source." in lint_ctx.warn_messages
assert "Modern CWL version [v1.0]." in lint_ctx.info_messages
assert len(lint_ctx.info_messages) == 2
assert len(lint_ctx.valid_messages) == 4
assert len(lint_ctx.warn_messages) == 2
assert not lint_ctx.error_messages
def test_skip_by_name(lint_ctx):
# add a linter class name to the skip list
lint_ctx.skip_types = ["CitationsMissing"]
tool_source = get_xml_tool_source(CITATIONS_ABSENT)
run_lint_module(lint_ctx, citations, tool_source)
assert not lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.error_messages
def test_skip_by_module(lint_ctx):
# add a module name to the skip list -> all linters in this module are skipped
lint_ctx.skip_types = ["citations"]
tool_source = get_xml_tool_source(CITATIONS_ABSENT)
run_lint_module(lint_ctx, citations, tool_source)
assert not lint_ctx.warn_messages
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.error_messages
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 "Linter" not in linter_names
# make sure that linters from all modules are available
for prefix in [
"Citations",
"Command",
"CWL",
"ToolProfile",
"Help",
"Inputs",
"Outputs",
"StdIO",
"Tests",
"XMLOrder",
"XSD",
]:
assert len([x for x in linter_names if x.startswith(prefix)])
def test_linter_module_list():
linter_modules = submodules.import_submodules(galaxy.tool_util.linters)
linter_module_names = [m.__name__.split(".")[-1] for m in linter_modules]
linter_module_names = [n for n in linter_module_names if not n.startswith("_")]
assert len(linter_module_names) >= 11
# until 23.2 the linters were implemented as functions lint_xyz contained in a module named xyz
# with 24.0 the functions were split in multiple classes (1 per linter message)
# in order to keep the skip functionality of lint contexts working (which is used eg in planemo)
# with the old names, we now also check for module name if a linter should be skipped
# therefore we test here that the module names are not changed
# the keys of the following dict represent the linter names before the switch and the values give
# the number of linter classes when we switched
# so adding new functionality to a linter module is fine. But splitting one or removing functionality
# should raise an error here to point to the possible consequences of renaming
old_linters = {
"citations": 4,
"command": 5,
"cwl": 9,
"general": 17,
"help": 6,
"inputs": 52,
"output": 11,
"stdio": 3,
"tests": 21,
"xml_order": 1,
}
assert len(set(linter_module_names).intersection(set(old_linters.keys()))) == len(old_linters.keys())
for module in linter_modules:
module_name = module.__name__.split(".")[-1]
if module_name not in old_linters:
continue
linter_cnt = 0
for name, value in inspect.getmembers(module):
if callable(value) and name.startswith("lint_"):
continue
elif inspect.isclass(value) and issubclass(value, Linter) and not inspect.isabstract(value):
linter_cnt += 1
assert linter_cnt >= old_linters[module_name]