Skip to content

Commit f1d5921

Browse files
committed
Refactor tool testing.
- Introduce a new test command in addition to existing --test flag for install and update, try to sort out what options belong where. - Some basic reporting for passing and failing tests. - Have test outcomes affect the exit code (1 for install failures, 2 for installs work but tests fail). - General refactoring of test methods into smaller pieces.
1 parent aad404b commit f1d5921

1 file changed

Lines changed: 156 additions & 99 deletions

File tree

ephemeris/shed_tools.py

Lines changed: 156 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
INSTALL_TOOL_DEPENDENCIES = False
6969
INSTALL_REPOSITORY_DEPENDENCIES = True
7070
INSTALL_RESOLVER_DEPENDENCIES = True
71+
EXIT_CODE_INSTALL_ERRORS = 1
72+
EXIT_CODE_TOOL_TEST_ERRORS = 2
7173

7274

7375
def _ensure_log_configured(name):
@@ -278,7 +280,7 @@ def _parser():
278280
# update_tools in the name space and shed-tool update will not return all the install
279281
# variables.
280282
parser.set_defaults(
281-
update_tools=False,
283+
action="install",
282284
tool_list_file=None,
283285
tool_yaml=None,
284286
owner=None,
@@ -300,22 +302,51 @@ def _parser():
300302
"Use shed-tools install --help for more information",
301303
parents=[common_arguments],
302304
)
303-
install_command_parser.add_argument(
304-
"-t", "--toolsfile",
305-
dest="tool_list_file",
306-
help="Tools file to use (see tool_list.yaml.sample)",)
307-
install_command_parser.add_argument(
308-
"-y", "--yaml_tool",
309-
dest="tool_yaml",
310-
help="Install tool represented by yaml string",)
311-
install_command_parser.add_argument(
312-
"--name",
313-
help="The name of the tool to install (only applicable "
314-
"if the tools file is not provided).")
315-
install_command_parser.add_argument(
316-
"--owner",
317-
help="The owner of the tool to install (only applicable "
318-
"if the tools file is not provided).")
305+
update_command_parser = subparsers.add_parser(
306+
"update",
307+
help="This updates all tools in Galaxy to the latest revision. "
308+
"Use shed-tools update --help for more information",
309+
parents=[common_arguments])
310+
311+
test_command_parser = subparsers.add_parser(
312+
"test",
313+
help="This tests the supplied list of tools in Galaxy. "
314+
"Use shed-tools test --help for more information",
315+
)
316+
317+
for command_parser in [install_command_parser, test_command_parser]:
318+
command_parser.add_argument(
319+
"-t", "--toolsfile",
320+
dest="tool_list_file",
321+
help="Tools file to use (see tool_list.yaml.sample)",)
322+
command_parser.add_argument(
323+
"-y", "--yaml_tool",
324+
dest="tool_yaml",
325+
help="Install tool represented by yaml string",)
326+
command_parser.add_argument(
327+
"--name",
328+
help="The name of the tool to install (only applicable "
329+
"if the tools file is not provided).")
330+
command_parser.add_argument(
331+
"--owner",
332+
help="The owner of the tool to install (only applicable "
333+
"if the tools file is not provided).")
334+
command_parser.add_argument(
335+
"--revisions",
336+
default=None,
337+
nargs='*',
338+
dest="revisions",
339+
help="The revisions of the tool repository that will be installed. "
340+
"All revisions must be specified after this flag by a space."
341+
"Example: --revisions 0a5c7992b1ac f048033da666"
342+
"(Only applicable if the tools file is not provided).")
343+
command_parser.add_argument(
344+
"--toolshed",
345+
dest="tool_shed_url",
346+
help="The Tool Shed URL where to install the tool from. "
347+
"This is applicable only if the tool info is "
348+
"provided as an option vs. in the tools file.")
349+
319350
install_command_parser.add_argument(
320351
"--section",
321352
dest="tool_panel_section_id",
@@ -329,21 +360,6 @@ def _parser():
329360
help="Galaxy tool panel section label where tool will be installed "
330361
"(if the section does not exist, it will be created; "
331362
"only applicable if the tools file is not provided).")
332-
install_command_parser.add_argument(
333-
"--revisions",
334-
default=None,
335-
nargs='*',
336-
dest="revisions",
337-
help="The revisions of the tool repository that will be installed. "
338-
"All revisions must be specified after this flag by a space."
339-
"Example: --revisions 0a5c7992b1ac f048033da666"
340-
"(Only applicable if the tools file is not provided).")
341-
install_command_parser.add_argument(
342-
"--toolshed",
343-
dest="tool_shed_url",
344-
help="The Tool Shed URL where to install the tool from. "
345-
"This is applicable only if the tool info is "
346-
"provided as an option vs. in the tools file.")
347363
install_command_parser.add_argument(
348364
"--skip_install_tool_dependencies",
349365
action="store_true",
@@ -364,34 +380,40 @@ def _parser():
364380
dest="force_latest_revision",
365381
help="Will override the revisions in the tools file and always install the latest revision.")
366382

