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 = [ """""", """""", """""", """""", """""", """""", """""", """""", """""", r"""""", """""", r"""""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", ] 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 = [ """""", """""", """""", # at least one child assertion is required here... """""", """""", """""", """""", """""", # negative numbers not allowed """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", """""", # missing required arguments... """""", """""", """""", """""", ] TOOL_TEMPLATE = Template(""" > '$output' ]]> $assertion_xml """) 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)])