Make new report to support task file and remove old report

Make api.task.export to support the argument of task results, we
could transform json report to other report type, e.g:html,junit...,
and transform old json to new json format.

Change-Id: I512fc56cf768ee3ad67c65bbf03968a283cbf7d5
This commit is contained in:
chenhb 2017-10-14 13:55:08 +08:00
parent 4e2e6658c1
commit 0ae1453445
5 changed files with 57 additions and 281 deletions

View File

@ -606,17 +606,21 @@ class _Task(APIGroup):
return task_inst.to_dict()
def export(self, tasks_uuids, output_type, output_dest=None):
def export(self, tasks, output_type, output_dest=None):
"""Generate a report for a task or a few tasks.
:param tasks_uuids: List of tasks UUIDs
:param tasks: List of tasks UUIDs or tasks results
:param output_type: Plugin name of task exporter
:param output_dest: Destination for task report
"""
tasks_results = []
for task_uuid in tasks_uuids:
tasks_results.append(self.get(task_id=task_uuid, detailed=True))
tasks = tasks or []
for task in tasks:
if isinstance(task, dict):
tasks_results.append(task)
else:
tasks_results.append(self.get(task_id=task, detailed=True))
errors = texporter.TaskExporter.validate(
output_type, context={}, config={},
@ -630,7 +634,8 @@ class _Task(APIGroup):
reporter_cls = texporter.TaskExporter.get(output_type)
LOG.info("Building '%s' report for the following task(s): '%s'."
% (output_type, "', '".join(tasks_uuids)))
% (output_type,
"', '".join([task["uuid"] for task in tasks_results])))
result = texporter.TaskExporter.make(reporter_cls,
tasks_results,
output_dest,

View File

@ -31,7 +31,6 @@ import six
from rally.cli import cliutils
from rally.cli import envutils
from rally.common import fileutils
from rally.common.io import junit
from rally.common import logging
from rally.common import utils as rutils
from rally.common import version
@ -851,95 +850,17 @@ class TaskCommands(object):
release="0.10.0",
alternative=("rally task export "
"--type junit-xml"))
@cliutils.args("--uuid", dest="task_id", nargs="+", type=str,
help="UUIDs of tasks")
@envutils.with_default_task_id
@cliutils.args("--uuid", dest="tasks", nargs="+", type=str,
help="UUIDs of tasks or json reports of tasks")
@envutils.default_from_global("tasks", envutils.ENV_TASK, "uuid")
@cliutils.suppress_warnings
def report(self, api, task_id=None, out=None,
def report(self, api, tasks=None, out=None,
open_it=False, out_format="html"):
"""Generate a report for the specified task(s)."""
if [task for task in task_id if os.path.exists(
os.path.expanduser(task))]:
self._old_report(api, tasks=task_id, out=out,
open_it=open_it, out_format=out_format)
else:
self.export(api, task_id=task_id,
output_type=out_format,
output_dest=out,
open_it=open_it)
def _old_report(self, api, tasks=None, out=None, open_it=False,
out_format="html"):
"""Generate report file for specified task.
:param tasks: list, UUIDs of tasks or pathes files with tasks results
:param out: str, output file name
:param open_it: bool, whether to open output file in web browser
:param out_format: output format (junit, html or html_static)
"""
tasks = isinstance(tasks, list) and tasks or [tasks]
results = []
message = []
processed_names = {}
for task_file_or_uuid in tasks:
if os.path.exists(os.path.expanduser(task_file_or_uuid)):
results.extend(
self._load_task_results_file(api, task_file_or_uuid)
)
elif uuidutils.is_uuid_like(task_file_or_uuid):
results.append(api.task.get(task_id=task_file_or_uuid,
detailed=True))
else:
print("ERROR: Invalid UUID or file name passed: %s"
% task_file_or_uuid,
file=sys.stderr)
return 1
for task in results:
for workload in itertools.chain(
*[s["workloads"] for s in task["subtasks"]]):
if workload["name"] in processed_names:
processed_names[workload["name"]] += 1
workload["position"] = processed_names[workload["name"]]
else:
processed_names[workload["name"]] = 0
if out_format.startswith("html"):
result = plot.plot(results,
include_libs=(out_format == "html-static"))
elif out_format == "junit-xml":
test_suite = junit.JUnit("Rally test suite")
for task in results:
for workload in itertools.chain(
*[s["workloads"] for s in task["subtasks"]]):
w_sla = workload["sla_results"].get("sla", [])
if w_sla:
message = ",".join([sla["detail"] for sla in w_sla
if not sla["success"]])
if message:
outcome = junit.JUnit.FAILURE
else:
outcome = junit.JUnit.SUCCESS
test_suite.add_test(workload["name"],
workload["full_duration"], outcome,
message)
result = test_suite.to_xml()
else:
print("Invalid output format: %s" % out_format, file=sys.stderr)
return 1
if out:
output_file = os.path.expanduser(out)
with open(output_file, "w+") as f:
f.write(result)
if open_it:
webbrowser.open_new_tab("file://" + os.path.realpath(out))
else:
print(result)
self.export(api, tasks=tasks,
output_type=out_format,
output_dest=out,
open_it=open_it)
@cliutils.args("--force", action="store_true", help="force delete")
@cliutils.args("--uuid", type=str, dest="task_id", nargs="*",
@ -1016,8 +937,8 @@ class TaskCommands(object):
api.task.get(task_id=task_id)
fileutils.update_globals_file("RALLY_TASK", task_id)
@cliutils.args("--uuid", dest="task_id", nargs="+", type=str,
help="UUIDs of tasks")
@cliutils.args("--uuid", dest="tasks", nargs="+", type=str,
help="UUIDs of tasks or json reports of tasks")
@cliutils.args("--type", dest="output_type", type=str,
required=True,
help="Report type. Out-of-the-box "
@ -1032,14 +953,23 @@ class TaskCommands(object):
"types) to save the report to or a connection string."
" It depends on the report type."
)
@envutils.with_default_task_id
@envutils.default_from_global("tasks", envutils.ENV_TASK, "uuid")
@plugins.ensure_plugins_are_loaded
def export(self, api, task_id=None, output_type=None, output_dest=None,
def export(self, api, tasks=None, output_type=None, output_dest=None,
open_it=False):
"""Export task results to the custom task's exporting system."""
tasks = isinstance(tasks, list) and tasks or [tasks]
task_id = isinstance(task_id, list) and task_id or [task_id]
report = api.task.export(tasks_uuids=task_id,
exported_tasks = []
for task_file_or_uuid in tasks:
if os.path.exists(os.path.expanduser(task_file_or_uuid)):
exported_tasks.extend(
self._load_task_results_file(api, task_file_or_uuid)
)
else:
exported_tasks.append(task_file_or_uuid)
report = api.task.export(tasks=exported_tasks,
output_type=output_type,
output_dest=output_dest)
if "files" in report:

View File

@ -246,18 +246,6 @@ class TaskTestCase(unittest.TestCase):
self.assertEqual(expected, result_embedded)
self.assertEqual(not expected, result_external)
def test_report_one_uuid(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
rally("task start --task %s" % config.filename)
html_report = rally.gen_report_path(extension="html")
rally("task report --out %s" % html_report)
self.assertTrue(os.path.exists(html_report))
self._assert_html_report_libs_are_embedded(html_report, False)
self.assertRaises(utils.RallyCliError,
rally, "task report --report %s" % FAKE_TASK_UUID)
def test_new_report_one_uuid(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
@ -270,20 +258,6 @@ class TaskTestCase(unittest.TestCase):
self.assertRaises(utils.RallyCliError,
rally, "task report --report %s" % FAKE_TASK_UUID)
def test_report_bunch_uuids(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
task_uuids = []
for i in range(3):
res = rally("task start --task %s" % config.filename)
task_uuids.append(self._get_task_uuid(res))
html_report = rally.gen_report_path(extension="html")
rally("task report --tasks %s --out %s" % (" ".join(task_uuids),
html_report))
self.assertTrue(os.path.exists(html_report))
self._assert_html_report_libs_are_embedded(html_report, False)
def test_new_report_bunch_uuids(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
@ -297,7 +271,7 @@ class TaskTestCase(unittest.TestCase):
html_report))
self.assertTrue(os.path.exists(html_report))
def test_report_bunch_files(self):
def test_new_report_bunch_files(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
@ -311,12 +285,12 @@ class TaskTestCase(unittest.TestCase):
rally("task results", report_path=path, raw=True)
html_report = rally.gen_report_path(extension="html")
rally("task report --tasks %s --out %s" % (
rally("task report --uuid %s --out %s" % (
" ".join(files), html_report))
self.assertTrue(os.path.exists(html_report))
self._assert_html_report_libs_are_embedded(html_report, False)
def test_report_one_uuid_one_file(self):
def test_new_report_one_uuid_one_file(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
@ -330,10 +304,9 @@ class TaskTestCase(unittest.TestCase):
task_run_output = rally(
"task start --task %s" % config.filename)
task_uuid = self._get_task_uuid(task_run_output)
task_run_output = task_run_output.splitlines()
html_report = rally.gen_report_path(extension="html")
rally("task report --tasks"
rally("task report --uuid"
" %s %s --out %s" % (task_result_file, task_uuid,
html_report))
self.assertTrue(os.path.exists(html_report))
@ -341,7 +314,7 @@ class TaskTestCase(unittest.TestCase):
rally, "task report --report %s" % FAKE_TASK_UUID)
self._assert_html_report_libs_are_embedded(html_report, False)
def test_report_one_file_with_static_libs(self):
def test_new_report_one_file_with_static_libs(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
@ -358,16 +331,6 @@ class TaskTestCase(unittest.TestCase):
self.assertTrue(os.path.exists(html_report))
self._assert_html_report_libs_are_embedded(html_report)
def test_report_one_uuid_with_static_libs(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()
config = utils.TaskConfig(cfg)
rally("task start --task %s" % config.filename)
html_report = rally.gen_report_path(extension="html")
rally("task report --out %s --html-static" % html_report)
self.assertTrue(os.path.exists(html_report))
self._assert_html_report_libs_are_embedded(html_report)
def test_new_report_one_uuid_with_static_libs(self):
rally = utils.Rally()
cfg = self._get_sample_task_config()

View File

@ -723,142 +723,12 @@ class TaskCommandsTestCase(test.TestCase):
out="output.html", out_format="html")
self.assertEqual(1, ret)
@mock.patch("rally.cli.commands.task.os.path.realpath",
side_effect=lambda p: "realpath_%s" % p)
@mock.patch("rally.cli.commands.task.open",
side_effect=mock.mock_open(), create=True)
@mock.patch("rally.cli.commands.task.plot")
@mock.patch("rally.cli.commands.task.webbrowser")
def test_old_report_one_uuid(self, mock_webbrowser,
mock_plot, mock_open, mock_realpath):
task_id = "eb290c30-38d8-4c8f-bbcc-fc8f74b004ae"
task_obj = self._make_task()
self.fake_api.task.get.return_value = task_obj
mock_plot.plot.return_value = "html_report"
def reset_mocks():
for m in (self.fake_api.task.get, mock_webbrowser,
mock_plot, mock_open):
m.reset_mock()
self.task._old_report(self.fake_api, tasks=task_id,
out="/tmp/%s.html" % task_id)
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
mock_plot.plot.assert_called_once_with([task_obj], include_libs=False)
mock_open.side_effect().write.assert_called_once_with("html_report")
self.fake_api.task.get.assert_called_once_with(
task_id=task_id, detailed=True)
# JUnit
reset_mocks()
self.task._old_report(self.fake_api, tasks=task_id,
out="/tmp/%s.html" % task_id,
out_format="junit-xml")
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
self.assertFalse(mock_plot.plot.called)
# HTML
reset_mocks()
self.task._old_report(self.fake_api, task_id, out="output.html",
open_it=True, out_format="html")
mock_webbrowser.open_new_tab.assert_called_once_with(
"file://realpath_output.html")
mock_plot.plot.assert_called_once_with([task_obj], include_libs=False)
# HTML with embedded JS/CSS
reset_mocks()
self.task._old_report(self.fake_api, task_id, open_it=False,
out="output.html", out_format="html-static")
self.assertFalse(mock_webbrowser.open_new_tab.called)
mock_plot.plot.assert_called_once_with([task_obj], include_libs=True)
@mock.patch("rally.cli.commands.task.os.path.realpath",
side_effect=lambda p: "realpath_%s" % p)
@mock.patch("rally.cli.commands.task.open",
side_effect=mock.mock_open(), create=True)
@mock.patch("rally.cli.commands.task.plot")
@mock.patch("rally.cli.commands.task.webbrowser")
def test_old_report_bunch_uuids(self, mock_webbrowser,
mock_plot, mock_open, mock_realpath):
tasks = ["eb290c30-38d8-4c8f-bbcc-fc8f74b004ae",
"eb290c30-38d8-4c8f-bbcc-fc8f74b004af"]
task_obj = self._make_task()
self.fake_api.task.get.return_value = task_obj
mock_plot.plot.return_value = "html_report"
def reset_mocks():
for m in (self.fake_api.task.get, mock_webbrowser,
mock_plot, mock_open):
m.reset_mock()
self.task._old_report(self.fake_api, tasks=tasks,
out="/tmp/1_test.html")
mock_open.assert_called_once_with("/tmp/1_test.html", "w+")
mock_plot.plot.assert_called_once_with([task_obj, task_obj],
include_libs=False)
mock_open.side_effect().write.assert_called_once_with("html_report")
expected_get_calls = [mock.call(task_id=task, detailed=True)
for task in tasks]
self.fake_api.task.get.assert_has_calls(
expected_get_calls, any_order=True)
@mock.patch("rally.cli.commands.task.os.path.exists", return_value=True)
@mock.patch("rally.cli.commands.task.os.path.realpath",
side_effect=lambda p: "realpath_%s" % p)
@mock.patch("rally.cli.commands.task.open", create=True)
@mock.patch("rally.cli.commands.task.plot")
def test_old_report_one_file(self, mock_plot, mock_open, mock_realpath,
mock_path_exists):
task_file = "/tmp/some_file.json"
task_obj = self._make_task()
mock_plot.plot.return_value = "html_report"
mock_open.side_effect = mock.mock_open()
self.task._load_task_results_file = mock.MagicMock(
return_value=[task_obj]
)
self.task._old_report(self.real_api, tasks=task_file,
out="/tmp/1_test.html")
self.task._load_task_results_file.assert_called_once_with(
self.real_api, task_file)
expected_open_calls = [mock.call("/tmp/1_test.html", "w+")]
mock_open.assert_has_calls(expected_open_calls, any_order=True)
mock_plot.plot.assert_called_once_with([task_obj], include_libs=False)
mock_open.side_effect().write.assert_called_once_with("html_report")
@mock.patch("rally.cli.commands.task.os.path.exists", return_value=False)
@mock.patch("rally.cli.commands.task.tutils.open", create=True)
def test_old_report_exceptions(self, mock_open, mock_path_exists):
ret = self.task._old_report(self.real_api, tasks="/tmp/task.json",
out="/tmp/tmp.hsml")
self.assertEqual(1, ret)
@mock.patch("rally.cli.commands.task.os.path.exists", return_value=True)
def test_report(self, mock_path_exists):
self.task._old_report = mock.MagicMock()
def test_report(self):
self.task.export = mock.MagicMock()
self.task.report(self.fake_api, task_id="file",
out="out", open_it=False, out_format="html")
self.task._old_report.assert_called_once_with(
self.fake_api, tasks="file", out="out", open_it=False,
out_format="html"
)
self.task._old_report.reset_mock()
self.task.export.reset_mock()
mock_path_exists.return_value = False
self.task.report(self.fake_api, task_id="uuid",
self.task.report(self.fake_api, tasks="uuid",
out="out", open_it=False, out_format="junit-xml")
self.task.export.assert_called_once_with(
self.fake_api, task_id="uuid", output_type="junit-xml",
self.fake_api, tasks="uuid", output_type="junit-xml",
output_dest="out", open_it=False
)
@ -1074,17 +944,21 @@ class TaskCommandsTestCase(test.TestCase):
# file
self.fake_api.task.export.return_value = {
"files": {"output_dest": "content"}, "open": "output_dest"}
mock_path.exists.side_effect = [False, True, False]
mock_path.expanduser.return_value = "output_file"
mock_path.realpath.return_value = "real_path"
mock_fd = mock.mock_open()
mock_open.side_effect = mock_fd
self.task._load_task_results_file = mock.MagicMock(
return_value=[{"task": "task_1"}, {"task": "task2"}])
self.task.export(self.fake_api, task_id="uuid",
self.task.export(self.fake_api, tasks=["uuid", "file"],
output_type="json", output_dest="output_dest",
open_it=True)
self.fake_api.task.export.assert_called_once_with(
tasks_uuids=["uuid"], output_type="json",
tasks=["uuid"] + self.task._load_task_results_file.return_value,
output_type="json",
output_dest="output_dest"
)
mock_open.assert_called_once_with("output_file", "w+")
@ -1093,9 +967,10 @@ class TaskCommandsTestCase(test.TestCase):
# print
self.fake_api.task.export.reset_mock()
self.fake_api.task.export.return_value = {"print": "content"}
self.task.export(self.fake_api, task_id="uuid", output_type="json")
self.task.export(self.fake_api, tasks="uuid", output_type="json")
self.fake_api.task.export.assert_called_once_with(
tasks_uuids=["uuid"], output_type="json", output_dest=None
tasks=["uuid"],
output_type="json", output_dest=None
)
mock_print.assert_called_once_with("content")

View File

@ -456,8 +456,10 @@ class TaskAPITestCase(test.TestCase):
@mock.patch("rally.api.texporter.TaskExporter")
@mock.patch("rally.api.objects.Task.get")
def test_export(self, mock_task_get, mock_task_exporter):
task_id = ["uuid-1", "uuid-2"]
tasks_id = ["uuid-1", "uuid-2"]
tasks = [mock.Mock(), mock.Mock()]
tasks[0].to_dict.return_value = {"uuid": "uuid-1"}
tasks[1].to_dict.return_value = {"uuid": "uuid-2"}
mock_task_get.side_effect = tasks
output_type = mock.Mock()
output_dest = mock.Mock()
@ -467,7 +469,7 @@ class TaskAPITestCase(test.TestCase):
self.assertEqual(mock_task_exporter.make.return_value,
self.task_inst.export(
tasks_uuids=task_id,
tasks=tasks_id + [{"uuid": "uuid-3"}],
output_type=output_type,
output_dest=output_dest))
mock_task_exporter.get.assert_called_once_with(output_type)
@ -478,9 +480,10 @@ class TaskAPITestCase(test.TestCase):
vtype="syntax")
mock_task_exporter.make.assert_called_once_with(
reporter, [t.to_dict.return_value for t in tasks],
reporter,
[t.to_dict.return_value for t in tasks] + [{"uuid": "uuid-3"}],
output_dest, api=self.task_inst.api)
self.assertEqual([mock.call(u, detailed=True) for u in task_id],
self.assertEqual([mock.call(u, detailed=True) for u in tasks_id],
mock_task_get.call_args_list)
@mock.patch("rally.api.objects.Task")