Refactor the related command of task report and export
1.Use report plugins instead of separated reports, however, we still save old report command. 2.New report command rally task report --html --uuid <uuid> --out <dest> we can use --html and --html-static, and deprecate --junit, we have move junit to `rally task export` command. Example: rally task report --html --uuid xxxxxx --out /home/report.html 3.Change `rally task export` format, and deprecate old Exporter plugin. rally task export --uuid <uuid> --type <type> --to <dest> Example: rally task export --uuid xxxx --type junit-xml --to xxxxx 4.Remove FileExporter plugin. Change-Id: I44cafccb8d6c6c3cc704fb6e3ff2f49a756209ef
This commit is contained in:
parent
801e624c9c
commit
9d26483c12
@ -31,10 +31,10 @@ _rally()
|
|||||||
OPTS["task_abort"]="--uuid --soft"
|
OPTS["task_abort"]="--uuid --soft"
|
||||||
OPTS["task_delete"]="--force --uuid"
|
OPTS["task_delete"]="--force --uuid"
|
||||||
OPTS["task_detailed"]="--uuid --iterations-data"
|
OPTS["task_detailed"]="--uuid --iterations-data"
|
||||||
OPTS["task_export"]="--uuid --connection"
|
OPTS["task_export"]="--uuid --type --to"
|
||||||
OPTS["task_import"]="--file --deployment --tag"
|
OPTS["task_import"]="--file --deployment --tag"
|
||||||
OPTS["task_list"]="--deployment --all-deployments --status --uuids-only"
|
OPTS["task_list"]="--deployment --all-deployments --status --uuids-only"
|
||||||
OPTS["task_report"]="--tasks --out --open --html --html-static --junit"
|
OPTS["task_report"]="--out --open --html --html-static --uuid"
|
||||||
OPTS["task_results"]="--uuid"
|
OPTS["task_results"]="--uuid"
|
||||||
OPTS["task_sla-check"]="--uuid --json"
|
OPTS["task_sla-check"]="--uuid --json"
|
||||||
OPTS["task_sla_check"]="--uuid --json"
|
OPTS["task_sla_check"]="--uuid --json"
|
||||||
|
28
rally/api.py
28
rally/api.py
@ -38,6 +38,7 @@ from rally import consts
|
|||||||
from rally.deployment import engine as deploy_engine
|
from rally.deployment import engine as deploy_engine
|
||||||
from rally import exceptions
|
from rally import exceptions
|
||||||
from rally.task import engine
|
from rally.task import engine
|
||||||
|
from rally.task import exporter as texporter
|
||||||
from rally.verification import context as vcontext
|
from rally.verification import context as vcontext
|
||||||
from rally.verification import manager as vmanager
|
from rally.verification import manager as vmanager
|
||||||
from rally.verification import reporter as vreporter
|
from rally.verification import reporter as vreporter
|
||||||
@ -580,6 +581,33 @@ class _Task(APIGroup):
|
|||||||
|
|
||||||
return task_inst.to_dict()
|
return task_inst.to_dict()
|
||||||
|
|
||||||
|
@api_wrapper(path=API_REQUEST_PREFIX + "/task/export",
|
||||||
|
method="POST")
|
||||||
|
def export(self, tasks_uuids, output_type, output_dest=None):
|
||||||
|
"""Generate a report for a task or a few tasks.
|
||||||
|
|
||||||
|
:param tasks_uuids: List of tasks UUIDs
|
||||||
|
:param output_type: Plugin name of task reporter
|
||||||
|
:param output_dest: Destination for task report
|
||||||
|
"""
|
||||||
|
|
||||||
|
tasks_results = []
|
||||||
|
for task_uuid in tasks_uuids:
|
||||||
|
tasks_results.extend(self.get_detailed(
|
||||||
|
task_id=task_uuid)["results"])
|
||||||
|
|
||||||
|
reporter_cls = texporter.TaskExporter.get(output_type)
|
||||||
|
reporter_cls.validate(output_dest)
|
||||||
|
|
||||||
|
LOG.info("Building '%s' report for the following task(s): "
|
||||||
|
"'%s'.", output_type, "', '".join(tasks_uuids))
|
||||||
|
result = texporter.TaskExporter.make(reporter_cls,
|
||||||
|
tasks_results,
|
||||||
|
output_dest,
|
||||||
|
api=self.api)
|
||||||
|
LOG.info("The report has been successfully built.")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class _Verifier(APIGroup):
|
class _Verifier(APIGroup):
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import webbrowser
|
|||||||
import jsonschema
|
import jsonschema
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import six
|
import six
|
||||||
from six.moves.urllib import parse as urlparse
|
|
||||||
|
|
||||||
from rally.cli import cliutils
|
from rally.cli import cliutils
|
||||||
from rally.cli import envutils
|
from rally.cli import envutils
|
||||||
@ -39,7 +38,6 @@ from rally.common import yamlutils as yaml
|
|||||||
from rally import consts
|
from rally import consts
|
||||||
from rally import exceptions
|
from rally import exceptions
|
||||||
from rally import plugins
|
from rally import plugins
|
||||||
from rally.task import exporter
|
|
||||||
from rally.task.processing import plot
|
from rally.task.processing import plot
|
||||||
from rally.task.processing import utils as putils
|
from rally.task.processing import utils as putils
|
||||||
from rally.task import utils as tutils
|
from rally.task import utils as tutils
|
||||||
@ -450,7 +448,7 @@ class TaskCommands(object):
|
|||||||
print(_("* To plot HTML graphics with this data, run:"))
|
print(_("* To plot HTML graphics with this data, run:"))
|
||||||
print("\trally task report %s --out output.html\n" % task["uuid"])
|
print("\trally task report %s --out output.html\n" % task["uuid"])
|
||||||
print(_("* To generate a JUnit report, run:"))
|
print(_("* To generate a JUnit report, run:"))
|
||||||
print("\trally task report %s --junit --out output.xml\n" %
|
print("\trally task export %s --type junit --to output.xml\n" %
|
||||||
task["uuid"])
|
task["uuid"])
|
||||||
print(_("* To get raw JSON output of task results, run:"))
|
print(_("* To get raw JSON output of task results, run:"))
|
||||||
print("\trally task results %s\n" % task["uuid"])
|
print("\trally task results %s\n" % task["uuid"])
|
||||||
@ -624,31 +622,47 @@ class TaskCommands(object):
|
|||||||
else:
|
else:
|
||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
@cliutils.args("--tasks", dest="tasks", nargs="+",
|
@cliutils.deprecated_args("--tasks", dest="task_id", nargs="+",
|
||||||
help="UUIDs of tasks, or JSON files with task results")
|
release="0.10.0", alternative="--uuid")
|
||||||
@cliutils.args("--out", metavar="<path>",
|
@cliutils.args("--out", metavar="<path>",
|
||||||
type=str, dest="out", required=False,
|
type=str, dest="out", required=False,
|
||||||
help="Path to output file.")
|
help="Report destination. Can be a path to a file (in case"
|
||||||
|
" of HTML, HTML-STATIC, etc. types) to save the"
|
||||||
|
" report to or a connection string.")
|
||||||
@cliutils.args("--open", dest="open_it", action="store_true",
|
@cliutils.args("--open", dest="open_it", action="store_true",
|
||||||
help="Open the output in a browser.")
|
help="Open the output in a browser.")
|
||||||
@cliutils.args("--html", dest="out_format",
|
@cliutils.args("--html", dest="out_format",
|
||||||
action="store_const", const="html",
|
action="store_const", const="html")
|
||||||
help="Generate the report in HTML.")
|
|
||||||
@cliutils.args("--html-static", dest="out_format",
|
@cliutils.args("--html-static", dest="out_format",
|
||||||
action="store_const", const="html_static",
|
action="store_const", const="html-static")
|
||||||
help=("Generate the report in HTML with embedded "
|
@cliutils.deprecated_args("--junit", dest="out_format",
|
||||||
"JS and CSS, so it will not depend on "
|
action="store_const", const="junit-xml",
|
||||||
"Internet availability."))
|
release="0.10.0",
|
||||||
@cliutils.args("--junit", dest="out_format",
|
alternative=("rally task export "
|
||||||
action="store_const", const="junit",
|
"--type junit-xml"))
|
||||||
help="Generate the report in the JUnit format.")
|
@cliutils.args("--uuid", dest="task_id", nargs="+", type=str,
|
||||||
@envutils.default_from_global("tasks", envutils.ENV_TASK, "tasks")
|
help="UUIDs of tasks")
|
||||||
|
@envutils.with_default_task_id
|
||||||
@cliutils.suppress_warnings
|
@cliutils.suppress_warnings
|
||||||
def report(self, api, tasks=None, out=None, open_it=False,
|
def report(self, api, task_id=None, out=None,
|
||||||
|
open_it=False, out_format="html"):
|
||||||
|
"""generate report file or string for specified task."""
|
||||||
|
|
||||||
|
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"):
|
out_format="html"):
|
||||||
"""Generate report file for specified task.
|
"""Generate report file for specified task.
|
||||||
|
|
||||||
:param tasks: list, UUIDs od tasks or pathes files with tasks results
|
:param tasks: list, UUIDs of tasks or pathes files with tasks results
|
||||||
:param out: str, output file name
|
:param out: str, output file name
|
||||||
:param open_it: bool, whether to open output file in web browser
|
:param open_it: bool, whether to open output file in web browser
|
||||||
:param out_format: output format (junit, html or html_static)
|
:param out_format: output format (junit, html or html_static)
|
||||||
@ -692,7 +706,7 @@ class TaskCommands(object):
|
|||||||
if out_format.startswith("html"):
|
if out_format.startswith("html"):
|
||||||
result = plot.plot(results,
|
result = plot.plot(results,
|
||||||
include_libs=(out_format == "html_static"))
|
include_libs=(out_format == "html_static"))
|
||||||
elif out_format == "junit":
|
elif out_format == "junit-xml":
|
||||||
test_suite = junit.JUnit("Rally test suite")
|
test_suite = junit.JUnit("Rally test suite")
|
||||||
for result in results:
|
for result in results:
|
||||||
if isinstance(result["sla"], list):
|
if isinstance(result["sla"], list):
|
||||||
@ -801,51 +815,47 @@ class TaskCommands(object):
|
|||||||
api.task.get(task_id=task_id)
|
api.task.get(task_id=task_id)
|
||||||
fileutils.update_globals_file("RALLY_TASK", task_id)
|
fileutils.update_globals_file("RALLY_TASK", task_id)
|
||||||
|
|
||||||
@cliutils.args("--uuid", dest="uuid", type=str,
|
@cliutils.args("--uuid", dest="task_id", nargs="+", type=str,
|
||||||
|
help="UUIDs of tasks")
|
||||||
|
@cliutils.args("--type", dest="output_type", type=str,
|
||||||
required=True,
|
required=True,
|
||||||
help="UUID of a the task.")
|
help="Report type (Defaults to HTML). Out-of-the-box "
|
||||||
@cliutils.args("--connection", dest="connection_string", type=str,
|
"types: HTML, HTML-Static, JUnit-XML. "
|
||||||
required=True,
|
"HINT: You can list all types, executing `rally "
|
||||||
help="Connection url to the task export system.")
|
"plugin list --plugin-base TaskExporter` "
|
||||||
|
"command.")
|
||||||
|
@cliutils.args("--to", dest="output_dest", type=str,
|
||||||
|
metavar="<dest>", required=False,
|
||||||
|
help="Report destination. Can be a path to a file (in case"
|
||||||
|
" of HTML, HTML-Static, JUnit-XML, etc. types) to"
|
||||||
|
" save the report to or a connection string."
|
||||||
|
" It depends on the report type."
|
||||||
|
)
|
||||||
|
@envutils.with_default_task_id
|
||||||
@plugins.ensure_plugins_are_loaded
|
@plugins.ensure_plugins_are_loaded
|
||||||
def export(self, api, uuid, connection_string):
|
def export(self, api, task_id=None, output_type=None, output_dest=None,
|
||||||
|
open_it=False):
|
||||||
"""Export task results to the custom task's exporting system.
|
"""Export task results to the custom task's exporting system.
|
||||||
|
|
||||||
:param uuid: UUID of the task
|
:param task_id: UUID of the task
|
||||||
:param connection_string: string used to connect to the system
|
:param output_type: str, output type
|
||||||
|
:param output_dest: output format (html, html-static, junit-xml,etc)
|
||||||
"""
|
"""
|
||||||
|
task_id = isinstance(task_id, list) and task_id or [task_id]
|
||||||
|
report = api.task.export(tasks_uuids=task_id,
|
||||||
|
output_type=output_type,
|
||||||
|
output_dest=output_dest)
|
||||||
|
if "files" in report:
|
||||||
|
for path in report["files"]:
|
||||||
|
output_file = os.path.expanduser(path)
|
||||||
|
with open(output_file, "w+") as f:
|
||||||
|
f.write(report["files"][path])
|
||||||
|
if open_it:
|
||||||
|
if "open" in report:
|
||||||
|
webbrowser.open_new_tab(report["open"])
|
||||||
|
|
||||||
parsed_obj = urlparse.urlparse(connection_string)
|
if "print" in report:
|
||||||
try:
|
print(report["print"])
|
||||||
client = exporter.Exporter.get(parsed_obj.scheme)(
|
|
||||||
connection_string)
|
|
||||||
except exceptions.InvalidConnectionString as e:
|
|
||||||
if logging.is_debug():
|
|
||||||
LOG.exception(e)
|
|
||||||
print(e)
|
|
||||||
return 1
|
|
||||||
except exceptions.PluginNotFound as e:
|
|
||||||
if logging.is_debug():
|
|
||||||
LOG.exception(e)
|
|
||||||
msg = ("\nPlease check your connection string. The format of "
|
|
||||||
"`connection` should be plugin-name://"
|
|
||||||
"<user>:<pwd>@<full_address>:<port>/<path>.<type>")
|
|
||||||
print(str(e) + msg)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.export(uuid)
|
|
||||||
except (IOError, exceptions.RallyException) as e:
|
|
||||||
if logging.is_debug():
|
|
||||||
LOG.exception(e)
|
|
||||||
print(e)
|
|
||||||
return 1
|
|
||||||
print(_("Task %(uuid)s results was successfully exported to %("
|
|
||||||
"connection)s using %(name)s plugin.") % {
|
|
||||||
"uuid": uuid,
|
|
||||||
"connection": connection_string,
|
|
||||||
"name": parsed_obj.scheme
|
|
||||||
})
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _print_task_errors(task_id, task_errors):
|
def _print_task_errors(task_id, task_errors):
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
# Copyright 2016: Mirantis Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from six.moves.urllib import parse as urlparse
|
|
||||||
|
|
||||||
from rally import api
|
|
||||||
from rally.common import logging
|
|
||||||
from rally import exceptions
|
|
||||||
from rally.task import exporter
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@exporter.configure(name="file")
|
|
||||||
class FileExporter(exporter.Exporter):
|
|
||||||
"""Export task results in the file."""
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
"""Validate connection string.
|
|
||||||
|
|
||||||
The format of connection string in file plugin is
|
|
||||||
file:///<path>.<type-of-output>
|
|
||||||
"""
|
|
||||||
|
|
||||||
parse_obj = urlparse.urlparse(self.connection_string)
|
|
||||||
|
|
||||||
available_formats = ("json",)
|
|
||||||
available_formats_str = ", ".join(available_formats)
|
|
||||||
if self.connection_string is None or parse_obj.path == "":
|
|
||||||
raise exceptions.InvalidConnectionString(
|
|
||||||
"It should be `file:///<path>.<type-of-output>`.")
|
|
||||||
if self.type not in available_formats:
|
|
||||||
raise exceptions.InvalidConnectionString(
|
|
||||||
"Type of the exported task is not available. The available "
|
|
||||||
"formats are %s." %
|
|
||||||
available_formats_str)
|
|
||||||
|
|
||||||
def __init__(self, connection_string):
|
|
||||||
super(FileExporter, self).__init__(connection_string)
|
|
||||||
self.path = os.path.expanduser(urlparse.urlparse(
|
|
||||||
connection_string).path[1:])
|
|
||||||
self.type = connection_string.split(".")[-1]
|
|
||||||
self.validate()
|
|
||||||
|
|
||||||
def export(self, uuid):
|
|
||||||
"""Export results of the task to the file.
|
|
||||||
|
|
||||||
:param uuid: uuid of the task object
|
|
||||||
"""
|
|
||||||
rapi = api.API(config_args=sys.argv[1:], skip_db_check=True)
|
|
||||||
task = rapi.task.get_detailed(task_id=uuid)
|
|
||||||
|
|
||||||
LOG.debug("Got the task object by it's uuid %s. " % uuid)
|
|
||||||
|
|
||||||
task_results = [{"key": x["key"], "result": x["data"]["raw"],
|
|
||||||
"sla": x["data"]["sla"],
|
|
||||||
"hooks": x["data"].get("hooks"),
|
|
||||||
"load_duration": x["data"]["load_duration"],
|
|
||||||
"full_duration": x["data"]["full_duration"]}
|
|
||||||
for x in task["results"]]
|
|
||||||
|
|
||||||
if self.type == "json":
|
|
||||||
if task_results:
|
|
||||||
res = json.dumps(task_results, sort_keys=False, indent=4,
|
|
||||||
separators=(",", ": "))
|
|
||||||
LOG.debug("Got the task %s results." % uuid)
|
|
||||||
else:
|
|
||||||
msg = ("Task %s results would be available when it will "
|
|
||||||
"finish." % uuid)
|
|
||||||
raise exceptions.RallyException(msg)
|
|
||||||
|
|
||||||
if os.path.dirname(self.path) and (not os.path.exists(os.path.dirname(
|
|
||||||
self.path))):
|
|
||||||
raise IOError("There is no such directory: %s" %
|
|
||||||
os.path.dirname(self.path))
|
|
||||||
with open(self.path, "w") as f:
|
|
||||||
LOG.debug("Writing task %s results to the %s." % (
|
|
||||||
uuid, self.connection_string))
|
|
||||||
f.write(res)
|
|
||||||
LOG.debug("Task %s results was written to the %s." % (
|
|
||||||
uuid, self.connection_string))
|
|
||||||
|
|
||||||
|
|
||||||
@exporter.configure(name="file-exporter")
|
|
||||||
class DeprecatedFileExporter(FileExporter):
|
|
||||||
"""DEPRECATED."""
|
|
||||||
def __init__(self, connection_string):
|
|
||||||
super(DeprecatedFileExporter, self).__init__(connection_string)
|
|
||||||
LOG.warning("'file-exporter' plugin is deprecated. Use 'file' "
|
|
||||||
"instead.")
|
|
202
rally/plugins/common/exporter/reporters.py
Normal file
202
rally/plugins/common/exporter/reporters.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import os
|
||||||
|
|
||||||
|
from rally.common.io import junit
|
||||||
|
from rally.task import exporter
|
||||||
|
from rally.task.processing import plot
|
||||||
|
|
||||||
|
|
||||||
|
class OldJSONResultsMixin(object):
|
||||||
|
"""Generates task report in old JSON format.
|
||||||
|
|
||||||
|
An example of the report (All dates, numbers, names appearing in this
|
||||||
|
example are fictitious. Any resemblance to real things is purely
|
||||||
|
coincidental):
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"hooks": [],
|
||||||
|
"created_at": "2017-06-04T05:14:44",
|
||||||
|
"load_duration": 2.03029203414917,
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"timestamp": 1496553301.578394,
|
||||||
|
"error": [],
|
||||||
|
"duration": 1.0232760906219482,
|
||||||
|
"output": {
|
||||||
|
"additive": [],
|
||||||
|
"complete": []
|
||||||
|
},
|
||||||
|
"idle_duration": 0.0,
|
||||||
|
"atomic_actions": [
|
||||||
|
{
|
||||||
|
"finished_at": 1496553302.601537,
|
||||||
|
"started_at": 1496553301.57868,
|
||||||
|
"name": "cinder_v2.list_volumes",
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": 1496553302.608502,
|
||||||
|
"error": [],
|
||||||
|
"duration": 1.0001840591430664,
|
||||||
|
"output": {
|
||||||
|
"additive": [],
|
||||||
|
"complete": []
|
||||||
|
},
|
||||||
|
"idle_duration": 0.0,
|
||||||
|
"atomic_actions": [
|
||||||
|
{
|
||||||
|
"finished_at": 1496553303.608628,
|
||||||
|
"started_at": 1496553302.608545,
|
||||||
|
"name": "cinder_v2.list_volumes",
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"kw": {
|
||||||
|
"runner": {
|
||||||
|
"type": "constant",
|
||||||
|
"times": 2,
|
||||||
|
"concurrency": 1
|
||||||
|
},
|
||||||
|
"hooks": [],
|
||||||
|
"args": {
|
||||||
|
"detailed": true
|
||||||
|
},
|
||||||
|
"sla": {},
|
||||||
|
"context": {
|
||||||
|
"volumes": {
|
||||||
|
"size": 1,
|
||||||
|
"volumes_per_tenant": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pos": 0,
|
||||||
|
"name": "CinderVolumes.list_volumes",
|
||||||
|
"description": "List all volumes."
|
||||||
|
},
|
||||||
|
"full_duration": 29.969523191452026,
|
||||||
|
"sla": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _generate_tasks_results(self):
|
||||||
|
"""Prepare raw report."""
|
||||||
|
results = [{"key": x["key"], "result": x["data"]["raw"],
|
||||||
|
"sla": x["data"]["sla"],
|
||||||
|
"hooks": x["data"].get("hooks", []),
|
||||||
|
"load_duration": x["data"]["load_duration"],
|
||||||
|
"full_duration": x["data"]["full_duration"],
|
||||||
|
"created_at": x["created_at"]}
|
||||||
|
for x in self.tasks_results]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@exporter.configure("html")
|
||||||
|
class HTMLExporter(exporter.TaskExporter, OldJSONResultsMixin):
|
||||||
|
"""Generates task report in HTML format."""
|
||||||
|
INCLUDE_LIBS = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, output_destination):
|
||||||
|
"""Validate destination of report.
|
||||||
|
|
||||||
|
:param output_destination: Destination of report
|
||||||
|
"""
|
||||||
|
# nothing to check :)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _generate(self):
|
||||||
|
results = []
|
||||||
|
processed_names = {}
|
||||||
|
tasks_results = self._generate_tasks_results()
|
||||||
|
for task_result in tasks_results:
|
||||||
|
if task_result["key"]["name"] in processed_names:
|
||||||
|
processed_names[task_result["key"]["name"]] += 1
|
||||||
|
task_result["key"]["pos"] = processed_names[
|
||||||
|
task_result["key"]["name"]]
|
||||||
|
else:
|
||||||
|
processed_names[task_result["key"]["name"]] = 0
|
||||||
|
results.append(task_result)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
results = self._generate()
|
||||||
|
report = plot.plot(results,
|
||||||
|
include_libs=self.INCLUDE_LIBS)
|
||||||
|
|
||||||
|
if self.output_destination:
|
||||||
|
return {"files": {self.output_destination: report},
|
||||||
|
"open": "file://" + os.path.abspath(
|
||||||
|
self.output_destination)}
|
||||||
|
else:
|
||||||
|
return {"print": report}
|
||||||
|
|
||||||
|
|
||||||
|
@exporter.configure("html-static")
|
||||||
|
class HTMLStaticExporter(HTMLExporter):
|
||||||
|
"""Generates task report in HTML format with embedded JS/CSS."""
|
||||||
|
INCLUDE_LIBS = True
|
||||||
|
|
||||||
|
|
||||||
|
@exporter.configure("junit-xml")
|
||||||
|
class JUnitXMLExporter(HTMLExporter):
|
||||||
|
"""Generates task report in JUnit-XML format.
|
||||||
|
|
||||||
|
An example of the report (All dates, numbers, names appearing in this
|
||||||
|
example are fictitious. Any resemblance to real things is purely
|
||||||
|
coincidental):
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<testsuite errors="0"
|
||||||
|
failures="0"
|
||||||
|
name="Rally test suite"
|
||||||
|
tests="1"
|
||||||
|
time="29.97">
|
||||||
|
<testcase classname="CinderVolumes"
|
||||||
|
name="list_volumes"
|
||||||
|
time="29.97" />
|
||||||
|
</testsuite>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
results = self._generate()
|
||||||
|
test_suite = junit.JUnit("Rally test suite")
|
||||||
|
for result in results:
|
||||||
|
if isinstance(result["sla"], list):
|
||||||
|
message = ",".join([sla["detail"] for sla in
|
||||||
|
result["sla"] if not sla["success"]])
|
||||||
|
if message:
|
||||||
|
outcome = junit.JUnit.FAILURE
|
||||||
|
else:
|
||||||
|
outcome = junit.JUnit.SUCCESS
|
||||||
|
test_suite.add_test(result["key"]["name"],
|
||||||
|
result["full_duration"], outcome, message)
|
||||||
|
result = test_suite.to_xml()
|
||||||
|
|
||||||
|
if self.output_destination:
|
||||||
|
return {"files": {self.output_destination: result},
|
||||||
|
"open": "file://" + os.path.abspath(
|
||||||
|
self.output_destination)}
|
||||||
|
else:
|
||||||
|
return {"print": result}
|
@ -21,19 +21,46 @@ system by connection string.
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from rally.common import logging
|
||||||
from rally.common.plugin import plugin
|
from rally.common.plugin import plugin
|
||||||
|
from rally import consts
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
configure = plugin.configure
|
configure = plugin.configure
|
||||||
|
|
||||||
|
REPORT_RESPONSE_SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"$schema": consts.JSON_SCHEMA,
|
||||||
|
"properties": {
|
||||||
|
"files": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
".{1,}": {"type": "string"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"open": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"print": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@plugin.base()
|
@plugin.base()
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Exporter(plugin.Plugin):
|
class Exporter(plugin.Plugin):
|
||||||
|
|
||||||
def __init__(self, connection_string):
|
def __init__(self, connection_string):
|
||||||
|
LOG.warning("Sorry, we have not support old Exporter plugin since"
|
||||||
|
"Rally 0.10.0, please use TaskExporter instead.")
|
||||||
self.connection_string = connection_string
|
self.connection_string = connection_string
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@ -47,4 +74,60 @@ class Exporter(plugin.Plugin):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
"""Used to validate connection string."""
|
"""Used to validate connection string."""
|
||||||
|
|
||||||
TaskExporter = Exporter
|
|
||||||
|
@plugin.base()
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class TaskExporter(plugin.Plugin):
|
||||||
|
"""Base class for all exporters for Tasks."""
|
||||||
|
|
||||||
|
def __init__(self, tasks_results, output_destination, api=None):
|
||||||
|
"""Init reporter
|
||||||
|
|
||||||
|
:param tasks_results: list of results to generate report for
|
||||||
|
:param output_destination: destination of export
|
||||||
|
:param api: an instance of rally.api.API object
|
||||||
|
"""
|
||||||
|
super(TaskExporter, self).__init__()
|
||||||
|
self.tasks_results = tasks_results
|
||||||
|
self.output_destination = output_destination
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@abc.abstractmethod
|
||||||
|
def validate(cls, output_destination):
|
||||||
|
"""Validate destination of report.
|
||||||
|
|
||||||
|
:param output_destination: Destination of report
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def generate(self):
|
||||||
|
"""Generate report
|
||||||
|
|
||||||
|
:returns: a dict with 3 optional elements:
|
||||||
|
|
||||||
|
- key "files" with a dictionary of files to save on disk.
|
||||||
|
keys are paths, values are contents;
|
||||||
|
- key "print" - data to print at CLI level
|
||||||
|
- key "open" - path to file which should be open in case of
|
||||||
|
--open flag
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make(exporter_cls, task_results, output_destination, api=None):
|
||||||
|
"""Initialize exporter, generate and validate result.
|
||||||
|
|
||||||
|
It is a base method which is called from API layer. It cannot be
|
||||||
|
overridden. Do not even try! :)
|
||||||
|
|
||||||
|
:param exporter_cls: class of TaskExporter to be used
|
||||||
|
:param task_results: list of results to generate report for
|
||||||
|
:param output_destination: destination of export
|
||||||
|
:param api: an instance of rally.api.API object
|
||||||
|
"""
|
||||||
|
report = exporter_cls(task_results, output_destination,
|
||||||
|
api).generate()
|
||||||
|
|
||||||
|
jsonschema.validate(report, REPORT_RESPONSE_SCHEMA)
|
||||||
|
|
||||||
|
return report
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
<li><a href="rally-plot/detailed_with_iterations.txt.gz">Text report detailed</a> <code>$ rally task detailed --iterations-data</code>
|
<li><a href="rally-plot/detailed_with_iterations.txt.gz">Text report detailed</a> <code>$ rally task detailed --iterations-data</code>
|
||||||
<li><a href="rally-plot/sla.txt">Success criteria (SLA)</a> <code>$ rally task sla_check</code>
|
<li><a href="rally-plot/sla.txt">Success criteria (SLA)</a> <code>$ rally task sla_check</code>
|
||||||
<li><a href="rally-plot/results.json.gz">Raw results (JSON)</a> <code>$ rally task results</code>
|
<li><a href="rally-plot/results.json.gz">Raw results (JSON)</a> <code>$ rally task results</code>
|
||||||
|
<li><a href="rally-plot/junit.xml.gz">JUNIT-XML report</a> <code>$ rally task export --type junit-xml</code>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>About Rally</h2>
|
<h2>About Rally</h2>
|
||||||
|
@ -135,8 +135,10 @@ function run () {
|
|||||||
gzip -9 rally-plot/detailed.txt
|
gzip -9 rally-plot/detailed.txt
|
||||||
rally task detailed --iterations-data > rally-plot/detailed_with_iterations.txt
|
rally task detailed --iterations-data > rally-plot/detailed_with_iterations.txt
|
||||||
gzip -9 rally-plot/detailed_with_iterations.txt
|
gzip -9 rally-plot/detailed_with_iterations.txt
|
||||||
rally task report --out rally-plot/results.html
|
rally task report --html --out rally-plot/results.html
|
||||||
gzip -9 rally-plot/results.html
|
gzip -9 rally-plot/results.html
|
||||||
|
rally task export --type junit-xml --to rally-plot/junit.xml
|
||||||
|
gzip -9 rally-plot/junit.xml
|
||||||
|
|
||||||
# NOTE(stpierre): if the sla check fails, we still want osresources.py
|
# NOTE(stpierre): if the sla check fails, we still want osresources.py
|
||||||
# to run, so we turn off -e and save the return value
|
# to run, so we turn off -e and save the return value
|
||||||
|
@ -204,6 +204,8 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
rally = utils.Rally()
|
rally = utils.Rally()
|
||||||
self.assertRaises(utils.RallyCliError,
|
self.assertRaises(utils.RallyCliError,
|
||||||
rally, "task report --tasks %s" % FAKE_TASK_UUID)
|
rally, "task report --tasks %s" % FAKE_TASK_UUID)
|
||||||
|
self.assertRaises(utils.RallyCliError,
|
||||||
|
rally, "task report --uuid %s" % FAKE_TASK_UUID)
|
||||||
|
|
||||||
def test_sla_check_with_wrong_task_id(self):
|
def test_sla_check_with_wrong_task_id(self):
|
||||||
rally = utils.Rally()
|
rally = utils.Rally()
|
||||||
@ -233,16 +235,22 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
cfg = self._get_sample_task_config()
|
cfg = self._get_sample_task_config()
|
||||||
config = utils.TaskConfig(cfg)
|
config = utils.TaskConfig(cfg)
|
||||||
rally("task start --task %s" % config.filename)
|
rally("task start --task %s" % config.filename)
|
||||||
rally("task report --out %s" % rally.gen_report_path(extension="html"))
|
|
||||||
html_report = rally.gen_report_path(extension="html")
|
html_report = rally.gen_report_path(extension="html")
|
||||||
|
rally("task report --out %s" % html_report)
|
||||||
self.assertTrue(os.path.exists(html_report))
|
self.assertTrue(os.path.exists(html_report))
|
||||||
self._assert_html_report_libs_are_embedded(html_report, False)
|
self._assert_html_report_libs_are_embedded(html_report, False)
|
||||||
self.assertRaises(utils.RallyCliError,
|
self.assertRaises(utils.RallyCliError,
|
||||||
rally, "task report --report %s" % FAKE_TASK_UUID)
|
rally, "task report --report %s" % FAKE_TASK_UUID)
|
||||||
rally("task report --junit --out %s" %
|
|
||||||
rally.gen_report_path(extension="junit"))
|
def test_new_report_one_uuid(self):
|
||||||
self.assertTrue(os.path.exists(
|
rally = utils.Rally()
|
||||||
rally.gen_report_path(extension="junit")))
|
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,
|
self.assertRaises(utils.RallyCliError,
|
||||||
rally, "task report --report %s" % FAKE_TASK_UUID)
|
rally, "task report --report %s" % FAKE_TASK_UUID)
|
||||||
|
|
||||||
@ -262,6 +270,21 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
self.assertTrue(os.path.exists(html_report))
|
self.assertTrue(os.path.exists(html_report))
|
||||||
self._assert_html_report_libs_are_embedded(html_report, False)
|
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()
|
||||||
|
config = utils.TaskConfig(cfg)
|
||||||
|
task_uuids = []
|
||||||
|
for i in range(3):
|
||||||
|
res = rally("task start --task %s" % config.filename)
|
||||||
|
for line in res.splitlines():
|
||||||
|
if "finished" in line:
|
||||||
|
task_uuids.append(line.split(" ")[1][:-1])
|
||||||
|
html_report = rally.gen_report_path(extension="html")
|
||||||
|
rally("task report --uuid %s --out %s" % (" ".join(task_uuids),
|
||||||
|
html_report))
|
||||||
|
self.assertTrue(os.path.exists(html_report))
|
||||||
|
|
||||||
def test_report_bunch_files(self):
|
def test_report_bunch_files(self):
|
||||||
rally = utils.Rally()
|
rally = utils.Rally()
|
||||||
cfg = self._get_sample_task_config()
|
cfg = self._get_sample_task_config()
|
||||||
@ -289,7 +312,8 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
task_result_file = "/tmp/report_42.json"
|
task_result_file = "/tmp/report_42.json"
|
||||||
if os.path.exists(task_result_file):
|
if os.path.exists(task_result_file):
|
||||||
os.remove(task_result_file)
|
os.remove(task_result_file)
|
||||||
rally("task results", report_path=task_result_file, raw=True)
|
rally("task results", report_path=task_result_file,
|
||||||
|
raw=True)
|
||||||
|
|
||||||
task_run_output = rally(
|
task_run_output = rally(
|
||||||
"task start --task %s" % config.filename).splitlines()
|
"task start --task %s" % config.filename).splitlines()
|
||||||
@ -319,6 +343,16 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
self.assertTrue(os.path.exists(html_report))
|
self.assertTrue(os.path.exists(html_report))
|
||||||
self._assert_html_report_libs_are_embedded(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()
|
||||||
|
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_trends(self):
|
def test_trends(self):
|
||||||
cfg1 = {
|
cfg1 = {
|
||||||
"Dummy.dummy": [
|
"Dummy.dummy": [
|
||||||
@ -865,58 +899,38 @@ class TaskTestCase(unittest.TestCase):
|
|||||||
r"(?P<task_id>[0-9a-f\-]{36}): started", output)
|
r"(?P<task_id>[0-9a-f\-]{36}): started", output)
|
||||||
self.assertIsNotNone(result)
|
self.assertIsNotNone(result)
|
||||||
|
|
||||||
def test_export(self):
|
def test_export_one_uuid(self):
|
||||||
rally = utils.Rally()
|
rally = utils.Rally()
|
||||||
cfg = {
|
cfg = self._get_sample_task_config()
|
||||||
"Dummy.dummy": [
|
|
||||||
{
|
|
||||||
"runner": {
|
|
||||||
"type": "constant",
|
|
||||||
"times": 100,
|
|
||||||
"concurrency": 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
config = utils.TaskConfig(cfg)
|
config = utils.TaskConfig(cfg)
|
||||||
output = rally("task start --task %s" % config.filename)
|
rally("task start --task %s" % config.filename)
|
||||||
uuid = re.search(
|
html_report = rally.gen_report_path(extension="html")
|
||||||
r"(?P<uuid>[0-9a-f\-]{36}): started", output).group("uuid")
|
rally("task export --type html --to %s" % html_report)
|
||||||
connection = (
|
self.assertTrue(os.path.exists(html_report))
|
||||||
"file-exporter:///" + rally.gen_report_path(extension="json"))
|
self._assert_html_report_libs_are_embedded(html_report, False)
|
||||||
output = rally("task export --uuid %s --connection %s" % (
|
|
||||||
uuid, connection))
|
|
||||||
expected = (
|
|
||||||
"Task %(uuid)s results was successfully exported to %("
|
|
||||||
"connection)s using file-exporter plugin." % {
|
|
||||||
"uuid": uuid,
|
|
||||||
"connection": connection,
|
|
||||||
})
|
|
||||||
self.assertIn(expected, output)
|
|
||||||
|
|
||||||
def test_export_with_wrong_connection(self):
|
rally("task export --type html-static --to %s" % html_report)
|
||||||
|
self.assertTrue(os.path.exists(html_report))
|
||||||
|
self._assert_html_report_libs_are_embedded(html_report)
|
||||||
|
|
||||||
|
junit_report = rally.gen_report_path(extension="junit")
|
||||||
|
rally("task export --type junit-xml --to %s" % junit_report)
|
||||||
|
self.assertTrue(os.path.exists(junit_report))
|
||||||
|
|
||||||
|
def test_export_bunch_uuids(self):
|
||||||
rally = utils.Rally()
|
rally = utils.Rally()
|
||||||
cfg = {
|
cfg = self._get_sample_task_config()
|
||||||
"Dummy.dummy": [
|
|
||||||
{
|
|
||||||
"runner": {
|
|
||||||
"type": "constant",
|
|
||||||
"times": 100,
|
|
||||||
"concurrency": 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
config = utils.TaskConfig(cfg)
|
config = utils.TaskConfig(cfg)
|
||||||
output = rally("task start --task %s" % config.filename)
|
task_uuids = []
|
||||||
uuid = re.search(
|
for i in range(3):
|
||||||
r"(?P<uuid>[0-9a-f\-]{36}): started", output).group("uuid")
|
res = rally("task start --task %s" % config.filename)
|
||||||
connection = (
|
for line in res.splitlines():
|
||||||
"fake:///" + rally.gen_report_path(extension="json"))
|
if "finished" in line:
|
||||||
self.assertRaises(utils.RallyCliError,
|
task_uuids.append(line.split(" ")[1][:-1])
|
||||||
rally,
|
html_report = rally.gen_report_path(extension="html")
|
||||||
"task export --uuid %s --connection %s" % (
|
rally("task export --uuid %s --type html --to %s" % (
|
||||||
uuid, connection))
|
" ".join(task_uuids), html_report))
|
||||||
|
self.assertTrue(os.path.exists(html_report))
|
||||||
|
|
||||||
|
|
||||||
class SLATestCase(unittest.TestCase):
|
class SLATestCase(unittest.TestCase):
|
||||||
|
@ -631,7 +631,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
side_effect=mock.mock_open(), create=True)
|
side_effect=mock.mock_open(), create=True)
|
||||||
@mock.patch("rally.cli.commands.task.plot")
|
@mock.patch("rally.cli.commands.task.plot")
|
||||||
@mock.patch("rally.cli.commands.task.webbrowser")
|
@mock.patch("rally.cli.commands.task.webbrowser")
|
||||||
def test_report_one_uuid(self, mock_webbrowser,
|
def test_old_report_one_uuid(self, mock_webbrowser,
|
||||||
mock_plot, mock_open, mock_realpath):
|
mock_plot, mock_open, mock_realpath):
|
||||||
task_id = "eb290c30-38d8-4c8f-bbcc-fc8f74b004ae"
|
task_id = "eb290c30-38d8-4c8f-bbcc-fc8f74b004ae"
|
||||||
data = [
|
data = [
|
||||||
@ -663,7 +663,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
for m in (self.fake_api.task.get_detailed, mock_webbrowser,
|
for m in (self.fake_api.task.get_detailed, mock_webbrowser,
|
||||||
mock_plot, mock_open):
|
mock_plot, mock_open):
|
||||||
m.reset_mock()
|
m.reset_mock()
|
||||||
self.task.report(self.fake_api, tasks=task_id,
|
self.task._old_report(self.fake_api, tasks=task_id,
|
||||||
out="/tmp/%s.html" % task_id)
|
out="/tmp/%s.html" % task_id)
|
||||||
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
|
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
|
||||||
mock_plot.plot.assert_called_once_with(results, include_libs=False)
|
mock_plot.plot.assert_called_once_with(results, include_libs=False)
|
||||||
@ -674,14 +674,15 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
|
|
||||||
# JUnit
|
# JUnit
|
||||||
reset_mocks()
|
reset_mocks()
|
||||||
self.task.report(self.fake_api, tasks=task_id,
|
self.task._old_report(self.fake_api, tasks=task_id,
|
||||||
out="/tmp/%s.html" % task_id, out_format="junit")
|
out="/tmp/%s.html" % task_id,
|
||||||
|
out_format="junit-xml")
|
||||||
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
|
mock_open.assert_called_once_with("/tmp/%s.html" % task_id, "w+")
|
||||||
self.assertFalse(mock_plot.plot.called)
|
self.assertFalse(mock_plot.plot.called)
|
||||||
|
|
||||||
# HTML
|
# HTML
|
||||||
reset_mocks()
|
reset_mocks()
|
||||||
self.task.report(self.fake_api, task_id, out="output.html",
|
self.task._old_report(self.fake_api, task_id, out="output.html",
|
||||||
open_it=True, out_format="html")
|
open_it=True, out_format="html")
|
||||||
mock_webbrowser.open_new_tab.assert_called_once_with(
|
mock_webbrowser.open_new_tab.assert_called_once_with(
|
||||||
"file://realpath_output.html")
|
"file://realpath_output.html")
|
||||||
@ -689,7 +690,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
|
|
||||||
# HTML with embedded JS/CSS
|
# HTML with embedded JS/CSS
|
||||||
reset_mocks()
|
reset_mocks()
|
||||||
self.task.report(self.fake_api, task_id, open_it=False,
|
self.task._old_report(self.fake_api, task_id, open_it=False,
|
||||||
out="output.html", out_format="html_static")
|
out="output.html", out_format="html_static")
|
||||||
self.assertFalse(mock_webbrowser.open_new_tab.called)
|
self.assertFalse(mock_webbrowser.open_new_tab.called)
|
||||||
mock_plot.plot.assert_called_once_with(results, include_libs=True)
|
mock_plot.plot.assert_called_once_with(results, include_libs=True)
|
||||||
@ -700,7 +701,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
side_effect=mock.mock_open(), create=True)
|
side_effect=mock.mock_open(), create=True)
|
||||||
@mock.patch("rally.cli.commands.task.plot")
|
@mock.patch("rally.cli.commands.task.plot")
|
||||||
@mock.patch("rally.cli.commands.task.webbrowser")
|
@mock.patch("rally.cli.commands.task.webbrowser")
|
||||||
def test_report_bunch_uuids(self, mock_webbrowser,
|
def test_old_report_bunch_uuids(self, mock_webbrowser,
|
||||||
mock_plot, mock_open, mock_realpath):
|
mock_plot, mock_open, mock_realpath):
|
||||||
tasks = ["eb290c30-38d8-4c8f-bbcc-fc8f74b004ae",
|
tasks = ["eb290c30-38d8-4c8f-bbcc-fc8f74b004ae",
|
||||||
"eb290c30-38d8-4c8f-bbcc-fc8f74b004af"]
|
"eb290c30-38d8-4c8f-bbcc-fc8f74b004af"]
|
||||||
@ -737,7 +738,8 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
for m in (self.fake_api.task.get_detailed, mock_webbrowser,
|
for m in (self.fake_api.task.get_detailed, mock_webbrowser,
|
||||||
mock_plot, mock_open):
|
mock_plot, mock_open):
|
||||||
m.reset_mock()
|
m.reset_mock()
|
||||||
self.task.report(self.fake_api, tasks=tasks, out="/tmp/1_test.html")
|
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_open.assert_called_once_with("/tmp/1_test.html", "w+")
|
||||||
mock_plot.plot.assert_called_once_with(results, include_libs=False)
|
mock_plot.plot.assert_called_once_with(results, include_libs=False)
|
||||||
|
|
||||||
@ -751,7 +753,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
side_effect=lambda p: "realpath_%s" % p)
|
side_effect=lambda p: "realpath_%s" % p)
|
||||||
@mock.patch("rally.cli.commands.task.open", create=True)
|
@mock.patch("rally.cli.commands.task.open", create=True)
|
||||||
@mock.patch("rally.cli.commands.task.plot")
|
@mock.patch("rally.cli.commands.task.plot")
|
||||||
def test_report_one_file(self, mock_plot, mock_open, mock_realpath,
|
def test_old_report_one_file(self, mock_plot, mock_open, mock_realpath,
|
||||||
mock_path_exists):
|
mock_path_exists):
|
||||||
|
|
||||||
task_file = "/tmp/some_file.json"
|
task_file = "/tmp/some_file.json"
|
||||||
@ -783,7 +785,7 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
return_value=results
|
return_value=results
|
||||||
)
|
)
|
||||||
|
|
||||||
self.task.report(self.real_api, tasks=task_file,
|
self.task._old_report(self.real_api, tasks=task_file,
|
||||||
out="/tmp/1_test.html")
|
out="/tmp/1_test.html")
|
||||||
|
|
||||||
self.task._load_task_results_file.assert_called_once_with(
|
self.task._load_task_results_file.assert_called_once_with(
|
||||||
@ -795,11 +797,35 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
|
|
||||||
@mock.patch("rally.cli.commands.task.os.path.exists", return_value=False)
|
@mock.patch("rally.cli.commands.task.os.path.exists", return_value=False)
|
||||||
@mock.patch("rally.cli.commands.task.tutils.open", create=True)
|
@mock.patch("rally.cli.commands.task.tutils.open", create=True)
|
||||||
def test_report_exceptions(self, mock_open, mock_path_exists):
|
def test_old_report_exceptions(self, mock_open, mock_path_exists):
|
||||||
ret = self.task.report(self.real_api, tasks="/tmp/task.json",
|
ret = self.task._old_report(self.real_api, tasks="/tmp/task.json",
|
||||||
out="/tmp/tmp.hsml")
|
out="/tmp/tmp.hsml")
|
||||||
self.assertEqual(ret, 1)
|
self.assertEqual(ret, 1)
|
||||||
|
|
||||||
|
@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()
|
||||||
|
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",
|
||||||
|
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",
|
||||||
|
output_dest="out", open_it=False
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch("rally.cli.commands.task.cliutils.print_list")
|
@mock.patch("rally.cli.commands.task.cliutils.print_list")
|
||||||
@mock.patch("rally.cli.commands.task.envutils.get_global",
|
@mock.patch("rally.cli.commands.task.envutils.get_global",
|
||||||
return_value="123456789")
|
return_value="123456789")
|
||||||
@ -957,38 +983,40 @@ class TaskCommandsTestCase(test.TestCase):
|
|||||||
self.assertRaises(exceptions.TaskNotFound, self.task.use,
|
self.assertRaises(exceptions.TaskNotFound, self.task.use,
|
||||||
self.fake_api, task_id)
|
self.fake_api, task_id)
|
||||||
|
|
||||||
@mock.patch("rally.task.exporter.Exporter.get")
|
@mock.patch("rally.cli.commands.task.os.path")
|
||||||
def test_export(self, mock_exporter_get):
|
@mock.patch("rally.cli.commands.task.webbrowser.open_new_tab")
|
||||||
mock_client = mock.Mock()
|
@mock.patch("rally.cli.commands.task.open", create=True)
|
||||||
mock_exporter_class = mock.Mock(return_value=mock_client)
|
@mock.patch("rally.cli.commands.task.print")
|
||||||
mock_exporter_get.return_value = mock_exporter_class
|
def test_export(self, mock_print, mock_open, mock_open_new_tab,
|
||||||
self.task.export(self.fake_api, "fake_uuid", "file:///fake_path.json")
|
mock_path):
|
||||||
mock_exporter_get.assert_called_once_with("file")
|
|
||||||
mock_client.export.assert_called_once_with("fake_uuid")
|
|
||||||
|
|
||||||
@mock.patch("rally.task.exporter.Exporter.get")
|
# file
|
||||||
def test_export_exception(self, mock_exporter_get):
|
self.fake_api.task.export.return_value = {
|
||||||
mock_client = mock.Mock()
|
"files": {"output_dest": "content"}, "open": "output_dest"}
|
||||||
mock_exporter_class = mock.Mock(return_value=mock_client)
|
mock_path.expanduser.return_value = "output_file"
|
||||||
mock_exporter_get.return_value = mock_exporter_class
|
mock_path.realpath.return_value = "real_path"
|
||||||
mock_client.export.side_effect = IOError
|
mock_fd = mock.mock_open()
|
||||||
self.task.export(self.fake_api, "fake_uuid", "file:///fake_path.json")
|
mock_open.side_effect = mock_fd
|
||||||
mock_exporter_get.assert_called_once_with("file")
|
|
||||||
mock_client.export.assert_called_once_with("fake_uuid")
|
|
||||||
|
|
||||||
@mock.patch("rally.cli.commands.task.sys.stdout")
|
self.task.export(self.fake_api, task_id="uuid",
|
||||||
@mock.patch("rally.task.exporter.Exporter.get")
|
output_type="json", output_dest="output_dest",
|
||||||
def test_export_InvalidConnectionString(self, mock_exporter_get,
|
open_it=True)
|
||||||
mock_stdout):
|
|
||||||
mock_exporter_class = mock.Mock(
|
self.fake_api.task.export.assert_called_once_with(
|
||||||
side_effect=exceptions.InvalidConnectionString)
|
tasks_uuids=["uuid"], output_type="json",
|
||||||
mock_exporter_get.return_value = mock_exporter_class
|
output_dest="output_dest"
|
||||||
self.task.export(self.fake_api, "fake_uuid", "file:///fake_path.json")
|
)
|
||||||
mock_stdout.write.assert_has_calls([
|
mock_open.assert_called_once_with("output_file", "w+")
|
||||||
mock.call("The connection string is not valid: None. "
|
mock_fd.return_value.write.assert_called_once_with("content")
|
||||||
"Please check your connection string."),
|
|
||||||
mock.call("\n")])
|
# print
|
||||||
mock_exporter_get.assert_called_once_with("file")
|
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.fake_api.task.export.assert_called_once_with(
|
||||||
|
tasks_uuids=["uuid"], output_type="json", output_dest=None
|
||||||
|
)
|
||||||
|
mock_print.assert_called_once_with("content")
|
||||||
|
|
||||||
@mock.patch("rally.cli.commands.task.plot.charts")
|
@mock.patch("rally.cli.commands.task.plot.charts")
|
||||||
@mock.patch("rally.cli.commands.task.sys.stdout")
|
@mock.patch("rally.cli.commands.task.sys.stdout")
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
# Copyright 2016: Mirantis Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import ddt
|
|
||||||
import mock
|
|
||||||
import six
|
|
||||||
import six.moves.builtins as __builtin__
|
|
||||||
|
|
||||||
from rally import exceptions
|
|
||||||
from rally.plugins.common.exporter import file_system
|
|
||||||
from tests.unit import test
|
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
import io
|
|
||||||
file = io.BytesIO
|
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
|
||||||
class FileExporterTestCase(test.TestCase):
|
|
||||||
|
|
||||||
@mock.patch("rally.plugins.common.exporter.file_system.os.path.exists")
|
|
||||||
@mock.patch.object(__builtin__, "open", autospec=True)
|
|
||||||
@mock.patch("rally.plugins.common.exporter.file_system.json.dumps")
|
|
||||||
@mock.patch("rally.api.API")
|
|
||||||
def test_file_exporter_export(self, mock_api, mock_dumps,
|
|
||||||
mock_open, mock_exists):
|
|
||||||
rapi = mock_api.return_value
|
|
||||||
mock_exists.return_value = True
|
|
||||||
rapi.task.get_detailed.return_value = {"results": [{
|
|
||||||
"key": "fake_key",
|
|
||||||
"data": {
|
|
||||||
"raw": "bar_raw",
|
|
||||||
"sla": "baz_sla",
|
|
||||||
"hooks": "baz_hooks",
|
|
||||||
"load_duration": "foo_load_duration",
|
|
||||||
"full_duration": "foo_full_duration",
|
|
||||||
}}]}
|
|
||||||
mock_dumps.return_value = "fake_results"
|
|
||||||
input_mock = mock.MagicMock(spec=file)
|
|
||||||
mock_open.return_value = input_mock
|
|
||||||
|
|
||||||
exporter = file_system.FileExporter("file-exporter:///fake_path.json")
|
|
||||||
exporter.export("fake_uuid")
|
|
||||||
|
|
||||||
mock_open().__enter__().write.assert_called_once_with("fake_results")
|
|
||||||
rapi.task.get_detailed.assert_called_once_with(task_id="fake_uuid")
|
|
||||||
expected_dict = [
|
|
||||||
{
|
|
||||||
"load_duration": "foo_load_duration",
|
|
||||||
"full_duration": "foo_full_duration",
|
|
||||||
"result": "bar_raw",
|
|
||||||
"key": "fake_key",
|
|
||||||
"hooks": "baz_hooks",
|
|
||||||
"sla": "baz_sla"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
mock_dumps.assert_called_once_with(expected_dict, sort_keys=False,
|
|
||||||
indent=4, separators=(",", ": "))
|
|
||||||
|
|
||||||
@mock.patch("rally.api.API")
|
|
||||||
def test_file_exporter_export_running_task(self, mock_api):
|
|
||||||
mock_api.task.get_detailed.return_value = {"results": []}
|
|
||||||
|
|
||||||
exporter = file_system.FileExporter("file-exporter:///fake_path.json")
|
|
||||||
self.assertRaises(exceptions.RallyException, exporter.export,
|
|
||||||
"fake_uuid")
|
|
||||||
|
|
||||||
@ddt.data(
|
|
||||||
{"connection": "",
|
|
||||||
"raises": exceptions.InvalidConnectionString},
|
|
||||||
{"connection": "file-exporter:///fake_path.json",
|
|
||||||
"raises": None},
|
|
||||||
{"connection": "file-exporter:///fake_path.fake",
|
|
||||||
"raises": exceptions.InvalidConnectionString},
|
|
||||||
)
|
|
||||||
@ddt.unpack
|
|
||||||
def test_file_exporter_validate(self, connection, raises):
|
|
||||||
print(connection)
|
|
||||||
if raises:
|
|
||||||
self.assertRaises(raises, file_system.FileExporter, connection)
|
|
||||||
else:
|
|
||||||
file_system.FileExporter(connection)
|
|
180
tests/unit/plugins/common/exporter/test_reporters.py
Normal file
180
tests/unit/plugins/common/exporter/test_reporters.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.plugins.common.exporter import reporters
|
||||||
|
from tests.unit import test
|
||||||
|
|
||||||
|
PATH = "rally.plugins.common.exporter.reporters"
|
||||||
|
|
||||||
|
|
||||||
|
def get_tasks_results():
|
||||||
|
return [{"created_at": "2017-06-04T05:14:44",
|
||||||
|
"updated_at": "2017-06-04T05:15:14",
|
||||||
|
"task_uuid": "2fa4f5ff-7d23-4bb0-9b1f-8ee235f7f1c8",
|
||||||
|
"key": {
|
||||||
|
"kw": {},
|
||||||
|
"pos": 0,
|
||||||
|
"name": "CinderVolumes.list_volumes",
|
||||||
|
"description": "List all volumes."
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"raw": [],
|
||||||
|
"full_duration": 29.969523191452026,
|
||||||
|
"sla": [],
|
||||||
|
"load_duration": 2.03029203414917,
|
||||||
|
"hooks": []
|
||||||
|
},
|
||||||
|
"id": 3}]
|
||||||
|
|
||||||
|
|
||||||
|
class OldJSONResultsMixinTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def test__generate_tasks_results(self):
|
||||||
|
|
||||||
|
class DummyReport(reporters.OldJSONResultsMixin):
|
||||||
|
def __init__(self, raw_tasks_results):
|
||||||
|
self.tasks_results = raw_tasks_results
|
||||||
|
|
||||||
|
reporter = DummyReport(get_tasks_results())
|
||||||
|
results = reporter._generate_tasks_results()
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"hooks": [],
|
||||||
|
"created_at": "2017-06-04T05:14:44",
|
||||||
|
"load_duration": 2.03029203414917,
|
||||||
|
"result": [],
|
||||||
|
"key": {
|
||||||
|
"kw": {},
|
||||||
|
"pos": 0,
|
||||||
|
"name": "CinderVolumes.list_volumes",
|
||||||
|
"description": "List all volumes."
|
||||||
|
},
|
||||||
|
"full_duration": 29.969523191452026,
|
||||||
|
"sla": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
results
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLExporterTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def test_validate(self):
|
||||||
|
# nothing should fail
|
||||||
|
reporters.HTMLExporter.validate(mock.Mock())
|
||||||
|
reporters.HTMLExporter.validate("")
|
||||||
|
reporters.HTMLExporter.validate(None)
|
||||||
|
|
||||||
|
def test__generate(self):
|
||||||
|
tasks_results = get_tasks_results()
|
||||||
|
tasks_results.extend(get_tasks_results())
|
||||||
|
reporter = reporters.HTMLExporter(tasks_results, None)
|
||||||
|
results = reporter._generate()
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"hooks": [],
|
||||||
|
"created_at": "2017-06-04T05:14:44",
|
||||||
|
"load_duration": 2.03029203414917,
|
||||||
|
"result": [],
|
||||||
|
"key": {
|
||||||
|
"kw": {},
|
||||||
|
"pos": 0,
|
||||||
|
"name": "CinderVolumes.list_volumes",
|
||||||
|
"description": "List all volumes."
|
||||||
|
},
|
||||||
|
"full_duration": 29.969523191452026,
|
||||||
|
"sla": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hooks": [],
|
||||||
|
"created_at": "2017-06-04T05:14:44",
|
||||||
|
"load_duration": 2.03029203414917,
|
||||||
|
"result": [],
|
||||||
|
"key": {
|
||||||
|
"kw": {},
|
||||||
|
"pos": 1,
|
||||||
|
"name": "CinderVolumes.list_volumes",
|
||||||
|
"description": "List all volumes."
|
||||||
|
},
|
||||||
|
"full_duration": 29.969523191452026,
|
||||||
|
"sla": []
|
||||||
|
}], results)
|
||||||
|
|
||||||
|
@mock.patch("%s.HTMLExporter._generate" % PATH,
|
||||||
|
return_value="task_results")
|
||||||
|
@mock.patch("%s.plot.plot" % PATH, return_value="html")
|
||||||
|
def test_generate(self, mock_plot, mock__generate):
|
||||||
|
reporter = reporters.HTMLExporter([], output_destination=None)
|
||||||
|
self.assertEqual({"print": "html"}, reporter.generate())
|
||||||
|
mock__generate.assert_called_once_with()
|
||||||
|
mock_plot.assert_called_once_with("task_results",
|
||||||
|
include_libs=False)
|
||||||
|
|
||||||
|
mock__generate.reset_mock()
|
||||||
|
mock_plot.reset_mock()
|
||||||
|
reporter = reporters.HTMLExporter([], output_destination="path")
|
||||||
|
reporter.INCLUDE_LIBS = True
|
||||||
|
self.assertEqual({"files": {"path": "html"},
|
||||||
|
"open": "file://" + os.path.abspath("path")},
|
||||||
|
reporter.generate())
|
||||||
|
mock__generate.assert_called_once_with()
|
||||||
|
mock_plot.assert_called_once_with("task_results",
|
||||||
|
include_libs=True)
|
||||||
|
|
||||||
|
|
||||||
|
class JUnitXMLExporterTestCase(test.TestCase):
|
||||||
|
def test_generate(self):
|
||||||
|
content = ("<testsuite errors=\"0\""
|
||||||
|
" failures=\"0\""
|
||||||
|
" name=\"Rally test suite\""
|
||||||
|
" tests=\"1\""
|
||||||
|
" time=\"29.97\">"
|
||||||
|
"<testcase classname=\"CinderVolumes\""
|
||||||
|
" name=\"list_volumes\""
|
||||||
|
" time=\"29.97\" />"
|
||||||
|
"</testsuite>")
|
||||||
|
|
||||||
|
reporter = reporters.JUnitXMLExporter(get_tasks_results(),
|
||||||
|
output_destination=None)
|
||||||
|
self.assertEqual({"print": content}, reporter.generate())
|
||||||
|
|
||||||
|
reporter = reporters.JUnitXMLExporter(get_tasks_results(),
|
||||||
|
output_destination="path")
|
||||||
|
self.assertEqual({"files": {"path": content},
|
||||||
|
"open": "file://" + os.path.abspath("path")},
|
||||||
|
reporter.generate())
|
||||||
|
|
||||||
|
def test_generate_fail(self):
|
||||||
|
tasks_results = get_tasks_results()
|
||||||
|
tasks_results[0]["data"]["sla"] = [{"success": False,
|
||||||
|
"detail": "error"}]
|
||||||
|
content = ("<testsuite errors=\"0\""
|
||||||
|
" failures=\"1\""
|
||||||
|
" name=\"Rally test suite\""
|
||||||
|
" tests=\"1\""
|
||||||
|
" time=\"29.97\">"
|
||||||
|
"<testcase classname=\"CinderVolumes\""
|
||||||
|
" name=\"list_volumes\""
|
||||||
|
" time=\"29.97\">"
|
||||||
|
"<failure message=\"error\" /></testcase>"
|
||||||
|
"</testsuite>")
|
||||||
|
reporter = reporters.JUnitXMLExporter(tasks_results,
|
||||||
|
output_destination=None)
|
||||||
|
self.assertEqual({"print": content}, reporter.generate())
|
@ -12,6 +12,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
import mock
|
||||||
|
|
||||||
from rally.task import exporter
|
from rally.task import exporter
|
||||||
from tests.unit import test
|
from tests.unit import test
|
||||||
|
|
||||||
@ -33,3 +36,59 @@ class ExporterTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_task_export_instantiate(self):
|
def test_task_export_instantiate(self):
|
||||||
TestExporter("fake_connection")
|
TestExporter("fake_connection")
|
||||||
|
|
||||||
|
|
||||||
|
class TaskExporterTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def test_make(self):
|
||||||
|
reporter_cls = mock.Mock()
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"files": {}}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
reporter_cls.return_value.generate.return_value = {
|
||||||
|
"files": {"/path/foo": "content"}}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"open": "/path/foo"}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"print": "foo"}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {
|
||||||
|
"files": {"/path/foo": "content"}, "open": "/path/foo",
|
||||||
|
"print": "foo"}
|
||||||
|
exporter.TaskExporter.make(reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"files": []}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"files": ""}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"files": {"a": {}}}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"open": []}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"print": []}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
|
||||||
|
reporter_cls.return_value.generate.return_value = {"additional": ""}
|
||||||
|
self.assertRaises(jsonschema.ValidationError,
|
||||||
|
exporter.TaskExporter.make,
|
||||||
|
reporter_cls, None, None, None)
|
||||||
|
@ -419,6 +419,31 @@ class TaskAPITestCase(test.TestCase):
|
|||||||
self.task_uuid,
|
self.task_uuid,
|
||||||
status=expected_status)
|
status=expected_status)
|
||||||
|
|
||||||
|
@mock.patch("rally.api.texporter.TaskExporter")
|
||||||
|
@mock.patch("rally.api.objects.Task.get_detailed",
|
||||||
|
return_value={"results": ["detail"]})
|
||||||
|
def test_export(self, mock_task_get_detailed, mock_task_exporter):
|
||||||
|
task_id = ["uuid-1", "uuid-2"]
|
||||||
|
output_type = mock.Mock()
|
||||||
|
output_dest = mock.Mock()
|
||||||
|
|
||||||
|
reporter = mock_task_exporter.get.return_value
|
||||||
|
|
||||||
|
self.assertEqual(mock_task_exporter.make.return_value,
|
||||||
|
self.task_inst.export(
|
||||||
|
tasks_uuids=task_id,
|
||||||
|
output_type=output_type,
|
||||||
|
output_dest=output_dest))
|
||||||
|
mock_task_exporter.get.assert_called_once_with(output_type)
|
||||||
|
|
||||||
|
reporter.validate.assert_called_once_with(output_dest)
|
||||||
|
|
||||||
|
mock_task_exporter.make.assert_called_once_with(
|
||||||
|
reporter, ["detail", "detail"], output_dest,
|
||||||
|
api=self.task_inst.api)
|
||||||
|
self.assertEqual([mock.call(u) for u in task_id],
|
||||||
|
mock_task_get_detailed.call_args_list)
|
||||||
|
|
||||||
@mock.patch("rally.api.objects.Task")
|
@mock.patch("rally.api.objects.Task")
|
||||||
def test_get_detailed(self, mock_task):
|
def test_get_detailed(self, mock_task):
|
||||||
mock_task.get_detailed.return_value = "detailed_task_data"
|
mock_task.get_detailed.return_value = "detailed_task_data"
|
||||||
|
Loading…
Reference in New Issue
Block a user