diff --git a/lib/galaxy/tool_util/linters/inputs.py b/lib/galaxy/tool_util/linters/inputs.py
index 59d80845cd6..93557997224 100644
--- a/lib/galaxy/tool_util/linters/inputs.py
+++ b/lib/galaxy/tool_util/linters/inputs.py
@@ -66,14 +66,23 @@ def lint_inputs(tool_xml, lint_ctx):
if "name" in param_attrib and "argument" in param_attrib:
if param_attrib.get("name") == _parse_name(None, param_attrib.get("argument")):
lint_ctx.warn(f"Param input [{param_name}] 'name' attribute is redundant if argument implies the same name.")
+ if param_name.strip() == "":
+ lint_ctx.error("Param input with empty name.", line=param.sourceline, xpath=tool_xml.getpath(param))
+ elif not is_valid_cheetah_placeholder(param_name):
+ lint_ctx.warn(f"Param input [{param_name}] is not a valid Cheetah placeholder.", line=param.sourceline, xpath=tool_xml.getpath(param))
+
+ # TODO lint for params with duplicated name (in inputs & outputs)
if "type" not in param_attrib:
lint_ctx.error(f"Param input [{param_name}] input with no type specified.", line=param.sourceline, xpath=tool_xml.getpath(param))
continue
+ elif param_attrib["type"].strip() == "":
+ lint_ctx.error(f"Param input [{param_name}] with empty type specified.", line=param.sourceline, xpath=tool_xml.getpath(param))
+ continue
param_type = param_attrib["type"]
- if not is_valid_cheetah_placeholder(param_name):
- lint_ctx.warn(f"Param input [{param_name}] is not a valid Cheetah placeholder.", line=param.sourceline, xpath=tool_xml.getpath(param))
+ # TODO lint for valid param type - attribute combinations
+ # TODO lint required attributes for valid each param type
if param_type == "data":
if "format" not in param_attrib:
@@ -89,13 +98,18 @@ def lint_inputs(tool_xml, lint_ctx):
lint_ctx.warn(f"Select parameter [{param_name}] uses deprecated 'dynamic_options' attribute.", line=param.sourceline, xpath=tool_xml.getpath(param))
# check if options are defined by exactly one possibility
- if (dynamic_options is not None) + (len(options) > 0) + (len(select_options) > 0) != 1:
- lint_ctx.error(f"Select parameter [{param_name}] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute.", line=param.sourceline, xpath=tool_xml.getpath(param))
+ if param.getparent().tag != "conditional":
+ if (dynamic_options is not None) + (len(options) > 0) + (len(select_options) > 0) != 1:
+ lint_ctx.error(f"Select parameter [{param_name}] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute.", line=param.sourceline, xpath=tool_xml.getpath(param))
+ else:
+ if len(select_options) == 0:
+ lint_ctx.error(f"Select parameter of a conditional [{param_name}] options have to be defined by 'option' children elements.", line=param.sourceline, xpath=tool_xml.getpath(param))
# lint dynamic options
if len(options) == 1:
filters = options[0].findall("./filter")
# lint filters
+ # TODO check if dataset is available for filters referring other datasets
filter_adds_options = False
for f in filters:
ftype = f.get("type", None)
@@ -113,6 +127,8 @@ def lint_inputs(tool_xml, lint_ctx):
from_parameter = options[0].get("from_parameter", None)
from_dataset = options[0].get("from_dataset", None)
from_data_table = options[0].get("from_data_table", None)
+ # TODO check if input param is present for from_dataset
+
if (from_file is None and from_parameter is None
and from_dataset is None and from_data_table is None
and not filter_adds_options):
@@ -136,7 +152,7 @@ def lint_inputs(tool_xml, lint_ctx):
lint_ctx.warn(f"Select parameter [{param_name}] options uses deprecated 'transform_lines' attribute.", line=options[0].sourceline, xpath=tool_xml.getpath(options[0]))
elif len(options) > 1:
- lint_ctx.error(f"Select parameter [{param_name}] contains multiple options elements", line=options[1].sourceline, xpath=tool_xml.getpath(options[1]))
+ lint_ctx.error(f"Select parameter [{param_name}] contains multiple options elements.", line=options[1].sourceline, xpath=tool_xml.getpath(options[1]))
# lint statically defined options
if any('value' not in option.attrib for option in select_options):
@@ -174,6 +190,7 @@ def lint_inputs(tool_xml, lint_ctx):
# TODO: Validate type, much more...
# lint validators
+ # TODO check if dataset is available for validators referring other datasets
validators = param.findall("./validator")
for validator in validators:
vtype = validator.attrib['type']
@@ -184,13 +201,13 @@ def lint_inputs(tool_xml, lint_ctx):
if attrib in validator.attrib and vtype not in ATTRIB_VALIDATOR_COMPATIBILITY[attrib]:
lint_ctx.error(f"Parameter [{param_name}]: attribute '{attrib}' is incompatible with validator of type '{vtype}'", line=validator.sourceline, xpath=tool_xml.getpath(validator))
if vtype == "expression" and validator.text is None:
- lint_ctx.error(f"Parameter [{param_name}]: expression validator without content")
+ lint_ctx.error(f"Parameter [{param_name}]: expression validator without content", line=validator.sourceline, xpath=tool_xml.getpath(validator))
if vtype not in ["expression", "regex"] and validator.text is not None:
lint_ctx.warn(f"Parameter [{param_name}]: '{vtype}' validators are not expected to contain text (found '{validator.text}')", line=validator.sourceline, xpath=tool_xml.getpath(validator))
if vtype in ["in_range", "length", "dataset_metadata_in_range"] and ("min" not in validator.attrib and "max" not in validator.attrib):
lint_ctx.error(f"Parameter [{param_name}]: '{vtype}' validators need to define the 'min' or 'max' attribute(s)", line=validator.sourceline, xpath=tool_xml.getpath(validator))
if vtype in ["metadata"] and ("check" not in validator.attrib and "skip" not in validator.attrib):
- lint_ctx.error(f"Parameter [{param_name}]: '{vtype}' validators need to define the 'check' or 'skip' attribute(s) {validator.attrib}", line=validator.sourceline, xpath=tool_xml.getpath(validator))
+ lint_ctx.error(f"Parameter [{param_name}]: '{vtype}' validators need to define the 'check' or 'skip' attribute(s)", line=validator.sourceline, xpath=tool_xml.getpath(validator))
if vtype in ["value_in_data_table", "value_not_in_data_table", "dataset_metadata_in_data_table", "dataset_metadata_not_in_data_table"] and "table_name" not in validator.attrib:
lint_ctx.error(f"Parameter [{param_name}]: '{vtype}' validators need to define the 'table_name' attribute", line=validator.sourceline, xpath=tool_xml.getpath(validator))
@@ -202,14 +219,17 @@ def lint_inputs(tool_xml, lint_ctx):
if conditional.get("value_from"):
# Probably only the upload tool use this, no children elements
continue
- first_param = conditional.find("param")
- if first_param is None:
- lint_ctx.error(f"Conditional [{conditional_name}] has no child ", line=conditional.sourceline, xpath=tool_xml.getpath(conditional))
+ first_param = conditional.findall("param")
+ if len(first_param) != 1:
+ lint_ctx.error(f"Conditional [{conditional_name}] needs exactly one child found {len(first_param)}", line=conditional.sourceline, xpath=tool_xml.getpath(conditional))
continue
+ first_param = first_param[0]
first_param_type = first_param.get('type')
if first_param_type not in ['select', 'boolean']:
- lint_ctx.warn(f'Conditional [{conditional_name}] first param should have type="select" /> or type="boolean"', line=first_param.sourceline, xpath=tool_xml.getpath(first_param))
+ lint_ctx.error(f'Conditional [{conditional_name}] first param should have type="select" (or type="boolean" which is discouraged)', line=first_param.sourceline, xpath=tool_xml.getpath(first_param))
continue
+ elif first_param_type == 'boolean':
+ lint_ctx.warn(f'Conditional [{conditional_name}] first param of type="boolean" is discouraged, use a select', line=first_param.sourceline, xpath=tool_xml.getpath(first_param))
if first_param_type == 'select':
select_options = _find_with_attribute(first_param, 'option', 'value')
@@ -220,14 +240,15 @@ def lint_inputs(tool_xml, lint_ctx):
first_param.get('falsevalue', 'false')
]
- if string_as_bool(first_param.get('optional', False)):
- lint_ctx.warn(f"Conditional [{conditional_name}] test parameter cannot be optional", line=first_param.sourceline, xpath=tool_xml.getpath(first_param))
+ for incomp in ["optional", "multiple"]:
+ if string_as_bool(first_param.get(incomp, False)):
+ lint_ctx.warn(f'Conditional [{conditional_name}] test parameter cannot be {incomp}="true"', line=first_param.sourceline, xpath=tool_xml.getpath(first_param))
whens = conditional.findall('./when')
if any('value' not in when.attrib for when in whens):
lint_ctx.error(f"Conditional [{conditional_name}] when without value", line=conditional.sourceline, xpath=tool_xml.getpath(conditional))
- when_ids = [w.get('value') for w in whens]
+ when_ids = [w.get('value') for w in whens if w.get('value') is not None]
for option_id in option_ids:
if option_id not in when_ids:
@@ -241,6 +262,7 @@ def lint_inputs(tool_xml, lint_ctx):
lint_ctx.warn(f"Conditional [{conditional_name}] no truevalue/falsevalue found for when block '{when_id}'", line=conditional.sourceline, xpath=tool_xml.getpath(conditional))
if datasource:
+ # TODO only display is subtag of inputs, uihints is a separate top level tag (supporting only attrib minwidth)
for datasource_tag in ('display', 'uihints'):
if not any(param.tag == datasource_tag for param in inputs):
lint_ctx.info(f"{datasource_tag} tag usually present in data sources", line=tool_line, xpath=tool_path)
diff --git a/test/unit/tool_util/test_tool_linters.py b/test/unit/tool_util/test_tool_linters.py
index 0f23a90397a..39e87aef2f8 100644
--- a/test/unit/tool_util/test_tool_linters.py
+++ b/test/unit/tool_util/test_tool_linters.py
@@ -75,11 +75,17 @@ HELP_INVALID_RST = """
"""
# test tool xml for inputs linter
-NO_INPUTS_SECTION_XML = """
+INPUTS_NO_INPUTS = """
"""
+INPUTS_NO_INPUTS_DATASOURCE = """
+
+
+
+"""
+
INPUTS_REDUNDANT_NAME = """
@@ -88,19 +94,99 @@ INPUTS_REDUNDANT_NAME = """
"""
-NO_WHEN_IN_CONDITIONAL_XML = """
+INPUTS_VALID = """
-
-
+
+
+
+
+"""
+
+INPUTS_PARAM_NAME = """
+
+
+
+
+
+
+
+
+"""
+
+INPUTS_PARAM_TYPE = """
+
+
+
+
+
+
+"""
+
+INPUTS_DATA_PARAM = """
+
+
+
+
+
+"""
+
+INPUTS_CONDITIONAL = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"""
-RADIO_SELECT_INCOMPATIBILITIES = """
+INPUTS_SELECT_INCOMPATIBLE_DISPLAY = """
@@ -120,7 +206,7 @@ RADIO_SELECT_INCOMPATIBILITIES = """
"""
-SELECT_DUPLICATED_OPTIONS = """
+INPUTS_SELECT_DUPLICATED_OPTIONS = """
@@ -143,6 +229,7 @@ SELECT_DUPLICATED_OPTIONS_WITH_DIFF_SELECTED = """
"""
SELECT_DEPRECATIONS = """
+INPUTS_SELECT_DEPRECATIONS = """
@@ -156,7 +243,7 @@ SELECT_DEPRECATIONS = """
"""
-SELECT_OPTION_DEFINITIONS = """
+INPUTS_SELECT_OPTION_DEFINITIONS = """
@@ -175,27 +262,44 @@ SELECT_OPTION_DEFINITIONS = """
-
-
-"""
-
-VALIDATOR_INCOMPATIBILITIES = """
-
- The BWA Mapper
- bwa.py --version
-
-
- TEXT
-
+
+
"""
-VALIDATOR_CORRECT = """
-
- The BWA Mapper
- bwa.py --version
+INPUTS_SELECT_FILTER = """
+
+
+
+
+
+
+
+
+
+
+"""
+
+INPUTS_VALIDATOR_INCOMPATIBILITIES = """
+
+
+
+ TEXT
+
+
+
+
+
+
+
+
+
+"""
+
+INPUTS_VALIDATOR_CORRECT = """
+
@@ -237,6 +341,16 @@ VALIDATOR_CORRECT = """
"""
+REPEATS = """
+
+
+
+
+
+
+
+"""
+
# test tool xml for outputs linter
# check that linter accepts format source for collection elements as means to specify format
@@ -385,10 +499,18 @@ TESTS = [
and len(x.info_messages) == 0 and len(x.valid_messages) == 1 and len(x.warn_messages) == 1 and len(x.error_messages) == 0
),
(
- NO_INPUTS_SECTION_XML, inputs.lint_inputs,
+ INPUTS_NO_INPUTS, inputs.lint_inputs,
lambda x:
'Found no input parameters.' in x.warn_messages
- and len(x.warn_messages) == 1 and len(x.error_messages) == 0
+ and len(x.info_messages) == 0 and len(x.valid_messages) == 0 and len(x.warn_messages) == 1 and len(x.error_messages) == 0
+ ),
+ (
+ INPUTS_NO_INPUTS_DATASOURCE, inputs.lint_inputs,
+ lambda x:
+ 'No input parameters, OK for data sources' in x.info_messages
+ and 'display tag usually present in data sources' in x.info_messages
+ and 'uihints tag usually present in data sources' in x.info_messages
+ and len(x.info_messages) == 3 and len(x.valid_messages) == 0 and len(x.warn_messages) == 0 and len(x.error_messages) == 0
),
(
INPUTS_REDUNDANT_NAME, inputs.lint_inputs,
@@ -397,13 +519,53 @@ TESTS = [
and len(x.warn_messages) == 1 and len(x.error_messages) == 0
),
(
- NO_WHEN_IN_CONDITIONAL_XML, inputs.lint_inputs,
+ INPUTS_VALID, inputs.lint_inputs,
lambda x:
- "Conditional [labels] no block found for select option 'none'" in x.warn_messages
- and len(x.warn_messages) == 1 and len(x.error_messages) == 0
+ "Found 2 input parameters." in x.info_messages
+ and len(x.info_messages) == 1 and len(x.valid_messages) == 0 and len(x.warn_messages) == 0 and len(x.error_messages) == 0
),
(
- RADIO_SELECT_INCOMPATIBILITIES, inputs.lint_inputs,
+ INPUTS_PARAM_NAME, inputs.lint_inputs,
+ lambda x:
+ "Found 4 input parameters." in x.info_messages
+ and 'Param input [2] is not a valid Cheetah placeholder.' in x.warn_messages
+ and 'Found param input with no name specified.' in x.error_messages
+ and 'Param input with empty name.' in x.error_messages
+ and len(x.info_messages) == 1 and len(x.valid_messages) == 0 and len(x.warn_messages) == 1 and len(x.error_messages) == 2
+ ),
+ (
+ INPUTS_PARAM_TYPE, inputs.lint_inputs,
+ lambda x:
+ "Found 2 input parameters." in x.info_messages
+ and 'Param input [valid_name] input with no type specified.' in x.error_messages
+ and 'Param input [another_valid_name] with empty type specified.' in x.error_messages
+ and len(x.info_messages) == 1 and len(x.valid_messages) == 0 and len(x.warn_messages) == 0 and len(x.error_messages) == 2
+ ),
+ (
+ INPUTS_DATA_PARAM, inputs.lint_inputs,
+ lambda x:
+ "Found 1 input parameters." in x.info_messages
+ and "Param input [valid_name] with no format specified - 'data' format will be assumed." in x.warn_messages
+ and len(x.info_messages) == 1 and len(x.valid_messages) == 0 and len(x.warn_messages) == 1 and len(x.error_messages) == 0
+ ),
+ (
+ INPUTS_CONDITIONAL, inputs.lint_inputs,
+ lambda x:
+ "Conditional without a name" in x.error_messages
+ and "Select parameter of a conditional [select] options have to be defined by 'option' children elements." in x.error_messages
+ and 'Conditional [cond_wo_param] needs exactly one child found 0' in x.error_messages
+ and 'Conditional [cond_w_mult_param] needs exactly one child found 2' in x.error_messages
+ and 'Conditional [cond_text] first param should have type="select" (or type="boolean" which is discouraged)' in x.error_messages
+ and 'Conditional [cond_boolean] first param of type="boolean" is discouraged, use a select' in x.warn_messages
+ and "Conditional [cond_boolean] no truevalue/falsevalue found for when block 'False'" in x.warn_messages
+ and 'Conditional [cond_w_optional_select] test parameter cannot be optional="true"' in x.warn_messages
+ and 'Conditional [cond_w_multiple_select] test parameter cannot be multiple="true"' in x.warn_messages
+ and "Conditional [when_wo_value] when without value" in x.error_messages
+ and "Conditional [missing_when] no block found for select option 'none'" in x.warn_messages
+ and len(x.warn_messages) == 6 and len(x.error_messages) == 6
+ ),
+ (
+ INPUTS_SELECT_INCOMPATIBLE_DISPLAY, inputs.lint_inputs,
lambda x:
'Select [radio_select] display="radio" is incompatible with optional="true"' in x.error_messages
and 'Select [radio_select] display="radio" is incompatible with multiple="true"' in x.error_messages
@@ -412,7 +574,7 @@ TESTS = [
and len(x.warn_messages) == 0 and len(x.error_messages) == 4
),
(
- SELECT_DUPLICATED_OPTIONS, inputs.lint_inputs,
+ INPUTS_SELECT_DUPLICATED_OPTIONS, inputs.lint_inputs,
lambda x:
'Select parameter [select] has multiple options with the same text content' in x.error_messages
and 'Select parameter [select] has multiple options with the same value' in x.error_messages
@@ -425,6 +587,7 @@ TESTS = [
),
(
SELECT_DEPRECATIONS, inputs.lint_inputs,
+ INPUTS_SELECT_DEPRECATIONS, inputs.lint_inputs,
lambda x:
"Select parameter [select_do] uses deprecated 'dynamic_options' attribute." in x.warn_messages
and "Select parameter [select_ff] options uses deprecated 'from_file' attribute." in x.warn_messages
@@ -434,30 +597,48 @@ TESTS = [
and len(x.warn_messages) == 5 and len(x.error_messages) == 0
),
(
- SELECT_OPTION_DEFINITIONS, inputs.lint_inputs,
+ INPUTS_SELECT_OPTION_DEFINITIONS, inputs.lint_inputs,
lambda x:
"Select parameter [select_noopt] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute." in x.error_messages
and "Select parameter [select_noopts] options tag defines no options. Use 'from_dataset', 'from_data_table', or a filter that adds values." in x.error_messages
and "Select parameter [select_fd_op] options have to be defined by either 'option' children elements, a 'options' element or the 'dynamic_options' attribute." in x.error_messages
- and "Select parameter [select_fd_op] contains multiple options elements" in x.error_messages
+ and "Select parameter [select_fd_op] contains multiple options elements." in x.error_messages
and "Select parameter [select_fd_fdt] options uses 'from_dataset' and 'from_data_table' attribute." in x.error_messages
and "Select parameter [select_noval_notext] has option without value" in x.error_messages
and "Select parameter [select_noval_notext] has option without text" in x.warn_messages
- and len(x.warn_messages) == 1 and len(x.error_messages) == 6
+ and "Select parameter [select_meta_file_key_incomp] 'meta_file_key' is only compatible with 'from_dataset'." in x.error_messages
+ and len(x.warn_messages) == 1 and len(x.error_messages) == 7
),
(
- VALIDATOR_INCOMPATIBILITIES, inputs.lint_inputs,
+ INPUTS_SELECT_FILTER, inputs.lint_inputs,
+ lambda x:
+ "Select parameter [select_filter_types] contains filter without type." in x.error_messages
+ and "Select parameter [select_filter_types] contains filter with unknown type 'unknown_filter_type'." in x.error_messages
+ and len(x.warn_messages) == 0 and len(x.error_messages) == 2
+ ),
+ (
+ INPUTS_VALIDATOR_INCOMPATIBILITIES, inputs.lint_inputs,
lambda x:
"Parameter [param_name]: 'in_range' validators are not expected to contain text (found 'TEXT')" in x.warn_messages
and "Parameter [param_name]: validator with an incompatible type 'in_range'" in x.error_messages
and "Parameter [param_name]: 'in_range' validators need to define the 'min' or 'max' attribute(s)" in x.error_messages
and "Parameter [param_name]: attribute 'filename' is incompatible with validator of type 'regex'" in x.error_messages
- and len(x.warn_messages) == 1 and len(x.error_messages) == 3
+ and "Parameter [param_name]: expression validator without content" in x.error_messages
+ and "Parameter [another_param_name]: 'metadata' validators need to define the 'check' or 'skip' attribute(s)" in x.error_messages
+ and "Parameter [param_name]: 'value_in_data_table' validators need to define the 'table_name' attribute" in x.error_messages
+ and len(x.warn_messages) == 1 and len(x.error_messages) == 6
),
(
- VALIDATOR_CORRECT, inputs.lint_inputs,
+ INPUTS_VALIDATOR_CORRECT, inputs.lint_inputs,
lambda x: len(x.warn_messages) == 0 and len(x.error_messages) == 0
),
+ (
+ REPEATS, inputs.lint_repeats,
+ lambda x:
+ "Repeat does not specify name attribute." in x.error_messages
+ and "Repeat does not specify title attribute." in x.error_messages
+ and len(x.warn_messages) == 0 and len(x.error_messages) == 2
+ ),
(
OUTPUTS_COLLECTION_FORMAT_SOURCE, outputs.lint_output,
lambda x:
@@ -501,16 +682,23 @@ TEST_IDS = [
'help: empty',
'help: with todo',
'help: with invalid restructured text',
- 'lint no sections',
- 'input with redundant name',
- 'lint no when',
- 'radio select incompatibilities',
- 'select duplicated options',
- 'select duplicated options with different selected',
- 'select deprecations',
- 'select option definitions',
- 'validator imcompatibilities',
- 'validator all correct',
+ 'inputs: no inputs sections',
+ 'inputs: no inputs sections for datasource',
+ 'inputs: redundant param name',
+ 'inputs: valid',
+ 'inputs: param name',
+ 'inputs: param type',
+ 'inputs: data param',
+ 'inputs: conditional',
+ 'inputs: select with incompatible display',
+ 'inputs: select duplicated options',
+ 'inputs: select duplicated options with different selected',
+ 'inputs: select deprecations',
+ 'inputs: select option definitions',
+ 'inputs: select filter',
+ 'inputs: validator incompatibilities',
+ 'inputs: validator all correct',
+ 'repeats',
'outputs collection static elements with format_source',
'outputs discover datatsets with tool provided metadata',
'test without expectations',