Files
galaxy/test/unit/tool_util/test_assertion_models.py
T
Nicola Soranzo 7f89e0eab9 Code cleanups from pyupgrade
Found by running:

```
make pyupgrade
ruff check --fix .
ruff check --preview --unsafe-fixes --select=F401 --fix .
make format
make update-client-api-schema
```

The bulk of the changes are replacements of typing deprecated aliases
such as `Dict` and `List` with the standard library classes. These
aliases became redundant in Python 3.9 when the corresponding
pre-existing classes were enhanced to support `[]`, see
https://docs.python.org/3/library/typing.html#deprecated-aliases

Also:
- Update the `pyupgrade` Makefile target to use `--py38-plus` for the
  packages needed by Pulsar, including the new galaxy-tool-util-models
  package.
2025-07-31 11:17:22 +01:00

242 lines
12 KiB
Python

from string import Template
import lxml.etree as ET
import pytest
from pydantic import ValidationError
from galaxy.tool_util.verify.codegen import galaxy_xsd_path
from galaxy.tool_util.verify.parse import assertion_xml_els_to_models
from galaxy.tool_util_models.assertions import assertion_list
from galaxy.util.commands import shell
from galaxy.util.unittest_utils import skip_unless_executable
valid_assertions = [
{"that": "has_size", "size": "5G"},
{"that": "has_size", "size": 1},
{"that": "has_size", "size": "5Mi"},
{"that": "has_text", "text": "JBrowseDefaultMainPage"},
{"that": "has_line", "line": "'>Wildtype Staphylococcus aureus strain WT.'"},
{"that": "has_n_columns", "n": 2},
{"that": "is_valid_xml"},
{"that": "has_element_with_path", "path": "//el"},
{"that": "has_n_elements_with_path", "path": "//el", "n": 4},
{"that": "element_text_matches", "expression": "foob[a]r", "path": "//el"},
{"that": "element_text_is", "text": "foobar", "path": "//el"},
{
"that": "xml_element",
"path": "./elem/more[2]",
"children": [{"that": "has_text_matching", "expression": "foo$"}],
},
{"that": "xml_element", "path": "./elem/more[2]"},
{"that": "element_text", "path": "./elem/more[2]", "children": [{"that": "has_text", "text": "foo"}]},
{"that": "has_json_property_with_value", "property": "foobar", "value": "'6'"},
{"that": "has_json_property_with_text", "property": "foobar", "text": "cowdog"},
{"that": "has_archive_member", "path": ".*/my-file.txt"},
{"that": "has_archive_member", "path": ".*/my-file.txt", "children": [{"that": "has_text", "text": "1235abc"}]},
{"that": "has_image_width", "width": 560},
{"that": "has_image_width", "width": 560, "delta": 490},
{"that": "has_image_height", "height": 560, "delta": 490},
{"that": "has_image_height", "min": 45, "max": 90},
{"that": "has_image_channels", "channels": 3},
{"that": "has_image_channels", "channels": 3, "delta": 1},
{"that": "has_image_channels", "min": 1, "max": 4},
{"that": "has_image_channels", "min": 1, "max": 4, "negate": True},
{"that": "has_image_mean_intensity", "mean_intensity": 3.4},
{"that": "has_image_mean_intensity", "mean_intensity": 3.4, "eps": 0.2},
{"that": "has_image_mean_intensity", "mean_intensity": 3.4, "eps": 0.2, "channel": 1},
{"that": "has_image_mean_intensity", "min": 0.4, "max": 0.6, "channel": 1},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07, 223.34"},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07, 223.34", "channel": 1},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07, 223.34", "channel": 1, "eps": 0.2},
{"that": "has_image_n_labels", "n": 85},
{"that": "has_image_n_labels", "labels": [1, 3, 4]},
{"that": "has_image_n_labels", "n": 9, "exclude_labels": [1, 3, 4]},
{"that": "has_image_n_labels", "n": 9, "exclude_labels": [1, 3, 4], "negate": True},
{"that": "has_image_mean_object_size", "mean_object_size": 9, "exclude_labels": [1, 3, 4]},
{"that": "has_image_mean_object_size", "mean_object_size": 9, "labels": [1, 3, 4]},
{"that": "has_image_mean_object_size", "mean_object_size": 9, "channel": 1, "eps": 0.2},
{"that": "has_json_property_with_value", "property": "skipped_columns", "value": "[1, 3, 5]"},
{"that": "has_json_property_with_text", "property": "color", "text": "red"},
{"that": "has_n_columns", "n": 30},
{"that": "has_n_columns", "n": 30, "delta": 4},
{"that": "has_n_columns", "n": 30, "delta": 4, "sep": " ", "comment": "####"},
]
valid_xml_assertions = [
"""<has_size value="5G" />""",
"""<has_size size="5G" />""",
"""<has_size size="5G" />""",
"""<has_text text="JBrowseDefaultMainPage" />""",
"""<has_line line="'>Wildtype Staphylococcus aureus strain WT.'" />""",
"""<xml_element path="./elem/more[2]" attribute="name"><has_text_matching expression="foo$"/></xml_element>""",
"""<xml_element path="./elem/more[2]" attribute="name"></xml_element>""",
"""<element_text path="./elem/more[2]"><has_text text="foo" /></element_text>""",
"""<has_element_with_path path="BlastOutput_param/Parameters/Parameters_matrix" />""",
r"""<element_text_matches path="BlastOutput_version" expression="BLASTP\s+2\.2.*"/>""",
"""<element_text_is path="BlastOutput_program" text="blastp" />""",
r"""<attribute_matches path="outerElement/innerElement2" attribute="foo2" expression="bar\d+" />""",
"""<element_text path="BlastOutput_iterations/Iteration/Iteration_hits/Hit/Hit_def"><not_has_text text="EDK72998.1" /></element_text>""",
"""<has_archive_member path=".*/my-file.txt"><not_has_text text="EDK72998.1"/></has_archive_member>""",
"""<has_archive_member path=".*/my-file.txt"></has_archive_member>""",
"""<has_image_n_labels n="85" />""",
"""<has_image_center_of_mass center_of_mass="511.07, 223.34" />""",
"""<has_image_center_of_mass center_of_mass="511.07, 223.34" eps=".2" />""",
"""<has_image_mean_intensity mean_intensity="0.83" />""",
"""<has_image_height height="512" delta="2" />""",
"""<has_image_width width="512" delta="2" />""",
"""<has_image_channels channels="3" />""",
"""<has_image_channels channels="3" delta="1" />""",
"""<has_image_channels min="3" max="5" />""",
"""<has_image_channels min="3" max="5" negate="true" />""",
"""<has_json_property_with_value property="skipped_columns" value="[1, 3, 5]" />""",
"""<has_json_property_with_text property="color" text="red" />""",
"""<has_n_columns n="30" />""",
"""<has_n_columns n="30" delta="4" />""",
"""<has_n_columns n="30" delta="4" sep=" " comment="###" />""",
"""<has_image_width min="500" />""",
"""<has_image_height min="500" />""",
]
invalid_assertions = [
{"that": "has_size", "size": "5Gigabytes"},
# negative sizes not allowed
{"that": "has_size", "size": -1},
{"that": "has_n_columns", "n": [2]},
{"that": "has_n_columns", "n": -2},
{"that": "is_valid_xml_foo"},
{"that": "has_element_with_path", "path": 45},
{"that": "has_n_elements_with_path", "n": 4},
{"that": "has_n_elements_with_path", "n": -4},
{"that": "element_text_matches", "expression": 12, "path": "//el"},
# unclosed regex group
{"that": "element_text_matches", "expression": "[12", "path": "//el"},
{"that": "xml_element", "path": "./elem/more[2]", "children": [{"that": "foobar"}]},
{
"that": "xml_element",
"path": "./elem/more[2]",
"children": [{"that": "has_text_matching", "line": "invalidprop"}],
},
# must specify children for element_text
{"that": "element_text", "path": "./elem/more[2]"},
{"that": "has_json_property_with_value", "property": 42, "value": "cowdog"},
{"that": "has_json_property_with_text", "property": "foobar", "text": 6},
{"that": "has_archive_member", "path": ".*/my-file.txt", "extra": "param"},
{"that": "has_archive_member", "path": ".*/my-file.txt", "children": [{"that": "invalid"}]},
{"that": "has_image_width", "width": "560"},
{"that": "has_image_width", "width": -560},
{"that": "has_image_width", "width": 560, "delta": "wrong"},
{"that": "has_image_height", "height": -560},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07, 223.34, foobar"},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07"},
{"that": "has_image_center_of_mass", "center_of_mass": "511.07, cow"},
# negative mean object sizes are not allowed
{"that": "has_image_n_labels", "n": -85},
{"that": "has_image_n_labels", "n": 85, "delta": -3},
# negative mean object sizes are not allowed
{"that": "has_image_mean_object_size", "mean_object_size": -9, "exclude_labels": [1, 3, 4]},
{"that": "has_image_mean_object_size", "mean_object_size": -9.0, "exclude_labels": [1, 3, 4]},
{"that": "has_image_mean_object_size", "mean_object_size": 9, "exclude_labels": [1, 3, 4], "eps": -0.2},
# looks a little odd in JSON but value is JSON loaded so must be a string
{"that": "has_json_property_with_value", "property": "skipped_columns", "value": [1, 3, 5]},
# missing property
{"that": "has_json_property_with_value", "property": "skipped_columns"},
{"that": "has_json_property_with_text", "property": "color"},
{"that": "has_n_columns", "n": 30, "delta": "wrongtype"},
{"that": "has_n_columns", "n": 30, "delta": -2},
{"that": "has_n_columns", "n": 30, "delta": 4, "sep": " ", "comment": "####", "extra": "param"},
]
invalid_xml_assertions = [
"""<has_n_columns n="cow" />""",
"""<has_text line="JBrowseDefaultMainPage" />""",
"""<has_line text="JBrowseDefaultMainPage" />""",
# at least one child assertion is required here...
"""<element_text path="./elem/more[2]"></element_text>""",
"""<has_archive_member path=".*/my-file.txt"><not_has_text line="EDK72998.1"/></has_archive_member>""",
"""<has_archive_member path=".*/my-file.txt"><foobar /></has_archive_member>""",
"""<has_image_center_of_mass center_of_mass="511.07, 223.34" extra="param" />""",
"""<has_image_center_of_mass center_of_mass="511.07, 223.34" eps="foobar" />""",
# negative numbers not allowed
"""<has_image_n_labels n="-85" />""",
"""<has_image_n_labels n="85" delta="-3" />""",
"""<has_image_mean_intensity mean_intensity="not_a_number" />""",
"""<has_image_mean_intensity mean_intensity=".83" extra="param" />""",
"""<has_image_height height="512" delta="notanumber" />""",
"""<has_image_width width="512" delta="2" extra="param" />""",
"""<has_image_height height="512" delta="-2" />""",
"""<has_image_width width="512" delta="-2" />""",
"""<has_image_height height="-512" />""",
"""<has_image_width width="-512" />""",
"""<has_image_channels channels="3" exclude="wrong" />""",
"""<has_image_channels channels="3" delta="not_a_number" />""",
"""<has_image_channels min="3" max="5" foo="bar" />""",
"""<has_image_channels min="3" max="5" negate="NOTABOOLEAN" />""",
# missing required arguments...
"""<has_json_property_with_value property="skipped_columns" />""",
"""<has_json_property_with_text property="color" />""",
"""<has_n_columns n="30" delta="wrong_type" />""",
"""<has_n_columns n="30" delta="4" sep=" " comment="###" extra="param"/>""",
]
TOOL_TEMPLATE = Template(
"""
<tool id="gx_test" name="gx_test" version="1.0.0">
<command><![CDATA[
echo '$parameter' >> '$output'
]]></command>
<inputs>
<param name="parameter" value="1" type="integer" />
</inputs>
<outputs>
<data name="output" format="txt" />
</outputs>
<tests>
<test>
<param name="parameter" value_json="12456" />
<output name="output">
<assert_contents>
$assertion_xml
</assert_contents>
</output>
</test>
</tests>
</tool>
"""
)
def test_valid_json_models_validate():
assertion_list.model_validate(valid_assertions)
def test_invalid_json_models_do_not_validate():
for invalid_assertion in invalid_assertions:
with pytest.raises(ValidationError):
assertion_list.model_validate([invalid_assertion])
@skip_unless_executable("xmllint")
def test_valid_xsd(tmp_path):
for assertion_xml in valid_xml_assertions:
tool_xml = TOOL_TEMPLATE.safe_substitute(assertion_xml=assertion_xml)
tool_path = tmp_path / "tool.xml"
tool_path.write_text(tool_xml)
ret = shell(["xmllint", "--nowarning", "--noout", "--schema", galaxy_xsd_path, str(tool_path)])
assert ret == 0, f"{assertion_xml} failed to validate"
@skip_unless_executable("xmllint")
def test_invalid_xsd(tmp_path):
for assertion_xml in invalid_xml_assertions:
tool_xml = TOOL_TEMPLATE.safe_substitute(assertion_xml=assertion_xml)
tool_path = tmp_path / "tool.xml"
tool_path.write_text(tool_xml)
ret = shell(["xmllint", "--nowarning", "--noout", "--schema", galaxy_xsd_path, str(tool_path)])
assert ret != 0, f"{assertion_xml} validated when error expected"
def test_valid_xml_models_validate_after_json_transform():
for assertion_xml in valid_xml_assertions:
assertion_xml_els_to_models([ET.fromstring(assertion_xml)])