Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/galaxy/tool_util/verify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
tifffile = None # type: ignore[assignment, unused-ignore]


from packaging.version import Version

from galaxy.tool_util.parser.util import (
DEFAULT_DELTA,
DEFAULT_DELTA_FRAC,
Expand Down Expand Up @@ -75,6 +77,7 @@ def verify(
keep_outputs_dir: Optional[str] = None,
verify_extra_files: Optional[Callable] = None,
mode="file",
profile: Optional[str] = None,
):
"""Verify the content of a test output using test definitions described by attributes.

Expand All @@ -99,7 +102,11 @@ def get_filename(filename: str) -> str:
assertions = attributes.get("assert_list", None)
if assertions is not None:
try:
verify_assertions(output_content, attributes["assert_list"], attributes.get("decompress", False))
# Auto-detect separator based on file type for profile >= 26.0
sep: Optional[str] = None
if profile and Version(profile) >= Version("26.0"):
sep = "," if attributes.get("ftype") == "csv" else "\t"
verify_assertions(output_content, attributes["assert_list"], attributes.get("decompress", False), sep=sep)
except AssertionError as err:
errmsg = f"{item_label} different than expected\n"
errmsg += unicodify(err)
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/tool_util/verify/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
class ToolTestDescriptionDict(TypedDict):
tool_id: str
tool_version: Optional[str]
profile: NotRequired[Optional[str]]
name: str
test_index: int
inputs: ExpandedToolInputsJsonified
Expand Down
13 changes: 10 additions & 3 deletions lib/galaxy/tool_util/verify/asserts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import (
Callable,
Dict,
Optional,
Tuple,
)

Expand Down Expand Up @@ -41,7 +42,9 @@
assertion_functions: Dict[str, Callable] = {k: v[1] for (k, v) in assertion_module_and_functions.items()}


def verify_assertions(data: bytes, assertion_description_list: list, decompress: bool = False):
def verify_assertions(
data: bytes, assertion_description_list: list, decompress: bool = False, sep: Optional[str] = None
):
"""This function takes a list of assertions and a string to check
these assertions against."""
if decompress:
Expand All @@ -51,10 +54,10 @@ def verify_assertions(data: bytes, assertion_description_list: list, decompress:
with get_fileobj(tmpfh.name, mode="rb", compressed_formats=None) as fh:
data = fh.read()
for assertion_description in assertion_description_list:
verify_assertion(data, assertion_description)
verify_assertion(data, assertion_description, sep=sep)


def verify_assertion(data: bytes, assertion_description):
def verify_assertion(data: bytes, assertion_description, sep: Optional[str] = None):
tag = assertion_description["tag"]
assert_function_name = "assert_" + tag
assert_function = assertion_functions.get(assert_function_name)
Expand Down Expand Up @@ -103,5 +106,9 @@ def verify_assertion(data: bytes, assertion_description):
if "children" in assert_function_args:
args["children"] = assertion_description["children"]

# Only set sep if the assertion accepts it and it's not already specified in XML
if "sep" in assert_function_args and sep is not None and "sep" not in assertion_description["attributes"]:
args["sep"] = sep

# TODO: Verify all needed function arguments are specified.
assert_function(**args)
9 changes: 6 additions & 3 deletions lib/galaxy/tool_util/verify/asserts/tabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
)
from ._util import _assert_number

Sep = Annotated[str, AssertionParameter("Separator defining columns, default: tab")]
Sep = Annotated[
str, AssertionParameter("Separator defining columns, default: tab (or comma for csv with profile >= 26.0)")
]
Comment = Annotated[
str,
AssertionParameter(
Expand Down Expand Up @@ -53,9 +55,10 @@ def assert_has_n_columns(
Number of columns can optionally also be specified with ``delta``. Alternatively the
range of expected occurences can be specified by ``min`` and/or ``max``.

Optionally a column separator (``sep``, default is ``\t``) `and comment character(s)
Optionally a column separator (``sep``) and comment character(s)
can be specified (``comment``, default is empty string). The first non-comment
line is used for determining the number of columns.
line is used for determining the number of columns. For tools with profile >= 26.0,
the default separator is tab for most tabular data types, but comma for csv files.
"""
first_line = get_first_line(output, comment)
n_columns = len(first_line.split(sep))
Expand Down
42 changes: 38 additions & 4 deletions lib/galaxy/tool_util/verify/interactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,15 @@ class ValidToolTestDict(TypedDict):
error: Literal[False]
tool_id: str
tool_version: str
profile: NotRequired[Optional[str]]
test_index: int


class InvalidToolTestDict(TypedDict):
error: Literal[True]
tool_id: str
tool_version: str
profile: NotRequired[Optional[str]]
test_index: int
inputs: Any
exception: str
Expand Down Expand Up @@ -303,7 +305,13 @@ def get_tool_tests(self, tool_id: str, tool_version: Optional[str] = None) -> Li
return response.json()

def verify_output_collection(
self, output_collection_def, output_collection_id, history, tool_id, tool_version=None
self,
output_collection_def,
output_collection_id,
history,
tool_id,
tool_version=None,
profile: Optional[str] = None,
):
data_collection = self._get(
f"dataset_collections/{output_collection_id}", data={"instance_type": "history"}
Expand All @@ -319,6 +327,7 @@ def verify_dataset(element, element_attrib, element_outfile):
attributes=element_attrib,
tool_id=tool_id,
tool_version=tool_version,
profile=profile,
)
except AssertionError as e:
raise AssertionError(
Expand All @@ -327,7 +336,17 @@ def verify_dataset(element, element_attrib, element_outfile):

verify_collection(output_collection_def, data_collection, verify_dataset)

def verify_output(self, history_id, jobs, output_data, output_testdef, tool_id, maxseconds, tool_version=None):
def verify_output(
self,
history_id,
jobs,
output_data,
output_testdef,
tool_id,
maxseconds,
tool_version=None,
profile: Optional[str] = None,
):
outfile = output_testdef.outfile
attributes = output_testdef.attributes
name = output_testdef.name
Expand All @@ -342,6 +361,7 @@ def verify_output(self, history_id, jobs, output_data, output_testdef, tool_id,
attributes=attributes,
tool_id=tool_id,
tool_version=tool_version,
profile=profile,
)
except AssertionError as e:
raise AssertionError(f"Output {name}: {str(e)}")
Expand Down Expand Up @@ -378,6 +398,7 @@ def verify_output(self, history_id, jobs, output_data, output_testdef, tool_id,
primary_attributes,
tool_id=tool_id,
tool_version=tool_version,
profile=profile,
)
except AssertionError as e:
raise AssertionError(f"Primary output {name}: {str(e)}")
Expand All @@ -386,7 +407,9 @@ def wait_for_jobs(self, history_id, jobs, maxseconds):
for job in jobs:
self.wait_for_job(job["id"], history_id, maxseconds)

def verify_output_dataset(self, history_id, hda_id, outfile, attributes, tool_id, tool_version=None):
def verify_output_dataset(
self, history_id, hda_id, outfile, attributes, tool_id, tool_version=None, profile: Optional[str] = None
):
fetcher = self.__dataset_fetcher(history_id)
test_data_downloader = self.__test_data_downloader(tool_id, tool_version, attributes)
verify_hid(
Expand All @@ -396,6 +419,7 @@ def verify_output_dataset(self, history_id, hda_id, outfile, attributes, tool_id
dataset_fetcher=fetcher,
test_data_downloader=test_data_downloader,
keep_outputs_dir=self.keep_outputs_dir,
profile=profile,
)
self._verify_metadata(history_id, hda_id, attributes)

Expand Down Expand Up @@ -1300,6 +1324,7 @@ def verify_hid(
test_data_downloader,
dataset_fetcher=None,
keep_outputs_dir: Optional[str] = None,
profile: Optional[str] = None,
):
assert dataset_fetcher is not None

Expand All @@ -1322,6 +1347,7 @@ def verify_extra_files(extra_files):
get_filecontent=test_data_downloader,
keep_outputs_dir=keep_outputs_dir,
verify_extra_files=verify_extra_files,
profile=profile,
)


Expand Down Expand Up @@ -1757,6 +1783,7 @@ def register_exception(e: Exception):
tool_id=job["tool_id"],
maxseconds=maxseconds,
tool_version=testdef.tool_version,
profile=testdef.profile,
)
except Exception as e:
register_exception(e)
Expand Down Expand Up @@ -1816,7 +1843,11 @@ def register_exception(e: Exception):
# the job completed so re-hit the API for more information.
data_collection_id = data_collection_list[name]["id"]
galaxy_interactor.verify_output_collection(
output_collection_def, data_collection_id, history, job["tool_id"]
output_collection_def,
data_collection_id,
history,
job["tool_id"],
profile=testdef.profile,
)
except Exception as e:
register_exception(e)
Expand Down Expand Up @@ -1993,6 +2024,7 @@ class ToolTestDescription:
name: str
tool_id: str
tool_version: Optional[str]
profile: Optional[str]
test_index: int
num_outputs: Optional[int]
stdout: Optional[AssertionList]
Expand Down Expand Up @@ -2041,6 +2073,7 @@ def __init__(self, json_dict: ToolTestDescriptionDict):
self.request_schema = json_dict.get("request_schema", None)
self.tool_id = json_dict["tool_id"]
self.tool_version = json_dict.get("tool_version")
self.profile = json_dict.get("profile")
self.maxseconds = json_dict.get("maxseconds")

def test_data(self):
Expand All @@ -2067,6 +2100,7 @@ def to_dict(self) -> ToolTestDescriptionDict:
"test_index": self.test_index,
"tool_id": self.tool_id,
"tool_version": self.tool_version,
"profile": self.profile,
"required_files": self.required_files,
"required_data_tables": self.required_data_tables,
"required_loc_files": self.required_loc_files,
Expand Down
4 changes: 4 additions & 0 deletions lib/galaxy/tool_util/verify/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def _description_from_tool_source(
required_data_tables,
required_loc_files,
)
profile = tool_source.parse_profile()
processed_test_dict = ValidToolTestDict(
{
"inputs": processed_inputs,
Expand All @@ -164,16 +165,19 @@ def _description_from_tool_source(
"required_loc_files": required_loc_files,
"tool_id": tool_id,
"tool_version": tool_version,
"profile": profile,
"test_index": test_index,
"maxseconds": maxseconds,
"error": False,
}
)
except Exception:
profile = tool_source.parse_profile()
processed_test_dict = InvalidToolTestDict(
{
"tool_id": tool_id,
"tool_version": tool_version,
"profile": profile,
"test_index": test_index,
"inputs": {},
"error": True,
Expand Down
7 changes: 4 additions & 3 deletions lib/galaxy/tool_util/xsd/galaxy.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -2606,9 +2606,10 @@ For instance, ``<has_n_columns n="3"/>``. The assertion tests only the first lin
Number of columns can optionally also be specified with ``delta``. Alternatively the
range of expected occurences can be specified by ``min`` and/or ``max``.

Optionally a column separator (``sep``, default is `` ``) `and comment character(s)
Optionally a column separator (``sep``) and comment character(s)
can be specified (``comment``, default is empty string). The first non-comment
line is used for determining the number of columns.
line is used for determining the number of columns. For tools with profile >= 26.0,
the default separator is tab for most tabular data types, but comma for csv files.

$attribute_list::5]]></xs:documentation>
</xs:annotation>
Expand All @@ -2635,7 +2636,7 @@ $attribute_list::5]]></xs:documentation>
</xs:attribute>
<xs:attribute name="sep" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation xml:lang="en"><![CDATA[Separator defining columns, default: tab]]></xs:documentation>
<xs:documentation xml:lang="en"><![CDATA[Separator defining columns, default: tab (or comma for csv with profile >= 26.0)]]></xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="comment" type="xs:string" use="optional">
Expand Down
Loading
Loading