367-
update_command_parser = subparsers.add_parser(
368-
"update",
369-
help="This updates all tools in Galaxy to the latest revision."
370-
"Use shed-tools update --help for more information",
371-
parents=[common_arguments])
372-
373383
for command_parser in [update_command_parser, install_command_parser]:
374384
command_parser.add_argument(
375385
"--test",
376386
action="store_true",
377387
dest="test",
378388
help="Run tool tests on install tools, requires Galaxy 18.05 or newer."
379389
)
380-
command_parser.add_argument(
381-
"--test_json",
382-
dest="test_json",
383-
help="If --test is specified, record tool test output to specified file. "
384-
"This file can be turned into nice reports with ``planemo test_reports <output.json>``."
385-
)
386390
command_parser.add_argument(
387391
"--test_existing",
388392
action="store_true",
389-
help="If --test is specified, also run tool tests on repositories already installed "
393+
help="If testing tools during install, also run tool tests on repositories already installed "
390394
"(i.e. skipped repositories)."
391395
)
396+
command_parser.add_argument(
397+
"--test_json",
398+
dest="test_json",
399+
help="If testing tools, record tool test output to specified file. "
400+
"This file can be turned into reports with ``planemo test_reports <output.json>``."
401+
)
402+
403+
# Same test_json as above but language modified for test instead of install/update.
404+
test_command_parser.add_argument(
405+
"--test_json",
406+
dest="test_json",
407+
help="Record tool test output to specified file. "
408+
"This file can be turned into reports with ``planemo test_reports <output.json>``."
409+
)
392410

393411
update_command_parser.set_defaults(
394-
update_tools=True,
412+
action="update",
413+
)
414+
415+
test_command_parser.set_defaults(
416+
action="test"
395417
)
396418

