# Copyright (C) 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
#
# This file is part of Kitty.
#
# Kitty is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Kitty is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Kitty. If not, see <http://www.gnu.org/licenses/>.
'''
This module defines the :class:`~kitty.data.report.Report` class
'''
[docs]class Report(object):
'''
This class represent a report for a single test.
This report may contain subreports from nested entities.
:example:
In this example, the report, generated by the controller, indicates
failure.
::
report = Report('Controller')
report.add('generation time', 0)
report.failed('target does not respond')
'''
PASSED = 'passed'
FAILED = 'failed'
ERROR = 'error'
allowed_statuses = [PASSED, FAILED, ERROR]
reserved_keys = {
'status': 'set_status(status), success(), failed(reason) or error(reason)',
'failed': 'failed(reason)'
}
[docs] def __init__(self, name, default_failed=False):
'''
:param name: name of the report (or the issuer)
:param default_failed: is the default status of the report failed (default: False)
'''
self._data_fields = {}
self._sub_reports = {}
self._name = name
self._default_failed = default_failed
self.clear()
[docs] def clear(self):
'''
Set the report to its defaults.
This will clear the report, keeping only the name and setting the
failure status to the default.
'''
self._data_fields = {}
self._sub_reports = {}
self.set_status(Report.FAILED if self._default_failed else Report.PASSED)
self.add('name', self._name)
self.add('sub_reports', [])
[docs] def get_name(self):
'''
:return: the name of the report
'''
return self.get('name')
[docs] def passed(self):
'''
Set the report status to PASSED
'''
self.set_status(Report.PASSED)
if 'reason' in self._data_fields:
del self._data_fields['reason']
[docs] def success(self):
'''
Set the report status to PASSED.
.. deprecated:: 0.6.7
use :func:`~kitty.data.report.Report.passed`
'''
self.passed()
[docs] def failed(self, reason=None):
'''
Set the test status to Report.FAILED, and set the failure reason
:param reason: failure reason (default: None)
'''
self.set_status(Report.FAILED)
if reason:
self.add('reason', reason)
[docs] def error(self, reason=None):
'''
Set the test status to Report.ERROR, and set the error reason
:param reason: error reason (default: None)
'''
self.set_status(Report.ERROR)
if reason:
self.add('reason', reason)
[docs] def set_status(self, new_status):
'''
Set the status of the report.
:param new_status: the new status of the report (either PASSED, FAILED or ERROR)
'''
if new_status not in Report.allowed_statuses:
raise Exception('status must be one of: %s' % (', '.join(Report.allowed_statuses)))
self._data_fields['status'] = new_status.lower()
[docs] def add(self, key, value):
'''
Add an entry to the report
:param key: entry's key
:param value: the actual value
:example:
::
my_report.add('retry count', 3)
'''
if key in self.reserved_keys:
raise Exception('You cannot add the key %s directly, use %s' % (key, self.reserved_keys[key]))
if isinstance(value, Report):
self._sub_reports[key] = value
self._data_fields['sub_reports'].append(key)
else:
self._data_fields[key] = value
[docs] def get(self, key):
'''
Get a value for a given key
:param key: entry's key
:return: corresponding value
'''
if key in self._data_fields:
return self._data_fields[key]
if key in self._sub_reports:
return self._sub_reports[key]
return None
[docs] def to_dict(self, encoding='base64'):
'''
Return a dictionary version of the report
:param encoding: required encoding for the string values (default: 'base64')
:rtype: dictionary
:return: dictionary representation of the report
'''
res = {}
for k, v in self._data_fields.items():
if isinstance(v, unicode):
v = v.encode('utf-8')
if isinstance(v, str):
v = v.encode(encoding)[:-1]
res[k] = v
for k, v in self._sub_reports.items():
res[k] = v.to_dict(encoding)
return res
@classmethod
def _decode(cls, val, encoding):
if isinstance(val, str):
val = val.decode(encoding)
return val
@classmethod
[docs] def from_dict(cls, d, encoding='base64'):
'''
Construct a ``Report`` object from dictionary.
:type d: dictionary
:param d: dictionary representing the report
:param encoding: encoding of strings in the dictionary (default: 'base64')
:return: Report object
'''
report = Report(Report._decode(d['name'], encoding))
report.set_status(Report._decode(d['status'], encoding))
sub_reports = Report._decode(d['sub_reports'], encoding)
del d['sub_reports']
for k, v in d.items():
if k in sub_reports:
report.add(k, Report.from_dict(v))
else:
if k.lower() == 'status':
report.set_status(Report._decode(v, encoding))
else:
report.add(k, Report._decode(v, encoding))
return report
[docs] def get_status(self):
'''
Get the status of the report and its sub-reports.
:rtype: str
:return: report status ('passed', 'failed' or 'error')
'''
status = self.get('status')
if status == Report.PASSED:
for sr_name in self._sub_reports:
sr = self._sub_reports[sr_name]
sr_status = sr.get_status()
reason = sr.get('reason')
if sr_status == Report.ERROR:
self.error(reason)
break
if sr_status == Report.FAILED:
self.failed(reason)
break
status = self.get('status')
return status
[docs] def is_failed(self):
'''
.. deprecated:: 0.6.7
use :func:`~kitty.data.report.Report.get_status`
'''
raise NotImplementedError('API was changed, use get_status instead')