From a15fd1fcb8558802ad6bc32ced8eb92940e978cf Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Thu, 30 Mar 2023 17:29:08 +0200 Subject: [PATCH 01/78] fix assertion linters n, min, max, ... accept bytes (eg 500k) i.e. the type annotation and thereby linting was wrong I suggest to remove it, since its tested in the xsd linter anyway (otherwise we would check for str) --- lib/galaxy/tool_util/linters/tests.py | 14 +------------- test/unit/tool_util/test_tool_linters.py | 5 ++--- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/tool_util/linters/tests.py b/lib/galaxy/tool_util/linters/tests.py index fd99d3f8d3e3..e156b9927e74 100644 --- a/lib/galaxy/tool_util/linters/tests.py +++ b/lib/galaxy/tool_util/linters/tests.py @@ -152,23 +152,11 @@ def _check_asserts(test_idx, assertions, lint_ctx): lint_ctx.error(f"Test {test_idx}: unknown assertion '{a.tag}'", node=a) continue assert_function_sig = signature(asserts.assertion_functions[assert_function_name]) - # check type of the attributes (int, float ...) + # check of the attributes for attrib in a.attrib: if attrib not in assert_function_sig.parameters: lint_ctx.error(f"Test {test_idx}: unknown attribute '{attrib}' for '{a.tag}'", node=a) continue - annotation = assert_function_sig.parameters[attrib].annotation - annotation = _handle_optionals(annotation) - if annotation is not Parameter.empty: - try: - annotation(a.attrib[attrib]) - except TypeError: - raise Exception(f"Faild to instantiate {attrib} for {assert_function_name}") - except ValueError: - lint_ctx.error( - f"Test {test_idx}: attribute '{attrib}' for '{a.tag}' needs to be '{annotation.__name__}' got '{a.attrib[attrib]}'", - node=a, - ) # check missing required attributes for p in assert_function_sig.parameters: if p in ["output", "output_bytes", "verify_assertions_function", "children"]: diff --git a/test/unit/tool_util/test_tool_linters.py b/test/unit/tool_util/test_tool_linters.py index 089c07dd02a6..254d077c1eaa 100644 --- a/test/unit/tool_util/test_tool_linters.py +++ b/test/unit/tool_util/test_tool_linters.py @@ -1562,8 +1562,7 @@ def test_tests_asserts(lint_ctx): assert "Test 1: unknown assertion 'invalid'" in lint_ctx.error_messages assert "Test 1: unknown attribute 'invalid_attrib' for 'has_text'" in lint_ctx.error_messages assert "Test 1: missing attribute 'text' for 'has_text'" in lint_ctx.error_messages - assert "Test 1: attribute 'value' for 'has_size' needs to be 'int' got '500k'" in lint_ctx.error_messages - assert "Test 1: attribute 'delta' for 'has_size' needs to be 'int' got '1O'" in lint_ctx.error_messages + assert "Test 1: attribute 'value' for 'has_size' needs to be 'int' got '500k'" not in lint_ctx.error_messages assert ( "Test 1: unknown attribute 'invalid_attrib_also_checked_in_nested_asserts' for 'not_has_text'" in lint_ctx.error_messages @@ -1572,7 +1571,7 @@ def test_tests_asserts(lint_ctx): 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 + assert len(lint_ctx.error_messages) == 7 def test_tests_output_type_mismatch(lint_ctx): From 566a87d01ded3963408c99f601623cd23e9cca12 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Thu, 30 Mar 2023 17:31:13 +0200 Subject: [PATCH 02/78] fix type annotations for assertions the functions always get a str (from the xml attributes) --- lib/galaxy/tool_util/verify/asserts/_util.py | 33 ++++++++++-- .../tool_util/verify/asserts/archive.py | 12 ++--- lib/galaxy/tool_util/verify/asserts/size.py | 10 ++-- .../tool_util/verify/asserts/tabular.py | 10 ++-- lib/galaxy/tool_util/verify/asserts/text.py | 50 +++++++++---------- lib/galaxy/tool_util/verify/asserts/xml.py | 32 ++++++------ 6 files changed, 86 insertions(+), 61 deletions(-) diff --git a/lib/galaxy/tool_util/verify/asserts/_util.py b/lib/galaxy/tool_util/verify/asserts/_util.py index c8f6281c760f..d852e1350968 100644 --- a/lib/galaxy/tool_util/verify/asserts/_util.py +++ b/lib/galaxy/tool_util/verify/asserts/_util.py @@ -1,10 +1,24 @@ from math import inf +from typing import ( + Any, + Callable, + Optional +) from galaxy.util import asbool from galaxy.util.bytesize import parse_bytesize -def _assert_number(count, n, delta, min, max, negate, n_text, min_max_text): +def _assert_number( + count, + n: Optional[str], + delta: str, + min: Optional[str], + max: Optional[str], + negate: str, + n_text, + min_max_text +): """ helper function for assering that count is in - [n-delta:n+delta] @@ -26,12 +40,12 @@ def _assert_number(count, n, delta, min, max, negate, n_text, min_max_text): ) if min is not None or max is not None: if min is None: - min = -inf # also replacing min/max for output + min = "-inf" # also replacing min/max for output min_bytes = -inf else: min_bytes = parse_bytesize(min) if max is None: - max = inf + max = "inf" max_bytes = inf else: max_bytes = parse_bytesize(max) @@ -42,7 +56,18 @@ def _assert_number(count, n, delta, min, max, negate, n_text, min_max_text): def _assert_presence_number( - output, text, n, delta, min, max, negate, check_presence_foo, count_foo, presence_text, n_text, min_max_text + output, + text, + n: Optional[str], + delta: str, + min: Optional[str], + max: Optional[str], + negate: str, + check_presence_foo: Callable[[Any, Any], bool], + count_foo: Callable[[Any, Any], int], + presence_text: str, + n_text: str, + min_max_text: str ): """ helper function to assert that diff --git a/lib/galaxy/tool_util/verify/asserts/archive.py b/lib/galaxy/tool_util/verify/asserts/archive.py index a65bb814df34..fc86ebf94f2c 100644 --- a/lib/galaxy/tool_util/verify/asserts/archive.py +++ b/lib/galaxy/tool_util/verify/asserts/archive.py @@ -55,12 +55,12 @@ def assert_has_archive_member( path, verify_assertions_function, children, - all="false", - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + all="False", + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Recursively checks the specified children assertions against the text of the first element matching the specified path found within the archive. diff --git a/lib/galaxy/tool_util/verify/asserts/size.py b/lib/galaxy/tool_util/verify/asserts/size.py index 089ba5492276..4a3cc31912ed 100644 --- a/lib/galaxy/tool_util/verify/asserts/size.py +++ b/lib/galaxy/tool_util/verify/asserts/size.py @@ -5,11 +5,11 @@ def assert_has_size( output_bytes, - value: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + value: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """ Asserts the specified output has a size of the specified value, diff --git a/lib/galaxy/tool_util/verify/asserts/tabular.py b/lib/galaxy/tool_util/verify/asserts/tabular.py index 6679fa9c06b5..5dfdb2899f27 100644 --- a/lib/galaxy/tool_util/verify/asserts/tabular.py +++ b/lib/galaxy/tool_util/verify/asserts/tabular.py @@ -20,13 +20,13 @@ def get_first_line(output, comment): def assert_has_n_columns( output, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, sep="\t", comment="", - negate: bool = False, + negate: str = "False", ): """Asserts the tabular output contains n columns. The optional sep argument specifies the column seperator used to determine the diff --git a/lib/galaxy/tool_util/verify/asserts/text.py b/lib/galaxy/tool_util/verify/asserts/text.py index a29a88bb3364..7365ca4ddf88 100644 --- a/lib/galaxy/tool_util/verify/asserts/text.py +++ b/lib/galaxy/tool_util/verify/asserts/text.py @@ -10,11 +10,11 @@ def assert_has_text( output, text, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts specified output contains the substring specified by the argument text. The exact number of occurrences can be @@ -46,11 +46,11 @@ def assert_not_has_text(output, text): def assert_has_line( output, line, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts the specified output contains the line specified by the argument line. The exact number of occurrences can be optionally @@ -74,11 +74,11 @@ def assert_has_line( def assert_has_n_lines( output, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts the specified output contains ``n`` lines allowing for a difference in the number of lines (delta) @@ -100,11 +100,11 @@ def assert_has_n_lines( def assert_has_text_matching( output, expression, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts the specified output contains text matching the regular expression specified by the argument expression. @@ -130,11 +130,11 @@ def assert_has_text_matching( def assert_has_line_matching( output, expression, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts the specified output contains a line matching the regular expression specified by the argument expression. If n is given diff --git a/lib/galaxy/tool_util/verify/asserts/xml.py b/lib/galaxy/tool_util/verify/asserts/xml.py index 14d858d38bb0..98efc01ebe41 100644 --- a/lib/galaxy/tool_util/verify/asserts/xml.py +++ b/lib/galaxy/tool_util/verify/asserts/xml.py @@ -20,7 +20,7 @@ def assert_is_valid_xml(output): raise AssertionError(f"Expected valid XML, but could not parse output. {unicodify(e)}") -def assert_has_element_with_path(output, path, negate: bool = False): +def assert_has_element_with_path(output, path, negate: str = "False"): """Asserts the specified output has at least one XML element with a path matching the specified path argument. Valid paths are the simplified subsets of XPath implemented by lxml.etree; @@ -31,44 +31,44 @@ def assert_has_element_with_path(output, path, negate: bool = False): def assert_has_n_elements_with_path( output, path, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """Asserts the specified output has exactly n elements matching the path specified.""" assert_xml_element(output, path, n=n, delta=delta, min=min, max=max, negate=negate) -def assert_element_text_matches(output, path, expression, negate: bool = False): +def assert_element_text_matches(output, path, expression, negate: str = "False"): """Asserts the text of the first element matching the specified path matches the specified regular expression.""" sub = {"tag": "has_text_matching", "attributes": {"expression": expression, "negate": negate}} assert_xml_element(output, path, asserts.verify_assertions, [sub]) -def assert_element_text_is(output, path, text, negate: bool = False): +def assert_element_text_is(output, path, text, negate: str = "False"): """Asserts the text of the first element matching the specified path matches exactly the specified text.""" assert_element_text_matches(output, path, re.escape(text) + "$", negate=negate) -def assert_attribute_matches(output, path, attribute, expression, negate: bool = False): +def assert_attribute_matches(output, path, attribute, expression, negate: str = "False"): """Asserts the specified attribute of the first element matching the specified path matches the specified regular expression.""" sub = {"tag": "has_text_matching", "attributes": {"expression": expression, "negate": negate}} assert_xml_element(output, path, asserts.verify_assertions, [sub], attribute=attribute) -def assert_attribute_is(output, path, attribute, text, negate: bool = False): +def assert_attribute_is(output, path, attribute, text, negate: str = "False"): """Asserts the specified attribute of the first element matching the specified path matches exactly the specified text.""" assert_attribute_matches(output, path, attribute, re.escape(text) + "$", negate=negate) -def assert_element_text(output, path, verify_assertions_function, children, negate: bool = False): +def assert_element_text(output, path, verify_assertions_function, children, negate: str = "False"): """Recursively checks the specified assertions against the text of the first element matching the specified path.""" assert_xml_element(output, path, verify_assertions_function, children, negate=negate) @@ -81,11 +81,11 @@ def assert_xml_element( children=None, attribute=None, all=False, - n: Optional[int] = None, - delta: int = 0, - min: Optional[int] = None, - max: Optional[int] = None, - negate: bool = False, + n: Optional[str] = None, + delta: str = "0", + min: Optional[str] = None, + max: Optional[str] = None, + negate: str = "False", ): """ Check if path occurs in the xml. If n and delta or min and max are given From fd7963b09bcebda7af94e01a764ba24b9794ac27 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Thu, 30 Mar 2023 22:26:34 +0200 Subject: [PATCH 03/78] assertion typing: allow int/bool in addition to str --- .../tool_util/verify/asserts/__init__.py | 2 +- lib/galaxy/tool_util/verify/asserts/_util.py | 49 ++++++----- .../tool_util/verify/asserts/archive.py | 39 +++++---- lib/galaxy/tool_util/verify/asserts/hdf5.py | 7 +- lib/galaxy/tool_util/verify/asserts/size.py | 16 ++-- .../tool_util/verify/asserts/tabular.py | 25 +++--- lib/galaxy/tool_util/verify/asserts/text.py | 85 ++++++++++--------- lib/galaxy/tool_util/verify/asserts/xml.py | 59 +++++++------ 8 files changed, 151 insertions(+), 131 deletions(-) diff --git a/lib/galaxy/tool_util/verify/asserts/__init__.py b/lib/galaxy/tool_util/verify/asserts/__init__.py index 58b38debbf0c..d154e81f2bf8 100644 --- a/lib/galaxy/tool_util/verify/asserts/__init__.py +++ b/lib/galaxy/tool_util/verify/asserts/__init__.py @@ -60,7 +60,7 @@ def verify_assertion(data, assertion_description): # output. children is the parsed version of the child elements of # the XML element describing this assertion. See # assert_element_text in test/base/asserts/xml.py as an example of - # how to use verify_assertions_function and children in conjuction + # how to use verify_assertions_function and children in conjunction # to apply assertion checking to a subset of the input. The parsed # version of an elements child elements do not need to just define # assertions, developers of assertion functions can also use the diff --git a/lib/galaxy/tool_util/verify/asserts/_util.py b/lib/galaxy/tool_util/verify/asserts/_util.py index d852e1350968..2e3b604e98dc 100644 --- a/lib/galaxy/tool_util/verify/asserts/_util.py +++ b/lib/galaxy/tool_util/verify/asserts/_util.py @@ -1,8 +1,9 @@ from math import inf from typing import ( - Any, Callable, - Optional + Optional, + TypeVar, + Union, ) from galaxy.util import asbool @@ -10,15 +11,15 @@ def _assert_number( - count, - n: Optional[str], - delta: str, - min: Optional[str], - max: Optional[str], - negate: str, - n_text, - min_max_text -): + count: int, + n: Optional[Union[int, str]], + delta: Union[int, str], + min: Optional[Union[int, str]], + max: Optional[Union[int, str]], + negate: Union[bool, str], + n_text: str, + min_max_text: str, +) -> None: """ helper function for assering that count is in - [n-delta:n+delta] @@ -55,20 +56,24 @@ def _assert_number( ) +OutputType = TypeVar("OutputType") +TextType = TypeVar("TextType") + + def _assert_presence_number( - output, - text, - n: Optional[str], - delta: str, - min: Optional[str], - max: Optional[str], - negate: str, - check_presence_foo: Callable[[Any, Any], bool], - count_foo: Callable[[Any, Any], int], + output: OutputType, + text: TextType, + n: Optional[Union[int, str]], + delta: Union[int, str], + min: Optional[Union[int, str]], + max: Optional[Union[int, str]], + negate: Union[bool, str], + check_presence_foo: Callable[[OutputType, TextType], bool], + count_foo: Callable[[OutputType, TextType], int], presence_text: str, n_text: str, - min_max_text: str -): + min_max_text: str, +) -> None: """ helper function to assert that - text is present in output using check_presence_foo diff --git a/lib/galaxy/tool_util/verify/asserts/archive.py b/lib/galaxy/tool_util/verify/asserts/archive.py index fc86ebf94f2c..5244dc90e1f8 100644 --- a/lib/galaxy/tool_util/verify/asserts/archive.py +++ b/lib/galaxy/tool_util/verify/asserts/archive.py @@ -3,14 +3,17 @@ import tarfile import tempfile import zipfile -from typing import Optional +from typing import ( + Union, + Optional, +) from galaxy.util import asbool from ._util import _assert_presence_number -def _extract_from_tar(bytes, fn): - with io.BytesIO(bytes) as temp: +def _extract_from_tar(output_bytes, fn): + with io.BytesIO(output_bytes) as temp: with tarfile.open(fileobj=temp, mode="r") as tar_temp: ti = tar_temp.getmember(fn) # zip treats directories like empty files. @@ -21,9 +24,9 @@ def _extract_from_tar(bytes, fn): return member_fh.read() -def _list_from_tar(bytes, path): +def _list_from_tar(output_bytes, path): lst = list() - with io.BytesIO(bytes) as temp: + with io.BytesIO(output_bytes) as temp: with tarfile.open(fileobj=temp, mode="r") as tar_temp: for fn in tar_temp.getnames(): if not re.match(path, fn): @@ -32,16 +35,16 @@ def _list_from_tar(bytes, path): return sorted(lst) -def _extract_from_zip(bytes, fn): - with io.BytesIO(bytes) as temp: +def _extract_from_zip(output_bytes, fn): + with io.BytesIO(output_bytes) as temp: with zipfile.ZipFile(temp, mode="r") as zip_temp: with zip_temp.open(fn) as member_fh: return member_fh.read() -def _list_from_zip(bytes, path): +def _list_from_zip(output_bytes, path): lst = list() - with io.BytesIO(bytes) as temp: + with io.BytesIO(output_bytes) as temp: with zipfile.ZipFile(temp, mode="r") as zip_temp: for fn in zip_temp.namelist(): if not re.match(path, fn): @@ -51,17 +54,17 @@ def _list_from_zip(bytes, path): def assert_has_archive_member( - output_bytes, - path, + output_bytes: bytes, + path: str, verify_assertions_function, children, - all="False", - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + all: Union[bool, str] = False, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Recursively checks the specified children assertions against the text of the first element matching the specified path found within the archive. Currently supported formats: .zip, .tar, .tar.gz.""" diff --git a/lib/galaxy/tool_util/verify/asserts/hdf5.py b/lib/galaxy/tool_util/verify/asserts/hdf5.py index 8eab9f747945..e94c61c3af04 100644 --- a/lib/galaxy/tool_util/verify/asserts/hdf5.py +++ b/lib/galaxy/tool_util/verify/asserts/hdf5.py @@ -13,7 +13,7 @@ def _assert_h5py(): raise Exception(IMPORT_MISSING_MESSAGE) -def assert_has_h5_attribute(output_bytes, key, value): +def assert_has_h5_attribute(output_bytes: bytes, key: str, value: str) -> None: """Asserts the specified HDF5 output has a given key-value pair as HDF5 attribute""" _assert_h5py() @@ -25,11 +25,10 @@ def assert_has_h5_attribute(output_bytes, key, value): # TODO the function actually queries groups. so the function and argument name are misleading -def assert_has_h5_keys(output_bytes, keys): +def assert_has_h5_keys(output_bytes: bytes, keys: str) -> None: """Asserts the specified HDF5 output has the given keys.""" _assert_h5py() - keys = [k.strip() for k in keys.strip().split(",")] - h5_keys = sorted(keys) + h5_keys = sorted([k.strip() for k in keys.strip().split(",")]) output_temp = io.BytesIO(output_bytes) local_keys = [] diff --git a/lib/galaxy/tool_util/verify/asserts/size.py b/lib/galaxy/tool_util/verify/asserts/size.py index 4a3cc31912ed..8282a918f03f 100644 --- a/lib/galaxy/tool_util/verify/asserts/size.py +++ b/lib/galaxy/tool_util/verify/asserts/size.py @@ -1,16 +1,16 @@ -from typing import Optional +from typing import Union, Optional from ._util import _assert_number def assert_has_size( - output_bytes, - value: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output_bytes: bytes, + value: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """ Asserts the specified output has a size of the specified value, allowing for absolute (delta) and relative (delta_frac) difference. diff --git a/lib/galaxy/tool_util/verify/asserts/tabular.py b/lib/galaxy/tool_util/verify/asserts/tabular.py index 5dfdb2899f27..1d046f60ca07 100644 --- a/lib/galaxy/tool_util/verify/asserts/tabular.py +++ b/lib/galaxy/tool_util/verify/asserts/tabular.py @@ -1,10 +1,13 @@ import re -from typing import Optional +from typing import ( + Union, + Optional, +) from ._util import _assert_number -def get_first_line(output, comment): +def get_first_line(output: str, comment: str) -> str: """ get the first non-comment and non-empty line """ @@ -19,15 +22,15 @@ def get_first_line(output, comment): def assert_has_n_columns( - output, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - sep="\t", - comment="", - negate: str = "False", -): + output: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + sep: str = "\t", + comment: str = "", + negate: Union[bool, str] = False, +) -> None: """Asserts the tabular output contains n columns. The optional sep argument specifies the column seperator used to determine the number of columns. The optional comment argument specifies diff --git a/lib/galaxy/tool_util/verify/asserts/text.py b/lib/galaxy/tool_util/verify/asserts/text.py index 7365ca4ddf88..b6e0f39d04c5 100644 --- a/lib/galaxy/tool_util/verify/asserts/text.py +++ b/lib/galaxy/tool_util/verify/asserts/text.py @@ -1,5 +1,8 @@ import re -from typing import Optional +from typing import ( + Optional, + Union, +) from ._util import ( _assert_number, @@ -8,14 +11,14 @@ def assert_has_text( - output, - text, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + text: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts specified output contains the substring specified by the argument text. The exact number of occurrences can be optionally specified by the argument n""" @@ -36,7 +39,7 @@ def assert_has_text( ) -def assert_not_has_text(output, text): +def assert_not_has_text(output: str, text: str) -> None: """Asserts specified output does not contain the substring specified by the argument text""" assert output is not None, "Checking not_has_text assertion on empty output (None)" @@ -44,14 +47,14 @@ def assert_not_has_text(output, text): def assert_has_line( - output, - line, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + line: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts the specified output contains the line specified by the argument line. The exact number of occurrences can be optionally specified by the argument n""" @@ -73,13 +76,13 @@ def assert_has_line( def assert_has_n_lines( - output, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts the specified output contains ``n`` lines allowing for a difference in the number of lines (delta) or relative differebce in the number of lines""" @@ -98,14 +101,14 @@ def assert_has_n_lines( def assert_has_text_matching( - output, - expression, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + expression: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts the specified output contains text matching the regular expression specified by the argument expression. If n is given the assertion checks for exacly n (nonoverlapping) @@ -128,14 +131,14 @@ def assert_has_text_matching( def assert_has_line_matching( - output, - expression, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + expression: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts the specified output contains a line matching the regular expression specified by the argument expression. If n is given the assertion checks for exactly n occurences.""" diff --git a/lib/galaxy/tool_util/verify/asserts/xml.py b/lib/galaxy/tool_util/verify/asserts/xml.py index 98efc01ebe41..b081ac22e59c 100644 --- a/lib/galaxy/tool_util/verify/asserts/xml.py +++ b/lib/galaxy/tool_util/verify/asserts/xml.py @@ -1,5 +1,8 @@ import re -from typing import Optional +from typing import ( + Union, + Optional, +) from lxml.etree import XMLSyntaxError @@ -11,7 +14,7 @@ ) -def assert_is_valid_xml(output): +def assert_is_valid_xml(output: str) -> None: """Simple assertion that just verifies the specified output is valid XML.""" try: @@ -20,7 +23,7 @@ def assert_is_valid_xml(output): raise AssertionError(f"Expected valid XML, but could not parse output. {unicodify(e)}") -def assert_has_element_with_path(output, path, negate: str = "False"): +def assert_has_element_with_path(output: str, path: str, negate: Union[bool, str] = False) -> None: """Asserts the specified output has at least one XML element with a path matching the specified path argument. Valid paths are the simplified subsets of XPath implemented by lxml.etree; @@ -29,64 +32,68 @@ def assert_has_element_with_path(output, path, negate: str = "False"): def assert_has_n_elements_with_path( - output, - path, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + output: str, + path: str, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """Asserts the specified output has exactly n elements matching the path specified.""" assert_xml_element(output, path, n=n, delta=delta, min=min, max=max, negate=negate) -def assert_element_text_matches(output, path, expression, negate: str = "False"): +def assert_element_text_matches(output: str, path: str, expression: str, negate: Union[bool, str] = False) -> None: """Asserts the text of the first element matching the specified path matches the specified regular expression.""" sub = {"tag": "has_text_matching", "attributes": {"expression": expression, "negate": negate}} assert_xml_element(output, path, asserts.verify_assertions, [sub]) -def assert_element_text_is(output, path, text, negate: str = "False"): +def assert_element_text_is(output: str, path: str, text: str, negate: Union[bool, str] = False) -> None: """Asserts the text of the first element matching the specified path matches exactly the specified text.""" assert_element_text_matches(output, path, re.escape(text) + "$", negate=negate) -def assert_attribute_matches(output, path, attribute, expression, negate: str = "False"): +def assert_attribute_matches( + output: str, path: str, attribute, expression: str, negate: Union[bool, str] = False +) -> None: """Asserts the specified attribute of the first element matching the specified path matches the specified regular expression.""" sub = {"tag": "has_text_matching", "attributes": {"expression": expression, "negate": negate}} assert_xml_element(output, path, asserts.verify_assertions, [sub], attribute=attribute) -def assert_attribute_is(output, path, attribute, text, negate: str = "False"): +def assert_attribute_is(output: str, path: str, attribute: str, text, negate: Union[bool, str] = False) -> None: """Asserts the specified attribute of the first element matching the specified path matches exactly the specified text.""" assert_attribute_matches(output, path, attribute, re.escape(text) + "$", negate=negate) -def assert_element_text(output, path, verify_assertions_function, children, negate: str = "False"): +def assert_element_text( + output: str, path: str, verify_assertions_function, children, negate: Union[bool, str] = False +) -> None: """Recursively checks the specified assertions against the text of the first element matching the specified path.""" assert_xml_element(output, path, verify_assertions_function, children, negate=negate) def assert_xml_element( - output, - path, + output: str, + path: str, verify_assertions_function=None, children=None, - attribute=None, - all=False, - n: Optional[str] = None, - delta: str = "0", - min: Optional[str] = None, - max: Optional[str] = None, - negate: str = "False", -): + attribute: Optional[str] = None, + all: Union[bool, str] = False, + n: Optional[Union[int, str]] = None, + delta: Union[int, str] = 0, + min: Optional[Union[int, str]] = None, + max: Optional[Union[int, str]] = None, + negate: Union[bool, str] = False, +) -> None: """ Check if path occurs in the xml. If n and delta or min and max are given also the number of occurences is checked. From ed7c2d6584fe0372050e353a4a65012043e2c82c Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 20 Apr 2023 09:48:16 -0400 Subject: [PATCH 04/78] Handle empty lines better in tabular display dataset parsing --- .../components/Visualizations/Tabular/TabularChunkedView.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/Visualizations/Tabular/TabularChunkedView.vue b/client/src/components/Visualizations/Tabular/TabularChunkedView.vue index c5c1cfddcdb2..1125c6adcf15 100644 --- a/client/src/components/Visualizations/Tabular/TabularChunkedView.vue +++ b/client/src/components/Visualizations/Tabular/TabularChunkedView.vue @@ -88,7 +88,8 @@ function processChunk(chunk: TabularChunk) { parsedChunk = chunk.ck_data.trim().split("\n"); parsedChunk = parsedChunk.map((line) => { try { - return parse(line, { delimiter: delimiter.value })[0]; + const parsedLine = parse(line, { delimiter: delimiter.value })[0]; + return parsedLine || [line]; } catch (error) { // Failing lines get passed through intact for row-level // rendering/parsing. From c6067f739c5170cd123eec07a4af8a049ff3ae00 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 20 Apr 2023 10:40:31 -0400 Subject: [PATCH 05/78] Expand last column to fill tabular display table for better formatting --- .../Visualizations/Tabular/TabularChunkedView.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/components/Visualizations/Tabular/TabularChunkedView.vue b/client/src/components/Visualizations/Tabular/TabularChunkedView.vue index 1125c6adcf15..f0142e99ca38 100644 --- a/client/src/components/Visualizations/Tabular/TabularChunkedView.vue +++ b/client/src/components/Visualizations/Tabular/TabularChunkedView.vue @@ -181,9 +181,15 @@ onMounted(() => { - + {{ element }} + + {{ row.slice(-1)[0] }} + From 2328939bc03a75dcfbcb5f71884df8166589bf32 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:33:20 +0200 Subject: [PATCH 06/78] Display history name This will retrieve the history from the server for now. I should be replaced by the corresponding pinia store in the next release. --- .../History/Export/HistoryExport.test.ts | 13 +++++++++++ .../History/Export/HistoryExport.vue | 22 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/client/src/components/History/Export/HistoryExport.test.ts b/client/src/components/History/Export/HistoryExport.test.ts index e2ec1de4f383..ea620ea40ed5 100644 --- a/client/src/components/History/Export/HistoryExport.test.ts +++ b/client/src/components/History/Export/HistoryExport.test.ts @@ -19,7 +19,13 @@ const mockGetExportRecords = getExportRecords as jest.MockedFunction { beforeEach(async () => { axiosMock = new MockAdapter(axios); axiosMock.onGet(REMOTE_FILES_API_ENDPOINT).reply(200, []); + axiosMock.onGet(GET_HISTORY_ENDPOINT).reply(200, FAKE_HISTORY); }); afterEach(() => { axiosMock.restore(); }); + it("should render the history name", async () => { + const wrapper = await mountHistoryExport(); + + expect(wrapper.find("#history-name").text()).toBe(FAKE_HISTORY.name); + }); + it("should render export options", async () => { const wrapper = await mountHistoryExport(); diff --git a/client/src/components/History/Export/HistoryExport.vue b/client/src/components/History/Export/HistoryExport.vue index d506da0ec9d6..2e559e2895f7 100644 --- a/client/src/components/History/Export/HistoryExport.vue +++ b/client/src/components/History/Export/HistoryExport.vue @@ -6,6 +6,7 @@ import ExportRecordDetails from "components/Common/ExportRecordDetails.vue"; import ExportRecordTable from "components/Common/ExportRecordTable.vue"; import ExportOptions from "./ExportOptions.vue"; import ExportToFileSourceForm from "components/Common/ExportForm.vue"; +import { getHistoryById } from "@/store/historyStore/model/queries"; import { getExportRecords, exportToFileSource, reimportHistoryFromRecord } from "./services"; import { useTaskMonitor } from "composables/taskMonitor"; import { useFileSources } from "composables/fileSources"; @@ -13,6 +14,9 @@ import { useShortTermStorage, DEFAULT_EXPORT_PARAMS } from "composables/shortTer import { useConfirmDialog } from "composables/confirmDialog"; import { copy as sendToClipboard } from "utils/clipboard"; import { absPath } from "@/utils/redirect"; +import { faFileExport } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import { library } from "@fortawesome/fontawesome-svg-core"; const { isRunning: isExportTaskRunning, @@ -39,11 +43,18 @@ const props = defineProps({ }, }); +library.add(faFileExport); + const POLLING_DELAY = 3000; +const history = ref(null); +const isLoadingHistory = ref(true); + const exportParams = reactive(DEFAULT_EXPORT_PARAMS); const isLoadingRecords = ref(true); const exportRecords = ref(null); + +const historyName = computed(() => history.value?.name ?? props.historyId); const latestExportRecord = computed(() => (exportRecords.value?.length ? exportRecords.value.at(0) : null)); const previousExportRecords = computed(() => (exportRecords.value ? exportRecords.value.slice(1) : null)); const hasPreviousExports = computed(() => previousExportRecords.value?.length > 0); @@ -58,6 +69,10 @@ const actionMessageVariant = ref(null); onMounted(async () => { updateExports(); + //TODO: replace direct query with useHistoriesStore in 23.1 + isLoadingHistory.value = true; + history.value = await getHistoryById(props.historyId); + isLoadingHistory.value = false; }); watch(isExportTaskRunning, (newValue, oldValue) => { @@ -161,7 +176,12 @@ function updateExportParams(newParams) {