Skip to content

Commit f663ecf

Browse files
committed
Implement a --failed flag for the test command.
This will use ``tool_test_output.json`` to determine which tests failed the last time ``planemo test`` was executed and only run those previously failing tests. This should speed up test-driven development of tools. Requires galaxyproject/galaxy#266. Closes #145.
1 parent cdbf55a commit f663ecf

2 files changed

Lines changed: 103 additions & 39 deletions

File tree

planemo/commands/cmd_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@
8282
"reports for more complete summary). Set to 'none' to disable "
8383
"completely.")
8484
)
85+
@click.option(
86+
"--failed",
87+
is_flag=True,
88+
help="Re-run only failed tests. This command will read "
89+
"tool_test_output.json to determine which tests failed so this "
90+
"file must have been produced with the same set of tool ids "
91+
"previously.",
92+
default=False,
93+
)
8594
@options.galaxy_root_option()
8695
@options.install_galaxy_option()
8796
@options.no_cache_galaxy_option()
@@ -149,6 +158,7 @@ def cli(ctx, path, **kwds):
149158
html_report_file,
150159
xunit_report_file,
151160
structured_report_file,
161+
failed=kwds["failed"],
152162
).build()
153163
cmd = "; ".join([
154164
cd_to_galaxy_command,

planemo/galaxy_test.py

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" Utilities for reasoning about Galaxy test results.
22
"""
3+
from __future__ import print_function
34
from __future__ import absolute_import
45

56
from collections import namedtuple
@@ -18,10 +19,12 @@ def __init__(
1819
html_report_file,
1920
xunit_report_file,
2021
structured_report_file,
22+
failed=False,
2123
):
2224
self.html_report_file = html_report_file
2325
self.xunit_report_file = xunit_report_file
2426
self.structured_report_file = structured_report_file
27+
self.failed = failed
2528

2629
def build(self):
2730
xunit_report_file = self.xunit_report_file
@@ -36,58 +39,45 @@ def build(self):
3639
else:
3740
sd_arg = ""
3841
tests = "functional.test_toolbox"
42+
if self.failed:
43+
sd = StructuredData(self.structured_report_file)
44+
failed_ids = sd.failed_ids
45+
tests = " ".join(failed_ids)
3946
return RUN_TESTS_CMD % (html_report_file, xunit_arg, sd_arg, tests)
4047

4148

42-
class GalaxyTestResults(object):
43-
""" Class that combine the test-centric xunit output
44-
with the Galaxy centric structured data output - and
45-
abstracts away the difference (someday).
49+
class StructuredData(object):
50+
""" Abstraction around Galaxy's structured test data output.
4651
"""
4752

48-
def __init__(
49-
self,
50-
output_json_path,
51-
output_xml_path,
52-
output_html_path,
53-
exit_code,
54-
):
55-
self.output_html_path = output_html_path
56-
self.exit_code = exit_code
53+
def __init__(self, json_path):
54+
self.json_path = json_path
5755
try:
58-
output_json_f = open(output_json_path, "r")
59-
structured_data = json.load(output_json_f)
60-
structured_data_tests = structured_data["tests"]
56+
with open(json_path, "r") as output_json_f:
57+
structured_data = json.load(output_json_f)
58+
structured_data_tests = structured_data["tests"]
6159
except Exception:
62-
# Older Galaxy's will not support this option.
60+
print("Warning: Targetting older Galaxy which did not "
61+
"produce a structured test results files.")
6362
structured_data = {}
6463
structured_data_tests = {}
6564
self.structured_data = structured_data
6665
self.structured_data_tests = structured_data_tests
67-
6866
structured_data_by_id = {}
6967
for test in self.structured_data_tests:
7068
structured_data_by_id[test["id"]] = test["data"]
7169
self.structured_data_by_id = structured_data_by_id
70+
self.has_details = "summary" in structured_data
71+
if self.has_details:
72+
self._read_summary()
7273

73-
if output_xml_path:
74-
self.xunit_tree = parse_xunit_report(output_xml_path)
75-
self.__merge_xunit()
76-
self.has_details = True
77-
else:
78-
self.xunit_tree = ET.fromstring("<testsuite />")
79-
self.has_details = False
80-
try:
81-
json.dump(self.structured_data, open(output_json_path, "w"))
82-
except Exception:
83-
pass
84-
85-
@property
86-
def _xunit_root(self):
87-
return self.xunit_tree.getroot()
74+
def update(self):
75+
with open(self.json_path, "w") as out_f:
76+
json.dump(self.structured_data, out_f)
8877

89-
def __merge_xunit(self):
90-
xunit_attrib = self._xunit_root.attrib
78+
def merge_xunit(self, xunit_root):
79+
self.has_details = True
80+
xunit_attrib = xunit_root.attrib
9181
num_tests = int(xunit_attrib.get("tests", 0))
9282
num_failures = int(xunit_attrib.get("failures", 0))
9383
num_errors = int(xunit_attrib.get("errors", 0))
@@ -103,7 +93,7 @@ def __merge_xunit(self):
10393
self.num_tests = num_tests
10494
self.num_problems = num_skips + num_errors + num_failures
10595

106-
for testcase_el in self.xunit_testcase_elements:
96+
for testcase_el in xunit_testcase_elements_from_root(xunit_root):
10797
test = case_id(testcase_el)
10898
test_data = self.structured_data_by_id.get(test.id)
10999
if not test_data:
@@ -121,14 +111,78 @@ def __merge_xunit(self):
121111
status = "success"
122112
test_data["status"] = status
123113

114+
def _read_summary(self):
115+
# TODO: read relevant data out of summary object.
116+
pass
117+
118+
@property
119+
def failed_ids(self):
120+
ids = set([])
121+
for test_data in self.structured_data_tests:
122+
if test_data["data"]["status"] == "success":
123+
continue
124+
test_case = test_data["id"].replace(".test_toolbox.", ".test_toolbox:")
125+
#test_case = test_case[0:test_case.find(".test_tool_")]
126+
ids.add(test_case)
127+
return ids
128+
129+
130+
class GalaxyTestResults(object):
131+
""" Class that combine the test-centric xunit output
132+
with the Galaxy centric structured data output - and
133+
abstracts away the difference (someday).
134+
"""
135+
136+
def __init__(
137+
self,
138+
output_json_path,
139+
output_xml_path,
140+
output_html_path,
141+
exit_code,
142+
):
143+
self.output_html_path = output_html_path
144+
self.exit_code = exit_code
145+
sd = StructuredData(output_json_path)
146+
self.sd = sd
147+
self.structured_data = sd.structured_data
148+
self.structured_data_tests = sd.structured_data_tests
149+
self.structured_data_by_id = sd.structured_data_by_id
150+
151+
if output_xml_path:
152+
self.xunit_tree = parse_xunit_report(output_xml_path)
153+
sd.merge_xunit(self._xunit_root)
154+
else:
155+
self.xunit_tree = ET.fromstring("<testsuite />")
156+
self.sd.update()
157+
158+
@property
159+
def has_details(self):
160+
return self.sd.has_details
161+
162+
@property
163+
def num_tests(self):
164+
return self.sd.num_tests
165+
166+
@property
167+
def num_problems(self):
168+
return self.sd.num_problems
169+
170+
@property
171+
def _xunit_root(self):
172+
return self.xunit_tree.getroot()
173+
124174
@property
125175
def all_tests_passed(self):
126-
return self.num_problems == 0
176+
return self.sd.num_problems == 0
127177

128178
@property
129179
def xunit_testcase_elements(self):
130-
for testcase_el in find_cases(self._xunit_root):
131-
yield testcase_el
180+
return xunit_testcase_elements_from_root(self._xunit_root)
181+
182+
183+
def xunit_testcase_elements_from_root(xunit_root):
184+
for testcase_el in find_cases(xunit_root):
185+
yield testcase_el
132186

133187

134188
def parse_xunit_report(xunit_report_path):

0 commit comments

Comments
 (0)