From cd1c38c527e54b8f7b0817eda5ac9aa0c68dc349 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 28 Aug 2017 10:48:15 -0400 Subject: [PATCH 1/2] Break up populator methods for greater composibility and reuse. All of these changes are needed for various downstream enhancements I'm working on for things like CWL support and extra file testing - but I think they should all be okay refactorings on their own. --- test/base/populators.py | 61 +++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/test/base/populators.py b/test/base/populators.py index c45316e9715..4c1a9d58ced 100644 --- a/test/base/populators.py +++ b/test/base/populators.py @@ -80,15 +80,23 @@ class BaseDatasetPopulator(object): """ def new_dataset(self, history_id, content='TestData123', wait=False, **kwds): + run_response = self.new_dataset_request(history_id, content=content, wait=wait, **kwds) + return run_response.json()["outputs"][0] + + def new_dataset_request(self, history_id, content='TestData123', wait=False, **kwds): payload = self.upload_payload(history_id, content, **kwds) - run_response = self._post("tools", data=payload) - run = run_response.json() + run_response = self.tools_post(payload) if wait: - assert run_response.status_code == 200, run - job = run["jobs"][0] - self.wait_for_job(job["id"]) - self.wait_for_history(history_id, assert_ok=True) - return run["outputs"][0] + self.wait_for_tool_run(history_id, run_response) + return run_response + + def wait_for_tool_run(self, history_id, run_response): + run = run_response.json() + assert run_response.status_code == 200, run + job = run["jobs"][0] + self.wait_for_job(job["id"]) + self.wait_for_history(history_id, assert_ok=True) + return run_response def wait_for_history(self, history_id, assert_ok=False, timeout=DEFAULT_TIMEOUT): try: @@ -144,6 +152,7 @@ class BaseDatasetPopulator(object): upload_params["files_0|space_to_tab"] = kwds["space_to_tab"] if "auto_decompress" in kwds: upload_params["files_0|auto_decompress"] = kwds["auto_decompress"] + upload_params.update(kwds.get("extra_inputs", {})) return self.run_tool_payload( tool_id='upload1', inputs=upload_params, @@ -163,15 +172,25 @@ class BaseDatasetPopulator(object): **kwds ) - def run_tool(self, tool_id, inputs, history_id, **kwds): + def run_tool(self, tool_id, inputs, history_id, assert_ok=True, **kwds): payload = self.run_tool_payload(tool_id, inputs, history_id, **kwds) - tool_response = self._post("tools", data=payload) - api_asserts.assert_status_code_is(tool_response, 200) - return tool_response.json() + tool_response = self.tools_post(payload) + if assert_ok: + api_asserts.assert_status_code_is(tool_response, 200) + return tool_response.json() + else: + return tool_response - def get_history_dataset_content(self, history_id, wait=True, **kwds): + def tools_post(self, payload): + tool_response = self._post("tools", data=payload) + return tool_response + + def get_history_dataset_content(self, history_id, wait=True, filename=None, **kwds): dataset_id = self.__history_content_id(history_id, wait=wait, **kwds) - display_response = self.__get_contents_request(history_id, "/%s/display" % dataset_id) + data = {} + if filename: + data["filename"] = filename + display_response = self.__get_contents_request(history_id, "/%s/display" % dataset_id, data=data) assert display_response.status_code == 200, display_response.content return display_response.content @@ -212,11 +231,11 @@ class BaseDatasetPopulator(object): history_content_id = history_contents[-1]["id"] return history_content_id - def __get_contents_request(self, history_id, suffix=""): + def __get_contents_request(self, history_id, suffix="", data={}): url = "histories/%s/contents" % history_id if suffix: url = "%s%s" % (url, suffix) - return self._get(url) + return self._get(url, data=data) class DatasetPopulator(BaseDatasetPopulator): @@ -231,8 +250,8 @@ class DatasetPopulator(BaseDatasetPopulator): return self.galaxy_interactor.post(route, data, files=files) - def _get(self, route): - return self.galaxy_interactor.get(route) + def _get(self, route, data={}): + return self.galaxy_interactor.get(route, data=data) def _summarize_history(self, history_id): self.galaxy_interactor._summarize_history(history_id) @@ -305,8 +324,8 @@ class WorkflowPopulator(BaseWorkflowPopulator, ImporterGalaxyInterface): def _post(self, route, data={}): return self.galaxy_interactor.post(route, data) - def _get(self, route): - return self.galaxy_interactor.get(route) + def _get(self, route, data={}): + return self.galaxy_interactor.get(route, data=data) # Required for ImporterGalaxyInterface interface - so we can recurisvely import # nested workflows. @@ -526,8 +545,8 @@ def wait_on_state(state_func, skip_states=["running", "queued", "new", "ready"], class GiPostGetMixin: """Mixin for adapting Galaxy testing populators helpers to bioblend.""" - def _get(self, route): - return self._gi.make_get_request(self.__url(route)) + def _get(self, route, data={}): + return self._gi.make_get_request(self.__url(route), data) def _post(self, route, data={}): data = data.copy() From 5a7a15d161bececf0a3cd93714e15768b329d95f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 28 Aug 2017 09:49:41 -0400 Subject: [PATCH 2/2] Test and clarifications for composite uploads. - Add an API test for datatype-defined composite uploads - including exercising newline conversion and the space_to_tab parameter. - Add a test decorator skip_without_datatype to mirror skip_without_tool for this test, improve both decorators. - The ftype parameter in the composite test tools does nothing - drop it and drop it from the XSD spec. - Slightly improve the documentation for these composite_data elements in the XSD. --- lib/galaxy/tools/xsd/galaxy.xsd | 12 ++-- test/api/test_tools.py | 68 ++++++++++++++++++- test/base/interactor.py | 3 - test/base/populators.py | 45 +++++++++--- test/functional/tools/composite.xml | 6 +- test/functional/tools/composite_output.xml | 6 +- .../tools/composite_output_tests.xml | 8 +-- test/functional/tools/metadata.xml | 6 +- tools/sr_assembly/velvetg.xml | 6 +- 9 files changed, 125 insertions(+), 35 deletions(-) diff --git a/lib/galaxy/tools/xsd/galaxy.xsd b/lib/galaxy/tools/xsd/galaxy.xsd index d32b0f60e5e..40ae2fa7fe3 100644 --- a/lib/galaxy/tools/xsd/galaxy.xsd +++ b/lib/galaxy/tools/xsd/galaxy.xsd @@ -1062,18 +1062,18 @@ of ``type`` ``data``. - Define extra composite input files for test input. + Define extra composite input files for test +input. The specified ``ftype`` on the parent ``param`` should specify a composite +datatype with defined static composite files. The order of the defined composite +files on the datatype must match the order specified with these elements and All +non-optional composite inputs must be specified as part of the ``param``. + Path relative to test-data of composite file. - - - Optional datatype of composite file for test input. - - diff --git a/test/api/test_tools.py b/test/api/test_tools.py index 435b54d477f..0dcfe546bac 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -6,7 +6,8 @@ from base.populators import ( DatasetCollectionPopulator, DatasetPopulator, LibraryPopulator, - skip_without_tool + skip_without_tool, + skip_without_datatype, ) from galaxy.tools.verify.test_data import TestDataResolver @@ -119,6 +120,71 @@ class ToolsTestCase(api.ApiTestCase): rdata_metadata = self._upload_and_get_details(open(rdata_path, "rb"), file_type="auto") self.assertEquals(rdata_metadata["file_ext"], "rdata") + @skip_without_datatype("velvet") + def test_composite_datatype(self): + with self.dataset_populator.test_history() as history_id: + dataset = self._velvet_upload(history_id, extra_inputs={ + "files_1|url_paste": "roadmaps content", + "files_1|type": "upload_dataset", + "files_2|url_paste": "log content", + "files_2|type": "upload_dataset", + }) + + roadmaps_content = self._get_roadmaps_content(history_id, dataset) + assert roadmaps_content.strip() == "roadmaps content", roadmaps_content + + @skip_without_datatype("velvet") + def test_composite_datatype_space_to_tab(self): + # Like previous test but set one upload with space_to_tab to True to + # verify that works. + with self.dataset_populator.test_history() as history_id: + dataset = self._velvet_upload(history_id, extra_inputs={ + "files_1|url_paste": "roadmaps content", + "files_1|type": "upload_dataset", + "files_1|space_to_tab": "Yes", + "files_2|url_paste": "log content", + "files_2|type": "upload_dataset", + }) + + roadmaps_content = self._get_roadmaps_content(history_id, dataset) + assert roadmaps_content.strip() == "roadmaps\tcontent", roadmaps_content + + @skip_without_datatype("velvet") + def test_composite_datatype_posix_lines(self): + # Like previous test but set one upload with space_to_tab to True to + # verify that works. + with self.dataset_populator.test_history() as history_id: + dataset = self._velvet_upload(history_id, extra_inputs={ + "files_1|url_paste": "roadmaps\rcontent", + "files_1|type": "upload_dataset", + "files_1|space_to_tab": "Yes", + "files_2|url_paste": "log\rcontent", + "files_2|type": "upload_dataset", + }) + + roadmaps_content = self._get_roadmaps_content(history_id, dataset) + assert roadmaps_content.strip() == "roadmaps\ncontent", roadmaps_content + + def _velvet_upload(self, history_id, extra_inputs): + payload = self.dataset_populator.upload_payload( + history_id, + "sequences content", + file_type="velvet", + extra_inputs=extra_inputs, + ) + run_response = self.dataset_populator.tools_post(payload) + self.dataset_populator.wait_for_tool_run(history_id, run_response) + datasets = run_response.json()["outputs"] + + assert len(datasets) == 1 + dataset = datasets[0] + + return dataset + + def _get_roadmaps_content(self, history_id, dataset): + roadmaps_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=dataset, filename="Roadmaps") + return roadmaps_content + def test_unzip_collection(self): with self.dataset_populator.test_history() as history_id: hdca_id = self.__build_pair(history_id, ["123", "456"]) diff --git a/test/base/interactor.py b/test/base/interactor.py index 1433169854c..736527477f5 100644 --- a/test/base/interactor.py +++ b/test/base/interactor.py @@ -188,10 +188,7 @@ class GalaxyInteractorApi(object): file_name = self.functional_test_case.get_filename(composite_file.get('value'), shed_tool_id=shed_tool_id) files["files_%s|file_data" % i] = open(file_name, 'rb') tool_input.update({ - # "files_%d|NAME" % i: name, "files_%d|type" % i: "upload_dataset", - # TODO: - # "files_%d|space_to_tab" % i: composite_file.get( 'space_to_tab', False ) }) name = test_data['name'] else: diff --git a/test/base/populators.py b/test/base/populators.py index 4c1a9d58ced..169b9935cfb 100644 --- a/test/base/populators.py +++ b/test/base/populators.py @@ -2,6 +2,7 @@ import contextlib import json import time +from functools import wraps from operator import itemgetter import requests @@ -26,8 +27,9 @@ DEFAULT_TIMEOUT = 60 # Secs to wait for state to turn ok def skip_without_tool(tool_id): - """ Decorate an API test method as requiring a specific tool, - have nose skip the test case is the tool is unavailable. + """Decorate an API test method as requiring a specific tool. + + Have test framework skip the test case is the tool is unavailable. """ def method_wrapper(method): @@ -39,21 +41,46 @@ def skip_without_tool(tool_id): tool_ids = [itemgetter("id")(_) for _ in tools] return tool_ids + @wraps(method) def wrapped_method(api_test_case, *args, **kwargs): - if tool_id not in get_tool_ids(api_test_case): - from nose.plugins.skip import SkipTest - raise SkipTest() - + _raise_skip_if(tool_id not in get_tool_ids(api_test_case)) return method(api_test_case, *args, **kwargs) - # Must preserve method name so nose can detect and report tests by - # name. - wrapped_method.__name__ = method.__name__ return wrapped_method return method_wrapper +def skip_without_datatype(extension): + """Decorate an API test method as requiring a specific datatype. + + Have test framework skip the test case is the tool is unavailable. + """ + + def has_datatype(api_test_case): + index_response = api_test_case.galaxy_interactor.get("datatypes") + assert index_response.status_code == 200, "Failed to fetch datatypes for target Galaxy." + datatypes = index_response.json() + assert isinstance(datatypes, list) + return extension in datatypes + + def method_wrapper(method): + @wraps(method) + def wrapped_method(api_test_case, *args, **kwargs): + _raise_skip_if(not has_datatype(api_test_case)) + method(api_test_case, *args, **kwargs) + + return wrapped_method + + return method_wrapper + + +def _raise_skip_if(check): + if check: + from nose.plugins.skip import SkipTest + raise SkipTest() + + # Deprecated mixin, use dataset populator instead. # TODO: Rework existing tests to target DatasetPopulator in a setup method instead. class TestsDatasets: diff --git a/test/functional/tools/composite.xml b/test/functional/tools/composite.xml index 8f8edbe58f3..f4c5e5bfc1b 100644 --- a/test/functional/tools/composite.xml +++ b/test/functional/tools/composite.xml @@ -10,9 +10,9 @@ - - - + + + diff --git a/test/functional/tools/composite_output.xml b/test/functional/tools/composite_output.xml index 6bb1bf1e78f..cc29995c1e3 100644 --- a/test/functional/tools/composite_output.xml +++ b/test/functional/tools/composite_output.xml @@ -10,9 +10,9 @@ - - - + + + diff --git a/test/functional/tools/composite_output_tests.xml b/test/functional/tools/composite_output_tests.xml index d12a5beb5c2..686743264d0 100644 --- a/test/functional/tools/composite_output_tests.xml +++ b/test/functional/tools/composite_output_tests.xml @@ -14,15 +14,15 @@ - - - + + + - + diff --git a/test/functional/tools/metadata.xml b/test/functional/tools/metadata.xml index cc68946ec0d..e9268b9f01c 100644 --- a/test/functional/tools/metadata.xml +++ b/test/functional/tools/metadata.xml @@ -10,9 +10,9 @@ - - - + + + diff --git a/tools/sr_assembly/velvetg.xml b/tools/sr_assembly/velvetg.xml index 9feaed56a19..4c27e829bd7 100644 --- a/tools/sr_assembly/velvetg.xml +++ b/tools/sr_assembly/velvetg.xml @@ -189,9 +189,9 @@ - - - + + +