From 6178df3dcff068700a2c9c75ca99b3f4c2ab7bb8 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Fri, 19 Dec 2025 10:48:23 +0100 Subject: [PATCH] tool testing add min/max attributes to test output collections --- lib/galaxy/tool_util/parser/interface.py | 6 + lib/galaxy/tool_util/parser/xml.py | 12 ++ lib/galaxy/tool_util/verify/interactor.py | 38 ++++- lib/galaxy/tool_util/xsd/galaxy.xsd | 20 +++ test/functional/tools/expect_num_outputs.xml | 151 +++++++++++++++++++ 5 files changed, 220 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/tool_util/parser/interface.py b/lib/galaxy/tool_util/parser/interface.py index 7ac2d7a2ab1..5354d6fe10f 100644 --- a/lib/galaxy/tool_util/parser/interface.py +++ b/lib/galaxy/tool_util/parser/interface.py @@ -89,6 +89,8 @@ class ToolSourceTestOutputAttributes(TypedDict): metric: str pin_labels: Optional[Any] count: Optional[int] + min: Optional[int] + max: Optional[int] metadata: Dict[str, Any] md5: Optional[str] checksum: Optional[str] @@ -865,6 +867,10 @@ class TestCollectionOutputDef: else: count = attrib.get("count") self.count = int(count) if count is not None else None + min = attrib.get("min") + self.min = int(min) if min is not None else None + max = attrib.get("max") + self.max = int(max) if max is not None else None self.attrib = attrib self.element_tests = element_tests diff --git a/lib/galaxy/tool_util/parser/xml.py b/lib/galaxy/tool_util/parser/xml.py index 7642d2139e8..8e07180debf 100644 --- a/lib/galaxy/tool_util/parser/xml.py +++ b/lib/galaxy/tool_util/parser/xml.py @@ -922,6 +922,16 @@ def __parse_test_attributes( count = int(attrib.pop("count")) except KeyError: pass + min: Optional[int] = None + try: + min = int(attrib.pop("min")) + except KeyError: + pass + max: Optional[int] = None + try: + max = int(attrib.pop("max")) + except KeyError: + pass extra_files: List[Dict[str, Any]] = [] ftype: Optional[str] = None if "ftype" in attrib: @@ -966,6 +976,8 @@ def __parse_test_attributes( pin_labels=pin_labels, location=location, count=count, + min=min, + max=max, metadata=metadata, md5=md5sum, checksum=checksum, diff --git a/lib/galaxy/tool_util/verify/interactor.py b/lib/galaxy/tool_util/verify/interactor.py index 57bfc4af316..c5c5a9f6a6b 100644 --- a/lib/galaxy/tool_util/verify/interactor.py +++ b/lib/galaxy/tool_util/verify/interactor.py @@ -332,6 +332,8 @@ class GalaxyInteractorApi: attributes = output_testdef.attributes name = output_testdef.name expected_count = attributes.get("count") + min_count = attributes.get("min") + max_count = attributes.get("max") hid = self.__output_id(output_data) # TODO: Twill version verifies dataset is 'ok' in here. try: @@ -357,6 +359,14 @@ class GalaxyInteractorApi: raise AssertionError( f"Output '{name}': expected to have '{expected_count}' datasets, but it had '{found_datasets}'" ) + if min_count is not None and min_count > found_datasets: + raise AssertionError( + f"Output '{name}': expected to have at least '{min_count}' datasets, but it had '{found_datasets}'" + ) + if max_count is not None and max_count < found_datasets: + raise AssertionError( + f"Output '{name}': expected to have at most '{max_count}' datasets, but it had '{found_datasets}'" + ) for designation, (primary_outfile, primary_attributes) in primary_datasets.items(): primary_output = None for output in outputs: @@ -1335,12 +1345,16 @@ def verify_collection(output_collection_def, data_collection, verify_dataset): message = f"Output collection '{name}': expected to be of type [{expected_collection_type}], was of type [{collection_type}]." raise AssertionError(message) - expected_element_count = output_collection_def.count - if expected_element_count is not None: - actual_element_count = len(data_collection["elements"]) - if expected_element_count != actual_element_count: - message = f"Output collection '{name}': expected to have {expected_element_count} elements, but it had {actual_element_count}." - raise AssertionError(message) + actual_element_count = len(data_collection["elements"]) + if output_collection_def.count and output_collection_def.count != actual_element_count: + message = f"Output collection '{name}': expected to have {output_collection_def.count} elements, but it had {actual_element_count}." + raise AssertionError(message) + if output_collection_def.min and output_collection_def.min > actual_element_count: + message = f"Output collection '{name}': expected to have at least {output_collection_def.min} elements, but it had {actual_element_count}." + raise AssertionError(message) + if output_collection_def.max and output_collection_def.max < actual_element_count: + message = f"Output collection '{name}': expected to have at most {output_collection_def.max} elements, but it had {actual_element_count}." + raise AssertionError(message) def get_element(elements, id): for element in elements: @@ -1356,7 +1370,6 @@ def verify_collection(output_collection_def, data_collection, verify_dataset): element_outfile, element_attrib = None, element_test else: element_outfile, element_attrib = element_test - expected_count = element_attrib.get("count") if "expected_sort_order" in element_attrib: expected_sort_order[element_attrib["expected_sort_order"]] = element_identifier @@ -1373,10 +1386,21 @@ def verify_collection(output_collection_def, data_collection, verify_dataset): elements = element["object"]["elements"] element_count = len(elements) verify_elements(elements, element_attrib.get("elements", {})) + expected_count = element_attrib.get("count") if expected_count is not None and expected_count != element_count: raise AssertionError( f"Element '{element_identifier}': expected to have {expected_count} elements, but it had {element_count}" ) + max = element_attrib.get("max") + if max is not None and max < element_count: + raise AssertionError( + f"Element '{element_identifier}': expected to have at most {max} elements, but it had {element_count}" + ) + min = element_attrib.get("min") + if min is not None and min > element_count: + raise AssertionError( + f"Element '{element_identifier}': expected to have at least {min} elements, but it had {element_count}" + ) if len(expected_sort_order) > 0: generated_sort_order = [_["element_identifier"] for _ in element_objects] diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index cdae18ce654..a9d48bf8929 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -1996,6 +1996,16 @@ This is available in Galaxy since release 17.05 and was introduced in [pull requ Number or datasets for this output. Should be used for outputs with ``discover_datasets`` + + + Minimum number or datasets for this output. Should be used for outputs with ``discover_datasets`` + + + + + Maximum number or datasets for this output. Should be used for outputs with ``discover_datasets`` + + URL that points to a remote output file that will downloaded and used for output comparison. @@ -2348,6 +2358,16 @@ This value is the same as the value of the ``name`` attribute of the Number of elements in output collection. + + + Minimum number of elements in output collection. + + + + + Maximum number of elements in output collection. + + diff --git a/test/functional/tools/expect_num_outputs.xml b/test/functional/tools/expect_num_outputs.xml index c2690cc3038..2af5c5842be 100644 --- a/test/functional/tools/expect_num_outputs.xml +++ b/test/functional/tools/expect_num_outputs.xml @@ -157,6 +157,42 @@ true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -322,5 +358,120 @@ true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +