From 499f76dd9ea3e9192726133ca11dc2a6f6c9cd00 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 5 Dec 2023 14:49:18 +0100 Subject: [PATCH] Add workflow test specific TestOutputCollection and TestOutputElement --- .../tool_util/schemas/generated/config.py | 1 - .../tool_util/schemas/test_file_from_xsd.py | 27 +- .../tool_util/schemas/test_file_schema.json | 274 +++++++++++++++++- 3 files changed, 295 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/tool_util/schemas/generated/config.py b/lib/galaxy/tool_util/schemas/generated/config.py index e77aea9c547e..e04f30745b20 100644 --- a/lib/galaxy/tool_util/schemas/generated/config.py +++ b/lib/galaxy/tool_util/schemas/generated/config.py @@ -2,7 +2,6 @@ alias_lookup = { "assert_contents": "asserts", - "element": "element_tests", } diff --git a/lib/galaxy/tool_util/schemas/test_file_from_xsd.py b/lib/galaxy/tool_util/schemas/test_file_from_xsd.py index c2a2201647b2..ffba30ea07a9 100644 --- a/lib/galaxy/tool_util/schemas/test_file_from_xsd.py +++ b/lib/galaxy/tool_util/schemas/test_file_from_xsd.py @@ -1,10 +1,18 @@ from __future__ import annotations import json -from typing import Union +from dataclasses import dataclass +from typing import ( + Union, +) -# bug with pydantic, need everything in namespace -from generated.galaxy import * # noqa: F403 +# bug with pydantic, need everything in namespace, otherwise fails with +# `pydantic.errors.PydanticUserError: `ListOfTests` is not fully defined; you should define `TestDiscoveredDataset`, then call `ListOfTests.model_rebuild()`.` +from generated.galaxy import * # noqa: F401,F403 +from generated.galaxy import ( + TestOutput, + TestOutputCollection as TestOutputCollection_, +) from pydantic import ( BaseModel, ConfigDict, @@ -14,7 +22,17 @@ extra_forbidden = ConfigDict(extra="forbid") -AnyOutput = Union[TestOutput, TestOutputCollection] # noqa: F405 +@dataclass +class TestOutputElement(TestOutput): + element_tests: dict[str, TestOutputElement | TestOutput] + + +@dataclass +class TestOutputCollection(TestOutputCollection_): + element_tests: dict[str, TestOutputElement | TestOutput] + + +AnyOutput = Union[TestOutputElement, TestOutput, TestOutputCollection] # noqa: F405 class Test(BaseModel): @@ -30,4 +48,3 @@ class ListOfTests(RootModel): print(json.dumps(ListOfTests.model_json_schema(), indent=2)) -# print(json.dumps(TypeAdapter(TestOutput).json_schema(), indent=2)) diff --git a/lib/galaxy/tool_util/schemas/test_file_schema.json b/lib/galaxy/tool_util/schemas/test_file_schema.json index cc13d86dffbe..31eee6ffa2dc 100644 --- a/lib/galaxy/tool_util/schemas/test_file_schema.json +++ b/lib/galaxy/tool_util/schemas/test_file_schema.json @@ -1471,6 +1471,9 @@ "outputs": { "additionalProperties": { "anyOf": [ + { + "$ref": "#/$defs/TestOutputElement" + }, { "$ref": "#/$defs/TestOutput" }, @@ -2534,6 +2537,20 @@ "additionalProperties": false, "properties": { "element_tests": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/TestOutputElement" + }, + { + "$ref": "#/$defs/TestOutput" + } + ] + }, + "title": "Element Tests", + "type": "object" + }, + "element": { "anyOf": [ { "items": { @@ -2545,7 +2562,7 @@ "$ref": "#/$defs/TestOutput" } ], - "title": "Element Tests" + "title": "Element" }, "type_value": { "anyOf": [ @@ -2574,6 +2591,9 @@ "title": "Count" } }, + "required": [ + "element_tests" + ], "title": "TestOutputCollection", "type": "object" }, @@ -2589,6 +2609,258 @@ "title": "TestOutputCompareType", "type": "string" }, + "TestOutputElement": { + "additionalProperties": false, + "properties": { + "element_tests": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/TestOutputElement" + }, + { + "$ref": "#/$defs/TestOutput" + } + ] + }, + "title": "Element Tests", + "type": "object" + }, + "discovered_dataset": { + "items": { + "$ref": "#/$defs/TestDiscoveredDataset" + }, + "title": "Discovered Dataset", + "type": "array" + }, + "asserts": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/TestAssertions" + }, + "type": "array" + }, + { + "$ref": "#/$defs/TestAssertions" + } + ], + "description": "$assertions\n### Examples\nThe following demonstrates a wide variety of text-based and tabular\nassertion statements.\n```xml\n<output name=\"out_file1\">\n<assert_contents>\n<has_text text=\"chr7\" />\n<not_has_text text=\"chr8\" />\n<has_text_matching expression=\"1274\\d+53\" />\n<has_line_matching expression=\".*\\s+127489808\\s+127494553\" />\n<!-- &#009; is XML escape code for tab -->\n<has_line line=\"chr7&#009;127471195&#009;127489808\" />\n<has_n_columns n=\"3\" />\n</assert_contents>\n</output>\n```\nThe following demonstrates a wide variety of XML assertion statements.\n```xml\n<output name=\"out_file1\">\n<assert_contents>\n<is_valid_xml />\n<has_element_with_path path=\"BlastOutput_param/Parameters/Parameters_matrix\" />\n<has_n_elements_with_path n=\"9\" path=\"BlastOutput_iterations/Iteration/Iteration_hits/Hit/Hit_num\" />\n<element_text_matches path=\"BlastOutput_version\" expression=\"BLASTP\\s+2\\.2.*\" />\n<element_text_is path=\"BlastOutput_program\" text=\"blastp\" />\n<element_text path=\"BlastOutput_iterations/Iteration/Iteration_hits/Hit/Hit_def\">\n<not_has_text text=\"EDK72998.1\" />\n<has_text_matching expression=\"ABK[\\d\\.]+\" />\n</element_text>\n</assert_contents>\n</output>\n```\nThe following demonstrates verifying XML content with XPath-like expressions.\n```xml\n<output name=\"out_file1\">\n<assert_contents>\n<attribute_is path=\"outerElement/innerElement1\" attribute=\"foo\" text=\"bar\" />\n<attribute_matches path=\"outerElement/innerElement2\" attribute=\"foo2\" expression=\"bar\\d+\" />\n</assert_contents>\n</output>\n```", + "title": "Asserts" + }, + "extra_files": { + "items": { + "$ref": "#/$defs/TestExtraFile" + }, + "title": "Extra Files", + "type": "array" + }, + "metadata": { + "items": { + "$ref": "#/$defs/TestOutputMetadata" + }, + "title": "Metadata", + "type": "array" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "This value is the same as the value of the ``name`` attribute of the ``<data>``\ntag set contained within the tool's ``<outputs>`` tag set.", + "title": "Name" + }, + "file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If specified, this value is the name of the output file stored in the target\n``test-data`` directory which will be used to compare the results of executing\nthe tool via the functional test framework.", + "title": "File" + }, + "value_json": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If specified, this value will be loaded as JSON and compared against the output\ngenerated as JSON. This can be useful for testing tool outputs that are not files.", + "title": "Value Json" + }, + "ftype": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If specified, this value will be checked against the corresponding output's\ndata type. If these do not match, the test will fail.", + "title": "Ftype" + }, + "sort": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/$defs/PermissiveBooleanValue" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Applies only if ``compare`` is ``diff``, ``re_match`` or ``re_match_multiline``. This flag causes the lines of the history data set to be sorted before the comparison. In case of ``diff`` and ``re_match`` also the local file is sorted. This could be\nuseful for non-deterministic output.", + "title": "Sort" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "An alias for ``file``.", + "title": "Value" + }, + "md5": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If specified, the target output's MD5 hash should match the value specified\nhere. For large static files it may be inconvenient to upload the entiry file\nand this can be used instead.", + "title": "Md5" + }, + "checksum": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If specified, the target output's checksum should match the value specified\nhere. This value should have the form ``hash_type$hash_value``\n(e.g. ``sha1$8156d7ca0f46ed7abac98f82e36cfaddb2aca041``). For large static files\nit may be inconvenient to upload the entiry file and this can be used instead.", + "title": "Checksum" + }, + "compare": { + "anyOf": [ + { + "$ref": "#/$defs/TestOutputCompareType" + }, + { + "type": "null" + } + ], + "default": null + }, + "lines_diff": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Applies only if ``compare`` is set to ``diff``, ``re_match``, and ``contains``. If ``compare`` is set to ``diff``, the number of lines of difference to allow (each line with a modification is a line added and a line removed so this counts as two lines).", + "title": "Lines Diff" + }, + "decompress": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/$defs/PermissiveBooleanValue" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If this attribute is true then try to decompress files if needed. This applies to\ntest assertions expressed with ``assert_contents`` or ``compare`` set to anything\nbut ``sim_size``.\nThis flag is useful for testing compressed outputs that are non-deterministic\ndespite having deterministic decompressed contents. By default, only files compressed\nwith bz2, gzip and zip will be automatically decompressed.\nNote, for specifying assertions for compressed as well as decompressed output\nthe corresponding output tag can be specified multiple times.\nThis is available in Galaxy since release 17.05 and was introduced in [pull request #3550](https://github.com/galaxyproject/galaxy/pull/3550).", + "title": "Decompress" + }, + "delta": { + "default": 10000, + "description": "If ``compare`` is set to ``sim_size``, this is the maximum allowed absolute size difference (in bytes) between the data set that is generated in the test and the file in ``test-data/`` that is referenced by the ``file`` attribute. Default value is 10000 bytes. Can be combined with ``delta_frac``.", + "title": "Delta", + "type": "integer" + }, + "delta_frac": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "If ``compare`` is set to ``sim_size``, this is the maximum allowed relative size difference between the data set that is generated in the test and the file in ``test-data/`` that is referenced by the ``file`` attribute. A value of 0.1 means that the file that is generated in the test can differ by at most 10% of the file in ``test-data``. The default is not to check for relative size difference. Can be combined with ``delta``.", + "title": "Delta Frac" + }, + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Number or datasets for this output. Should be used for outputs with ``discover_datasets``", + "title": "Count" + }, + "location": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "URL that points to a remote output file that will downloaded and used for output comparison.\nPlease use this option only when is not possible to include the files in the `test-data` folder, since\nthis is more error prone due to external factors like remote availability.\nYou can use it in two ways:\n- In combination with `file` it will look for the output file in the `test-data` folder, if it's not available on disk it will\ndownload the file pointed by `location` using the same name as in `file` (or `value`).\n- Specifiying the `location` without a `file` (or `value`), it will download the file and use it as an alias of `file`. The name of the file\nwill be infered from the last component of the location URL. For example, `location=\"https://my_url/my_file.txt\"` will be equivalent to `file=\"my_file.txt\"`.\nIf you specify a `checksum`, it will be also used to check the integrity of the download.", + "title": "Location" + } + }, + "required": [ + "element_tests" + ], + "title": "TestOutputElement", + "type": "object" + }, "TestOutputMetadata": { "additionalProperties": false, "properties": {