mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-16 07:19:13 +08:00
Re-use test_data specification used by many of the API workflow tests and a workflow used by another test. Add abstractions and DOM element for mapping labeled test data to tool form inputs.
964 lines
38 KiB
Python
964 lines
38 KiB
Python
import contextlib
|
|
import json
|
|
import os
|
|
import time
|
|
import unittest
|
|
from functools import wraps
|
|
from operator import itemgetter
|
|
|
|
import requests
|
|
from pkg_resources import resource_string
|
|
from six import StringIO
|
|
|
|
from galaxy.tools.verify.test_data import TestDataResolver
|
|
from . import api_asserts
|
|
from .workflows_format_2 import (
|
|
convert_and_import_workflow,
|
|
ImporterGalaxyInterface,
|
|
)
|
|
|
|
|
|
# Simple workflow that takes an input and call cat wrapper on it.
|
|
workflow_str = resource_string(__name__, "data/test_workflow_1.ga")
|
|
# Simple workflow that takes an input and filters with random lines twice in a
|
|
# row - first grabbing 8 lines at random and then 6.
|
|
workflow_random_x2_str = resource_string(__name__, "data/test_workflow_2.ga")
|
|
|
|
|
|
DEFAULT_TIMEOUT = 60 # Secs to wait for state to turn ok
|
|
|
|
SKIP_FLAKEY_TESTS_ON_ERROR = os.environ.get("GALAXY_TEST_SKIP_FLAKEY_TESTS_ON_ERROR", None)
|
|
|
|
|
|
def flakey(method):
|
|
|
|
@wraps(method)
|
|
def wrapped_method(test_case, *args, **kwargs):
|
|
try:
|
|
method(test_case, *args, **kwargs)
|
|
except unittest.SkipTest:
|
|
raise
|
|
except Exception:
|
|
if SKIP_FLAKEY_TESTS_ON_ERROR:
|
|
raise unittest.SkipTest("Error encountered during test marked as @flakey.")
|
|
else:
|
|
raise
|
|
|
|
return wrapped_method
|
|
|
|
|
|
def skip_without_tool(tool_id):
|
|
"""Decorate an API test method as requiring a specific tool.
|
|
|
|
Have test framework skip the test case if the tool is unavailable.
|
|
"""
|
|
|
|
def method_wrapper(method):
|
|
|
|
def get_tool_ids(api_test_case):
|
|
index = api_test_case.galaxy_interactor.get("tools", data=dict(in_panel=False))
|
|
tools = index.json()
|
|
# In panels by default, so flatten out sections...
|
|
tool_ids = [itemgetter("id")(_) for _ in tools]
|
|
return tool_ids
|
|
|
|
@wraps(method)
|
|
def wrapped_method(api_test_case, *args, **kwargs):
|
|
_raise_skip_if(tool_id not in get_tool_ids(api_test_case))
|
|
return method(api_test_case, *args, **kwargs)
|
|
|
|
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 if the datatype 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 summarize_instance_history_on_error(method):
|
|
@wraps(method)
|
|
def wrapped_method(api_test_case, *args, **kwds):
|
|
try:
|
|
method(api_test_case, *args, **kwds)
|
|
except Exception:
|
|
api_test_case.dataset_populator._summarize_history(api_test_case.history_id)
|
|
raise
|
|
|
|
return wrapped_method
|
|
|
|
|
|
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:
|
|
|
|
def _new_dataset(self, history_id, content='TestData123', **kwds):
|
|
return DatasetPopulator(self.galaxy_interactor).new_dataset(history_id, content=content, **kwds)
|
|
|
|
def _wait_for_history(self, history_id, assert_ok=False):
|
|
return DatasetPopulator(self.galaxy_interactor).wait_for_history(history_id, assert_ok=assert_ok)
|
|
|
|
def _new_history(self, **kwds):
|
|
return DatasetPopulator(self.galaxy_interactor).new_history(**kwds)
|
|
|
|
def _upload_payload(self, history_id, content, **kwds):
|
|
return DatasetPopulator(self.galaxy_interactor).upload_payload(history_id, content, **kwds)
|
|
|
|
def _run_tool_payload(self, tool_id, inputs, history_id, **kwds):
|
|
return DatasetPopulator(self.galaxy_interactor).run_tool_payload(tool_id, inputs, history_id, **kwds)
|
|
|
|
|
|
class BaseDatasetPopulator(object):
|
|
""" Abstract description of API operations optimized for testing
|
|
Galaxy - implementations must implement _get, _post and _delete.
|
|
"""
|
|
|
|
def new_dataset(self, history_id, content=None, 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=None, wait=False, **kwds):
|
|
if content is None and "ftp_files" not in kwds:
|
|
content = "TestData123"
|
|
payload = self.upload_payload(history_id, content=content, **kwds)
|
|
run_response = self.tools_post(payload)
|
|
if wait:
|
|
self.wait_for_tool_run(history_id, run_response, assert_ok=kwds.get('assert_ok', True))
|
|
return run_response
|
|
|
|
def fetch(self, payload, assert_ok=True, timeout=DEFAULT_TIMEOUT):
|
|
tool_response = self._post("tools/fetch", data=payload)
|
|
if assert_ok:
|
|
job = self.check_run(tool_response)
|
|
self.wait_for_job(job["id"], timeout=timeout)
|
|
|
|
job = tool_response.json()["jobs"][0]
|
|
details = self.get_job_details(job["id"]).json()
|
|
assert details["state"] == "ok", details
|
|
|
|
return tool_response
|
|
|
|
def wait_for_tool_run(self, history_id, run_response, timeout=DEFAULT_TIMEOUT, assert_ok=True):
|
|
job = self.check_run(run_response)
|
|
self.wait_for_job(job["id"], timeout=timeout)
|
|
self.wait_for_history(history_id, assert_ok=assert_ok, timeout=timeout)
|
|
return run_response
|
|
|
|
def check_run(self, run_response):
|
|
run = run_response.json()
|
|
assert run_response.status_code == 200, run
|
|
job = run["jobs"][0]
|
|
return job
|
|
|
|
def wait_for_history(self, history_id, assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
try:
|
|
return wait_on_state(lambda: self._get("histories/%s" % history_id), desc="history state", assert_ok=assert_ok, timeout=timeout)
|
|
except AssertionError:
|
|
self._summarize_history(history_id)
|
|
raise
|
|
|
|
def wait_for_history_jobs(self, history_id, assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
query_params = {"history_id": history_id}
|
|
|
|
def get_jobs():
|
|
jobs_response = self._get("jobs", query_params)
|
|
assert jobs_response.status_code == 200
|
|
return jobs_response.json()
|
|
|
|
def has_active_jobs():
|
|
jobs = get_jobs()
|
|
active_jobs = [j for j in jobs if j["state"] in ["new", "upload", "waiting", "queued", "running"]]
|
|
|
|
if len(active_jobs) == 0:
|
|
return True
|
|
else:
|
|
return None
|
|
|
|
try:
|
|
wait_on(has_active_jobs, "active jobs", timeout=timeout)
|
|
except TimeoutAssertionError as e:
|
|
jobs = get_jobs()
|
|
message = "Failed waiting on active jobs to complete, current jobs are [%s]. %s" % (jobs, e.message)
|
|
raise TimeoutAssertionError(message)
|
|
|
|
if assert_ok:
|
|
return self.wait_for_history(history_id, assert_ok=True, timeout=timeout)
|
|
|
|
def wait_for_job(self, job_id, assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
return wait_on_state(lambda: self.get_job_details(job_id), desc="job state", assert_ok=assert_ok, timeout=timeout)
|
|
|
|
def get_job_details(self, job_id, full=False):
|
|
return self._get("jobs/%s?full=%s" % (job_id, full))
|
|
|
|
def cancel_job(self, job_id):
|
|
return self._delete("jobs/%s" % job_id)
|
|
|
|
def _summarize_history(self, history_id):
|
|
pass
|
|
|
|
@contextlib.contextmanager
|
|
def test_history(self, **kwds):
|
|
# TODO: In the future allow targetting a specfic history here
|
|
# and/or deleting everything in the resulting history when done.
|
|
# These would be cool options for remote Galaxy test execution.
|
|
try:
|
|
history_id = self.new_history()
|
|
yield history_id
|
|
except Exception:
|
|
self._summarize_history(history_id)
|
|
raise
|
|
|
|
def new_history(self, **kwds):
|
|
name = kwds.get("name", "API Test History")
|
|
create_history_response = self._post("histories", data=dict(name=name))
|
|
history_id = create_history_response.json()["id"]
|
|
return history_id
|
|
|
|
def upload_payload(self, history_id, content=None, **kwds):
|
|
name = kwds.get("name", "Test Dataset")
|
|
dbkey = kwds.get("dbkey", "?")
|
|
file_type = kwds.get("file_type", 'txt')
|
|
upload_params = {
|
|
'files_0|NAME': name,
|
|
'dbkey': dbkey,
|
|
'file_type': file_type,
|
|
}
|
|
if dbkey is None:
|
|
del upload_params["dbkey"]
|
|
if content is None:
|
|
upload_params["files_0|ftp_files"] = kwds.get("ftp_files")
|
|
elif hasattr(content, 'read'):
|
|
upload_params["files_0|file_data"] = content
|
|
else:
|
|
upload_params['files_0|url_paste'] = content
|
|
|
|
if "to_posix_lines" in kwds:
|
|
upload_params["files_0|to_posix_lines"] = kwds["to_posix_lines"]
|
|
if "space_to_tab" in kwds:
|
|
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,
|
|
history_id=history_id,
|
|
upload_type='upload_dataset'
|
|
)
|
|
|
|
def get_remote_files(self, target="ftp"):
|
|
return self._get("remote_files", data={"target": target}).json()
|
|
|
|
def run_tool_payload(self, tool_id, inputs, history_id, **kwds):
|
|
if "files_0|file_data" in inputs:
|
|
kwds["__files"] = {"files_0|file_data": inputs["files_0|file_data"]}
|
|
del inputs["files_0|file_data"]
|
|
|
|
return dict(
|
|
tool_id=tool_id,
|
|
inputs=json.dumps(inputs),
|
|
history_id=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.tools_post(payload)
|
|
if assert_ok:
|
|
api_asserts.assert_status_code_is(tool_response, 200)
|
|
return tool_response.json()
|
|
else:
|
|
return tool_response
|
|
|
|
def tools_post(self, payload, url="tools"):
|
|
tool_response = self._post(url, 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)
|
|
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
|
|
|
|
def get_history_dataset_details(self, history_id, **kwds):
|
|
dataset_id = self.__history_content_id(history_id, **kwds)
|
|
details_response = self._get_contents_request(history_id, "/datasets/%s" % dataset_id)
|
|
assert details_response.status_code == 200
|
|
return details_response.json()
|
|
|
|
def get_history_collection_details(self, history_id, **kwds):
|
|
hdca_id = self.__history_content_id(history_id, **kwds)
|
|
details_response = self._get_contents_request(history_id, "/dataset_collections/%s" % hdca_id)
|
|
assert details_response.status_code == 200, details_response.content
|
|
return details_response.json()
|
|
|
|
def run_collection_creates_list(self, history_id, hdca_id):
|
|
inputs = {
|
|
"input1": {"src": "hdca", "id": hdca_id},
|
|
}
|
|
self.wait_for_history(history_id, assert_ok=True)
|
|
return self.run_tool("collection_creates_list", inputs, history_id)
|
|
|
|
def run_exit_code_from_file(self, history_id, hdca_id):
|
|
exit_code_inputs = {
|
|
"input": {'batch': True, 'values': [{"src": "hdca", "id": hdca_id}]},
|
|
}
|
|
response = self.run_tool("exit_code_from_file", exit_code_inputs, history_id, assert_ok=False).json()
|
|
self.wait_for_history(history_id, assert_ok=False)
|
|
return response
|
|
|
|
def __history_content_id(self, history_id, wait=True, **kwds):
|
|
if wait:
|
|
assert_ok = kwds.get("assert_ok", True)
|
|
self.wait_for_history(history_id, assert_ok=assert_ok)
|
|
# kwds should contain a 'dataset' object response, a 'dataset_id' or
|
|
# the last dataset in the history will be fetched.
|
|
if "dataset_id" in kwds:
|
|
history_content_id = kwds["dataset_id"]
|
|
elif "content_id" in kwds:
|
|
history_content_id = kwds["content_id"]
|
|
elif "dataset" in kwds:
|
|
history_content_id = kwds["dataset"]["id"]
|
|
else:
|
|
hid = kwds.get("hid", None) # If not hid, just grab last dataset
|
|
history_contents = self._get_contents_request(history_id).json()
|
|
if hid:
|
|
history_content_id = None
|
|
for history_item in history_contents:
|
|
if history_item["hid"] == hid:
|
|
history_content_id = history_item["id"]
|
|
if history_content_id is None:
|
|
raise Exception("Could not find content with HID [%s] in [%s]" % (hid, history_contents))
|
|
else:
|
|
# No hid specified - just grab most recent element.
|
|
history_content_id = history_contents[-1]["id"]
|
|
return history_content_id
|
|
|
|
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, data=data)
|
|
|
|
def ds_entry(self, history_content):
|
|
src = 'hda'
|
|
if 'history_content_type' in history_content and history_content['history_content_type'] == "dataset_collection":
|
|
src = 'hdca'
|
|
return dict(src=src, id=history_content["id"])
|
|
|
|
|
|
class DatasetPopulator(BaseDatasetPopulator):
|
|
|
|
def __init__(self, galaxy_interactor):
|
|
self.galaxy_interactor = galaxy_interactor
|
|
|
|
def _post(self, route, data={}, files=None):
|
|
files = data.get("__files", None)
|
|
if files is not None:
|
|
del data["__files"]
|
|
|
|
return self.galaxy_interactor.post(route, data, files=files)
|
|
|
|
def _get(self, route, data={}):
|
|
return self.galaxy_interactor.get(route, data=data)
|
|
|
|
def _delete(self, route, data={}):
|
|
return self.galaxy_interactor.delete(route, data=data)
|
|
|
|
def _summarize_history(self, history_id):
|
|
self.galaxy_interactor._summarize_history(history_id)
|
|
|
|
def wait_for_dataset(self, history_id, dataset_id, assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
return wait_on_state(lambda: self._get("histories/%s/contents/%s" % (history_id, dataset_id)), desc="dataset state", assert_ok=assert_ok, timeout=timeout)
|
|
|
|
|
|
class BaseWorkflowPopulator(object):
|
|
|
|
def load_workflow(self, name, content=workflow_str, add_pja=False):
|
|
workflow = json.loads(content)
|
|
workflow["name"] = name
|
|
if add_pja:
|
|
tool_step = workflow["steps"]["2"]
|
|
tool_step["post_job_actions"]["RenameDatasetActionout_file1"] = dict(
|
|
action_type="RenameDatasetAction",
|
|
output_name="out_file1",
|
|
action_arguments=dict(newname="foo ${replaceme}"),
|
|
)
|
|
return workflow
|
|
|
|
def load_random_x2_workflow(self, name):
|
|
return self.load_workflow(name, content=workflow_random_x2_str)
|
|
|
|
def load_workflow_from_resource(self, name, filename=None):
|
|
if filename is None:
|
|
filename = "data/%s.ga" % name
|
|
content = resource_string(__name__, filename)
|
|
return self.load_workflow(name, content=content)
|
|
|
|
def simple_workflow(self, name, **create_kwds):
|
|
workflow = self.load_workflow(name)
|
|
return self.create_workflow(workflow, **create_kwds)
|
|
|
|
def create_workflow(self, workflow, **create_kwds):
|
|
upload_response = self.create_workflow_response(workflow, **create_kwds)
|
|
uploaded_workflow_id = upload_response.json()["id"]
|
|
return uploaded_workflow_id
|
|
|
|
def create_workflow_response(self, workflow, **create_kwds):
|
|
data = dict(
|
|
workflow=json.dumps(workflow),
|
|
**create_kwds
|
|
)
|
|
upload_response = self._post("workflows/upload", data=data)
|
|
return upload_response
|
|
|
|
def upload_yaml_workflow(self, has_yaml, **kwds):
|
|
workflow = convert_and_import_workflow(has_yaml, galaxy_interface=self, **kwds)
|
|
return workflow["id"]
|
|
|
|
def wait_for_invocation(self, workflow_id, invocation_id, timeout=DEFAULT_TIMEOUT):
|
|
url = "workflows/%s/usage/%s" % (workflow_id, invocation_id)
|
|
return wait_on_state(lambda: self._get(url), desc="workflow invocation state", timeout=timeout)
|
|
|
|
def wait_for_workflow(self, workflow_id, invocation_id, history_id, assert_ok=True, timeout=DEFAULT_TIMEOUT):
|
|
""" Wait for a workflow invocation to completely schedule and then history
|
|
to be complete. """
|
|
self.wait_for_invocation(workflow_id, invocation_id, timeout=timeout)
|
|
self.dataset_populator.wait_for_history_jobs(history_id, assert_ok=assert_ok, timeout=timeout)
|
|
|
|
def invoke_workflow(self, history_id, workflow_id, inputs={}, request={}, assert_ok=True):
|
|
request["history"] = "hist_id=%s" % history_id,
|
|
if inputs:
|
|
request["inputs"] = json.dumps(inputs)
|
|
request["inputs_by"] = 'step_index'
|
|
url = "workflows/%s/usage" % (workflow_id)
|
|
invocation_response = self._post(url, data=request)
|
|
if assert_ok:
|
|
api_asserts.assert_status_code_is(invocation_response, 200)
|
|
invocation_id = invocation_response.json()["id"]
|
|
return invocation_id
|
|
else:
|
|
return invocation_response
|
|
|
|
|
|
class WorkflowPopulator(BaseWorkflowPopulator, ImporterGalaxyInterface):
|
|
|
|
def __init__(self, galaxy_interactor):
|
|
self.galaxy_interactor = galaxy_interactor
|
|
self.dataset_populator = DatasetPopulator(galaxy_interactor)
|
|
|
|
def _post(self, route, data={}):
|
|
return self.galaxy_interactor.post(route, data)
|
|
|
|
def _get(self, route, data={}):
|
|
return self.galaxy_interactor.get(route, data=data)
|
|
|
|
# Required for ImporterGalaxyInterface interface - so we can recurisvely import
|
|
# nested workflows.
|
|
def import_workflow(self, workflow, **kwds):
|
|
workflow_str = json.dumps(workflow, indent=4)
|
|
data = {
|
|
'workflow': workflow_str,
|
|
}
|
|
data.update(**kwds)
|
|
upload_response = self._post("workflows", data=data)
|
|
assert upload_response.status_code == 200, upload_response
|
|
return upload_response.json()
|
|
|
|
|
|
class LibraryPopulator(object):
|
|
|
|
def __init__(self, galaxy_interactor):
|
|
self.galaxy_interactor = galaxy_interactor
|
|
self.dataset_populator = DatasetPopulator(galaxy_interactor)
|
|
|
|
def get_libraries(self):
|
|
get_response = self.galaxy_interactor.get("libraries")
|
|
return get_response.json()
|
|
|
|
def new_private_library(self, name):
|
|
library = self.new_library(name)
|
|
library_id = library["id"]
|
|
|
|
role_id = self.user_private_role_id()
|
|
self.set_permissions(library_id, role_id)
|
|
return library
|
|
|
|
def new_library(self, name):
|
|
data = dict(name=name)
|
|
create_response = self.galaxy_interactor.post("libraries", data=data, admin=True)
|
|
return create_response.json()
|
|
|
|
def set_permissions(self, library_id, role_id=None):
|
|
if role_id:
|
|
perm_list = json.dumps(role_id)
|
|
else:
|
|
perm_list = json.dumps([])
|
|
|
|
permissions = dict(
|
|
LIBRARY_ACCESS_in=perm_list,
|
|
LIBRARY_MODIFY_in=perm_list,
|
|
LIBRARY_ADD_in=perm_list,
|
|
LIBRARY_MANAGE_in=perm_list,
|
|
)
|
|
self.galaxy_interactor.post("libraries/%s/permissions" % library_id, data=permissions, admin=True)
|
|
|
|
def user_email(self):
|
|
users_response = self.galaxy_interactor.get("users")
|
|
users = users_response.json()
|
|
assert len(users) == 1
|
|
return users[0]["email"]
|
|
|
|
def user_private_role_id(self):
|
|
user_email = self.user_email()
|
|
roles_response = self.galaxy_interactor.get("roles", admin=True)
|
|
users_roles = [r for r in roles_response.json() if r["name"] == user_email]
|
|
assert len(users_roles) == 1
|
|
return users_roles[0]["id"]
|
|
|
|
def create_dataset_request(self, library, **kwds):
|
|
upload_option = kwds.get("upload_option", "upload_file")
|
|
create_data = {
|
|
"folder_id": kwds.get("folder_id", library["root_folder_id"]),
|
|
"create_type": "file",
|
|
"files_0|NAME": kwds.get("name", "NewFile"),
|
|
"upload_option": upload_option,
|
|
"file_type": kwds.get("file_type", "auto"),
|
|
"db_key": kwds.get("db_key", "?"),
|
|
}
|
|
if kwds.get("link_data"):
|
|
create_data["link_data_only"] = "link_to_files"
|
|
|
|
if upload_option == "upload_file":
|
|
files = {
|
|
"files_0|file_data": kwds.get("file", StringIO(kwds.get("contents", "TestData"))),
|
|
}
|
|
elif upload_option == "upload_paths":
|
|
create_data["filesystem_paths"] = kwds["paths"]
|
|
files = {}
|
|
elif upload_option == "upload_directory":
|
|
create_data["server_dir"] = kwds["server_dir"]
|
|
files = {}
|
|
|
|
return create_data, files
|
|
|
|
def new_library_dataset(self, name, **create_dataset_kwds):
|
|
library = self.new_private_library(name)
|
|
payload, files = self.create_dataset_request(library, **create_dataset_kwds)
|
|
dataset = self.raw_library_contents_create(library["id"], payload, files=files).json()[0]
|
|
return self.wait_on_library_dataset(library, dataset)
|
|
|
|
def wait_on_library_dataset(self, library, dataset):
|
|
def show():
|
|
return self.galaxy_interactor.get("libraries/%s/contents/%s" % (library["id"], dataset["id"]))
|
|
|
|
wait_on_state(show, assert_ok=True, timeout=DEFAULT_TIMEOUT)
|
|
return show().json()
|
|
|
|
def raw_library_contents_create(self, library_id, payload, files={}):
|
|
url_rel = "libraries/%s/contents" % library_id
|
|
return self.galaxy_interactor.post(url_rel, payload, files=files)
|
|
|
|
def show_ldda(self, library_id, library_dataset_id):
|
|
return self.galaxy_interactor.get("libraries/%s/contents/%s" % (library_id, library_dataset_id))
|
|
|
|
def new_library_dataset_in_private_library(self, library_name="private_dataset", wait=True):
|
|
library = self.new_private_library(library_name)
|
|
payload, files = self.create_dataset_request(library, file_type="txt", contents="create_test")
|
|
create_response = self.galaxy_interactor.post("libraries/%s/contents" % library["id"], payload, files=files)
|
|
api_asserts.assert_status_code_is(create_response, 200)
|
|
library_datasets = create_response.json()
|
|
assert len(library_datasets) == 1
|
|
library_dataset = library_datasets[0]
|
|
if wait:
|
|
def show():
|
|
return self.show_ldda(library["id"], library_dataset["id"])
|
|
|
|
wait_on_state(show, assert_ok=True)
|
|
library_dataset = show().json()
|
|
|
|
return library, library_dataset
|
|
|
|
def get_library_contents_with_path(self, library_id, path):
|
|
all_contents_response = self.galaxy_interactor.get("libraries/%s/contents" % library_id)
|
|
api_asserts.assert_status_code_is(all_contents_response, 200)
|
|
all_contents = all_contents_response.json()
|
|
matching = [c for c in all_contents if c["name"] == path]
|
|
if len(matching) == 0:
|
|
raise Exception("Failed to find library contents with path [%s], contents are %s" % (path, all_contents))
|
|
get_response = self.galaxy_interactor.get(matching[0]["url"])
|
|
api_asserts.assert_status_code_is(get_response, 200)
|
|
return get_response.json()
|
|
|
|
def setup_fetch_to_folder(self, test_name):
|
|
history_id = self.dataset_populator.new_history()
|
|
library = self.new_private_library(test_name)
|
|
folder_id = library["root_folder_id"][1:]
|
|
destination = {"type": "library_folder", "library_folder_id": folder_id}
|
|
return history_id, library, destination
|
|
|
|
|
|
class BaseDatasetCollectionPopulator(object):
|
|
|
|
def create_list_from_pairs(self, history_id, pairs, name="Dataset Collection from pairs"):
|
|
return self.create_nested_collection(history_id=history_id,
|
|
collection=pairs,
|
|
collection_type='list:paired',
|
|
name=name)
|
|
|
|
def nested_collection_identifiers(self, history_id, collection_type):
|
|
rank_types = list(reversed(collection_type.split(":")))
|
|
assert len(rank_types) > 0
|
|
rank_type_0 = rank_types[0]
|
|
if rank_type_0 == "list":
|
|
identifiers = self.list_identifiers(history_id)
|
|
else:
|
|
identifiers = self.pair_identifiers(history_id)
|
|
nested_collection_type = rank_type_0
|
|
|
|
for i, rank_type in enumerate(reversed(rank_types[1:])):
|
|
name = "test_level_%d" % (i + 1) if rank_type == "list" else "paired"
|
|
identifiers = [dict(
|
|
src="new_collection",
|
|
name=name,
|
|
collection_type=nested_collection_type,
|
|
element_identifiers=identifiers,
|
|
)]
|
|
nested_collection_type = "%s:%s" % (rank_type, nested_collection_type)
|
|
return identifiers
|
|
|
|
def create_nested_collection(self, history_id, collection_type, name=None, collection=None, element_identifiers=None):
|
|
"""Create a nested collection either from collection or using collection_type)."""
|
|
assert collection_type is not None
|
|
name = name or "Test %s" % collection_type
|
|
if collection is not None:
|
|
assert element_identifiers is None
|
|
element_identifiers = []
|
|
for i, pair in enumerate(collection):
|
|
element_identifiers.append(dict(
|
|
name="test%d" % i,
|
|
src="hdca",
|
|
id=pair
|
|
))
|
|
if element_identifiers is None:
|
|
element_identifiers = self.nested_collection_identifiers(history_id, collection_type)
|
|
|
|
payload = dict(
|
|
instance_type="history",
|
|
history_id=history_id,
|
|
element_identifiers=json.dumps(element_identifiers),
|
|
collection_type=collection_type,
|
|
name=name,
|
|
)
|
|
return self.__create(payload)
|
|
|
|
def create_list_of_pairs_in_history(self, history_id, **kwds):
|
|
pair1 = self.create_pair_in_history(history_id, **kwds).json()["id"]
|
|
return self.create_list_from_pairs(history_id, [pair1])
|
|
|
|
def create_list_of_list_in_history(self, history_id, **kwds):
|
|
# create_nested_collection will generate nested collection from just datasets,
|
|
# this function uses recursive generation of history hdcas.
|
|
collection_type = kwds.pop('collection_type', 'list:list')
|
|
collection_types = collection_type.split(':')
|
|
list = self.create_list_in_history(history_id, **kwds).json()['id']
|
|
current_collection_type = 'list'
|
|
for collection_type in collection_types[1:]:
|
|
current_collection_type = "%s:%s" % (current_collection_type, collection_type)
|
|
response = self.create_nested_collection(history_id=history_id,
|
|
collection_type=current_collection_type,
|
|
name=current_collection_type,
|
|
collection=[list])
|
|
list = response.json()['id']
|
|
return response
|
|
|
|
def create_pair_in_history(self, history_id, **kwds):
|
|
payload = self.create_pair_payload(
|
|
history_id,
|
|
instance_type="history",
|
|
**kwds
|
|
)
|
|
return self.__create(payload)
|
|
|
|
def create_list_in_history(self, history_id, **kwds):
|
|
payload = self.create_list_payload(
|
|
history_id,
|
|
instance_type="history",
|
|
**kwds
|
|
)
|
|
return self.__create(payload)
|
|
|
|
def create_list_payload(self, history_id, **kwds):
|
|
return self.__create_payload(history_id, identifiers_func=self.list_identifiers, collection_type="list", **kwds)
|
|
|
|
def create_pair_payload(self, history_id, **kwds):
|
|
return self.__create_payload(history_id, identifiers_func=self.pair_identifiers, collection_type="paired", **kwds)
|
|
|
|
def __create_payload(self, history_id, identifiers_func, collection_type, **kwds):
|
|
contents = None
|
|
if "contents" in kwds:
|
|
contents = kwds["contents"]
|
|
del kwds["contents"]
|
|
|
|
if "element_identifiers" not in kwds:
|
|
kwds["element_identifiers"] = json.dumps(identifiers_func(history_id, contents=contents))
|
|
|
|
if "name" not in kwds:
|
|
kwds["name"] = "Test Dataset Collection"
|
|
|
|
payload = dict(
|
|
history_id=history_id,
|
|
collection_type=collection_type,
|
|
**kwds
|
|
)
|
|
return payload
|
|
|
|
def pair_identifiers(self, history_id, contents=None):
|
|
hda1, hda2 = self.__datasets(history_id, count=2, contents=contents)
|
|
|
|
element_identifiers = [
|
|
dict(name="forward", src="hda", id=hda1["id"]),
|
|
dict(name="reverse", src="hda", id=hda2["id"]),
|
|
]
|
|
return element_identifiers
|
|
|
|
def list_identifiers(self, history_id, contents=None):
|
|
count = 3 if contents is None else len(contents)
|
|
# Contents can be a list of strings (with name auto-assigned here) or a list of
|
|
# 2-tuples of form (name, dataset_content).
|
|
if contents and isinstance(contents[0], tuple):
|
|
hdas = self.__datasets(history_id, count=count, contents=[c[1] for c in contents])
|
|
|
|
def hda_to_identifier(i, hda):
|
|
return dict(name=contents[i][0], src="hda", id=hda["id"])
|
|
else:
|
|
hdas = self.__datasets(history_id, count=count, contents=contents)
|
|
|
|
def hda_to_identifier(i, hda):
|
|
return dict(name="data%d" % (i + 1), src="hda", id=hda["id"])
|
|
element_identifiers = [hda_to_identifier(i, hda) for (i, hda) in enumerate(hdas)]
|
|
return element_identifiers
|
|
|
|
def __create(self, payload):
|
|
return self._create_collection(payload)
|
|
|
|
def __datasets(self, history_id, count, contents=None):
|
|
datasets = []
|
|
for i in range(count):
|
|
new_kwds = {}
|
|
if contents:
|
|
new_kwds["content"] = contents[i]
|
|
datasets.append(self.dataset_populator.new_dataset(history_id, **new_kwds))
|
|
return datasets
|
|
|
|
def wait_for_dataset_collection(self, create_payload, assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
for element in create_payload["elements"]:
|
|
if element['element_type'] == 'hda':
|
|
self.dataset_populator.wait_for_dataset(history_id=element['object']['history_id'],
|
|
dataset_id=element['object']['id'],
|
|
assert_ok=assert_ok,
|
|
timeout=timeout)
|
|
elif element['element_type'] == 'dataset_collection':
|
|
self.wait_for_dataset_collection(element['object'], assert_ok=assert_ok, timeout=timeout)
|
|
|
|
|
|
class DatasetCollectionPopulator(BaseDatasetCollectionPopulator):
|
|
|
|
def __init__(self, galaxy_interactor):
|
|
self.galaxy_interactor = galaxy_interactor
|
|
self.dataset_populator = DatasetPopulator(galaxy_interactor)
|
|
|
|
def _create_collection(self, payload):
|
|
create_response = self.galaxy_interactor.post("dataset_collections", data=payload)
|
|
return create_response
|
|
|
|
|
|
def load_data_dict(history_id, test_data, dataset_populator, dataset_collection_populator):
|
|
|
|
def read_test_data(test_dict):
|
|
test_data_resolver = TestDataResolver()
|
|
filename = test_data_resolver.get_filename(test_dict["value"])
|
|
content = open(filename, "r").read()
|
|
return content
|
|
|
|
inputs = {}
|
|
label_map = {}
|
|
has_uploads = False
|
|
|
|
for key, value in test_data.items():
|
|
is_dict = isinstance(value, dict)
|
|
if is_dict and ("elements" in value or value.get("type", None) in ["list:paired", "list", "paired"]):
|
|
elements_data = value.get("elements", [])
|
|
elements = []
|
|
for element_data in elements_data:
|
|
identifier = element_data["identifier"]
|
|
input_type = element_data.get("type", "raw")
|
|
if input_type == "File":
|
|
content = read_test_data(element_data)
|
|
else:
|
|
content = element_data["content"]
|
|
elements.append((identifier, content))
|
|
# TODO: make this collection_type
|
|
collection_type = value["type"]
|
|
new_collection_kwds = {}
|
|
if "name" in value:
|
|
new_collection_kwds["name"] = value["name"]
|
|
if collection_type == "list:paired":
|
|
hdca = dataset_collection_populator.create_list_of_pairs_in_history(history_id, **new_collection_kwds).json()
|
|
elif collection_type == "list":
|
|
hdca = dataset_collection_populator.create_list_in_history(history_id, contents=elements, **new_collection_kwds).json()
|
|
else:
|
|
hdca = dataset_collection_populator.create_pair_in_history(history_id, contents=elements, **new_collection_kwds).json()
|
|
label_map[key] = dataset_populator.ds_entry(hdca)
|
|
inputs[key] = hdca
|
|
has_uploads = True
|
|
elif is_dict and "type" in value:
|
|
input_type = value["type"]
|
|
if input_type == "File":
|
|
content = read_test_data(value)
|
|
new_dataset_kwds = {
|
|
"content": content
|
|
}
|
|
if "name" in value:
|
|
new_dataset_kwds["name"] = value["name"]
|
|
if "file_type" in value:
|
|
new_dataset_kwds["file_type"] = value["file_type"]
|
|
hda = dataset_populator.new_dataset(history_id, **new_dataset_kwds)
|
|
label_map[key] = dataset_populator.ds_entry(hda)
|
|
has_uploads = True
|
|
elif input_type == "raw":
|
|
label_map[key] = value["value"]
|
|
inputs[key] = value["value"]
|
|
elif not is_dict:
|
|
has_uploads = True
|
|
hda = dataset_populator.new_dataset(history_id, content=value)
|
|
label_map[key] = dataset_populator.ds_entry(hda)
|
|
inputs[key] = hda
|
|
else:
|
|
raise ValueError("Invalid test_data def %" % test_data)
|
|
|
|
return inputs, label_map, has_uploads
|
|
|
|
|
|
def wait_on_state(state_func, desc="state", skip_states=["running", "queued", "new", "ready"], assert_ok=False, timeout=DEFAULT_TIMEOUT):
|
|
def get_state():
|
|
response = state_func()
|
|
assert response.status_code == 200, "Failed to fetch state update while waiting."
|
|
state = response.json()["state"]
|
|
if state in skip_states:
|
|
return None
|
|
else:
|
|
if assert_ok:
|
|
assert state == "ok", "Final state - %s - not okay." % state
|
|
return state
|
|
try:
|
|
return wait_on(get_state, desc=desc, timeout=timeout)
|
|
except TimeoutAssertionError as e:
|
|
response = state_func()
|
|
raise TimeoutAssertionError("%s Current response containing state [%s]." % (e.message, response.json()))
|
|
|
|
|
|
class GiPostGetMixin:
|
|
"""Mixin for adapting Galaxy testing populators helpers to bioblend."""
|
|
|
|
def _get(self, route, data={}):
|
|
return self._gi.make_get_request(self.__url(route), data)
|
|
|
|
def _post(self, route, data={}):
|
|
data = data.copy()
|
|
data['key'] = self._gi.key
|
|
return requests.post(self.__url(route), data=data)
|
|
|
|
def _delete(self, route, data={}):
|
|
data = data.copy()
|
|
data['key'] = self._gi.key
|
|
return requests.delete(self.__url(route), data=data)
|
|
|
|
def __url(self, route):
|
|
return self._gi.url + "/" + route
|
|
|
|
|
|
class GiDatasetPopulator(BaseDatasetPopulator, GiPostGetMixin):
|
|
|
|
"""Implementation of BaseDatasetPopulator backed by bioblend."""
|
|
|
|
def __init__(self, gi):
|
|
"""Construct a dataset populator from a bioblend GalaxyInstance."""
|
|
self._gi = gi
|
|
|
|
|
|
class GiDatasetCollectionPopulator(BaseDatasetCollectionPopulator, GiPostGetMixin):
|
|
|
|
"""Implementation of BaseDatasetCollectionPopulator backed by bioblend."""
|
|
|
|
def __init__(self, gi):
|
|
"""Construct a dataset collection populator from a bioblend GalaxyInstance."""
|
|
self._gi = gi
|
|
self.dataset_populator = GiDatasetPopulator(gi)
|
|
|
|
def _create_collection(self, payload):
|
|
create_response = self._post("dataset_collections", data=payload)
|
|
return create_response
|
|
|
|
|
|
class GiWorkflowPopulator(BaseWorkflowPopulator, GiPostGetMixin):
|
|
|
|
"""Implementation of BaseWorkflowPopulator backed by bioblend."""
|
|
|
|
def __init__(self, gi):
|
|
"""Construct a workflow populator from a bioblend GalaxyInstance."""
|
|
self._gi = gi
|
|
self.dataset_populator = GiDatasetPopulator(gi)
|
|
|
|
|
|
def wait_on(function, desc, timeout=DEFAULT_TIMEOUT):
|
|
delta = .25
|
|
iteration = 0
|
|
while True:
|
|
total_wait = delta * iteration
|
|
if total_wait > timeout:
|
|
timeout_message = "Timed out after %s seconds waiting on %s." % (
|
|
total_wait, desc
|
|
)
|
|
raise TimeoutAssertionError(timeout_message)
|
|
iteration += 1
|
|
value = function()
|
|
if value is not None:
|
|
return value
|
|
time.sleep(delta)
|
|
|
|
|
|
class TimeoutAssertionError(AssertionError):
|
|
|
|
def __init__(self, message):
|
|
super(TimeoutAssertionError, self).__init__(message)
|