397419
return parser
@@ -517,7 +539,7 @@ def get_install_repository_manager(options):
517539
'install_tool_dependencies', INSTALL_TOOL_DEPENDENCIES)
518540
elif options.tool_yaml:
519541
repositories = [yaml.safe_load(options.tool_yaml)]
520-
elif options.update_tools:
542+
elif options.action == "update":
521543
get_repository_list = GiToToolYaml(
522544
gi=gi,
523545
skip_tool_panel_section_name=False,
@@ -546,7 +568,7 @@ def get_install_repository_manager(options):
546568

547569
install_resolver_dependencies = options.install_resolver_dependencies or install_resolver_dependencies
548570

549-
force_latest_revision = options.force_latest_revision or options.update_tools
571+
force_latest_revision = options.force_latest_revision or options.action == "update"
550572

551573
return InstallToolManager(repositories=repositories,
552574
gi=gi,
@@ -587,10 +609,11 @@ def __init__(self,
587609
self.test = test
588610
self.test_existing = test_existing
589611
self.test_json = test_json
612+
self.tests_passed = []
590613
self.test_exceptions = []
591614

592615
def install_repositories(self):
593-
"""
616+
"""Attempt to ensure each repository in ``self.repositories`` is installed.
594617
"""
595618
installation_start = dt.datetime.now()
596619
installed_repositories_list = installed_repository_revisions(self.gi) # installed tools list
@@ -696,52 +719,81 @@ def install_repositories(self):
696719
)
697720
log.info("All repositories have been installed.")
698721
if self.test:
699-
installed_tools = []
700-
target_repos = self.installed_repositories
722+
target_repositories = self.installed_repositories
701723
if self.test_existing:
702-
target_repos += self.skipped_repositories
703-
for installed_repository in target_repos:
704-
repo_tools = tools_for_repository(self.gi, installed_repository)
705-
installed_tools.extend(repo_tools)
706-
707-
test_results = []
708-
709-
for tool in installed_tools:
710-
galaxy_interactor_kwds = {
711-
"galaxy_url": re.sub('/api', '', self.gi.url),
712-
"master_api_key": self.gi.key,
713-
"api_key": None, # TODO
714-
"keep_outputs_dir": '',
715-
}
716-
galaxy_interactor = GalaxyInteractorApi(**galaxy_interactor_kwds)
717-
tool_id = tool["id"]
718-
tool_version = tool["version"]
719-
tool_test_dicts = galaxy_interactor.get_tool_tests(tool_id, tool_version=tool_version)
720-
test_indices = list(range(len(tool_test_dicts)))
721-
for test_index in test_indices:
722-
def register(job_data):
723-
test_results.append({
724-
'id': tool_id + "-" + str(test_index),
725-
'has_data': True,
726-
'data': job_data,
727-
})
728-
729-
try:
730-
verify_tool(
731-
tool_id, galaxy_interactor, test_index=test_index, tool_version=tool_version,
732-
register_job_data=register, quiet=True
733-
)
734-
except Exception as e:
735-
self.test_exceptions.append(e)
724+
target_repositories += self.skipped_repositories
725+
self.test_repositories(target_repositories=target_repositories)
726+
log.info("Total run time: {0}".format(dt.datetime.now() - installation_start))
736727

737-
report_obj = {
738-
'version': '0.1',
739-
'tests': test_results,
740-
}
741-
with open(self.test_json or "tool_test_output.json", "w") as f:
742-
json.dump(report_obj, f)
728+
def test_repositories(self, target_repositories=None):
729+
"""Run tool tests for each tool in supplied repositories list or ``self.repositories``.
730+
"""
731+
tool_test_start = dt.datetime.now()
732+
if target_repositories is None:
733+
# Consider a variant of this that doesn't even consume a tool list YAML? target
734+
# something like installed_repository_revisions(self.gi)
735+
target_repositories = self.repositories
736+
installed_tools = []
737+
for target_repository in target_repositories:
738+
repo_tools = tools_for_repository(self.gi, target_repository)
739+
installed_tools.extend(repo_tools)
740+
741+
all_test_results = []
742+
743+
for tool in installed_tools:
744+
tool_test_results = self._test_tool(tool)
745+
all_test_results.extend(tool_test_results)
746+
747+
report_obj = {
748+
'version': '0.1',
749+
'tests': all_test_results,
750+
}
751+
with open(self.test_json or "tool_test_output.json", "w") as f:
752+
json.dump(report_obj, f)
753+
log.info("Passed tool tests ({0}): {1}".format(
754+
len(self.tests_passed),
755+
[t for t in self.tests_passed])
756+
)
757+
log.info("Failed tool tests ({0}): {1}".format(
758+
len(self.test_exceptions),
759+
[t[0] for t in self.errored_repositories])
760+
)
761+
log.info("Total tool test time: {0}".format(dt.datetime.now() - tool_test_start))
762+
763+
def _test_tool(self, tool):
764+
galaxy_interactor_kwds = {
765+
"galaxy_url": re.sub('/api', '', self.gi.url),
766+
"master_api_key": self.gi.key,
767+
"api_key": None, # TODO
768+
"keep_outputs_dir": '',
769+
}
770+
galaxy_interactor = GalaxyInteractorApi(**galaxy_interactor_kwds)
771+
tool_id = tool["id"]
772+
tool_version = tool["version"]
773+
tool_test_dicts = galaxy_interactor.get_tool_tests(tool_id, tool_version=tool_version)
774+
test_indices = list(range(len(tool_test_dicts)))
775+
tool_test_results = []
776+
777+
for test_index in test_indices:
778+
test_id = tool_id + "-" + str(test_index)
779+
780+
def register(job_data):
781+
tool_test_results.append({
782+
'id': test_id,
783+
'has_data': True,
784+
'data': job_data,
785+
})
786+
787+
try:
788+
verify_tool(
789+
tool_id, galaxy_interactor, test_index=test_index, tool_version=tool_version,
790+
register_job_data=register, quiet=True
791+
)
792+
self.tests_passed.append(test_id)
793+
except Exception as e:
794+
self.test_exceptions.append((test_id, e))
743795

744-
log.info("Total run time: {0}".format(dt.datetime.now() - installation_start))
796+
return tool_test_results
745797

746798
def create_repository_install_payload(self, repository_info):
747799
"""
@@ -820,23 +872,28 @@ def main():
820872
disable_external_library_logging()
821873
log = setup_global_logger(name=__name__, log_file='/tmp/galaxy_tool_install.log')
822874
options = _parse_cli_options()
875+
install_tool_manager = None
823876
if options.tool_list_file or options.tool_yaml or \
824877
options.name and options.owner and (options.tool_panel_section_id or options.tool_panel_section_label):
825-
if options.update_tools:
826-
sys.exit("--update can not be used together with tools to be installed.")
878+
if options.action == "update":
879+
sys.exit("update command can not be used together with tools to be installed.")
827880
install_tool_manager = get_install_repository_manager(options)
828-
install_tool_manager.install_repositories()
829-
if install_tool_manager.errored_repositories:
830-
sys.exit(1)
881+
if options.action == "test":
882+
install_tool_manager.test_repositories()
883+
else:
884+
install_tool_manager.install_repositories()
831885
elif options.update_tools:
832886
install_tool_manager = get_install_repository_manager(options)
833887
install_tool_manager.install_repositories()
834-
if install_tool_manager.errored_repositories:
835-
sys.exit(1)
836888
else:
837889
sys.exit("Must provide a tool list file, individual tools info , a list of data manager tasks or issue the update command. "
838890
"Look at usage.")
839891

892+
if install_tool_manager.errored_repositories:
893+
sys.exit(EXIT_CODE_INSTALL_ERRORS)
894+
elif install_tool_manager.test_exceptions:
895+
sys.exit(EXIT_CODE_TOOL_TEST_ERRORS)
896+
840897

841898
if __name__ == "__main__":
842899
main()

0 commit comments

Comments
 (0)