Lib.robot.result.model.py Maven / Gradle / Ivy
# Copyright 2008-2015 Nokia Solutions and Networks
#
# 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.
"""Module implementing result related model objects.
During test execution these objects are created internally by various runners.
At that time they can inspected and modified by listeners__.
When results are parsed from XML output files after execution to be able to
create logs and reports, these objects are created by the
:func:`~.resultbuilder.ExecutionResult` factory method.
At that point they can be inspected and modified by `pre-Rebot modifiers`__.
The :func:`~.resultbuilder.ExecutionResult` factory method can also be used
by custom scripts and tools. In such usage it is often easiest to inspect and
modify these objects using the :mod:`visitor interface `.
__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface
__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#programmatic-modification-of-results
"""
from itertools import chain
from robot.model import TotalStatisticsBuilder, Criticality
from robot import model, utils
from .configurer import SuiteConfigurer
from .messagefilter import MessageFilter
from .keywordremover import KeywordRemover
from .suiteteardownfailed import (SuiteTeardownFailureHandler,
SuiteTeardownFailed)
# TODO: Should remove model.Message altogether and just implement the whole
# thing here. Additionally model.Keyword should not have `message_class` at
# all or it should be None.
class Message(model.Message):
"""Represents a single log message.
See the base class for documentation of attributes not documented here.
"""
__slots__ = []
class Keyword(model.Keyword):
"""Represents results of a single keyword.
See the base class for documentation of attributes not documented here.
"""
__slots__ = ['kwname', 'libname', 'status', 'starttime', 'endtime', 'message']
message_class = Message
def __init__(self, kwname='', libname='', doc='', args=(), assign=(),
tags=(), timeout=None, type='kw', status='FAIL',
starttime=None, endtime=None):
model.Keyword.__init__(self, '', doc, args, assign, tags, timeout, type)
#: Name of the keyword without library or resource name.
self.kwname = kwname or ''
#: Name of the library or resource containing this keyword.
self.libname = libname or ''
#: Execution status as a string. Typically ``PASS`` or ``FAIL``, but
#: library keywords have status ``NOT_RUN`` in the dry-ryn mode.
#: See also :attr:`passed`.
self.status = status
#: Keyword execution start time in format ``%Y%m%d %H:%M:%S.%f``.
self.starttime = starttime
#: Keyword execution end time in format ``%Y%m%d %H:%M:%S.%f``.
self.endtime = endtime
#: Keyword status message. Used only if suite teardowns fails.
self.message = ''
@property
def elapsedtime(self):
"""Total execution time in milliseconds."""
return utils.get_elapsed_time(self.starttime, self.endtime)
@property
def name(self):
"""Keyword name in format ``libname.kwname``.
Just ``kwname`` if :attr:`libname` is empty. In practice that is the
case only with user keywords in the same file as the executed test case
or test suite.
Cannot be set directly. Set :attr:`libname` and :attr:`kwname`
separately instead.
"""
if not self.libname:
return self.kwname
return '%s.%s' % (self.libname, self.kwname)
@property
def passed(self):
"""``True`` or ``False`` depending on the :attr:`status`."""
return self.status == 'PASS'
@passed.setter
def passed(self, passed):
self.status = 'PASS' if passed else 'FAIL'
class TestCase(model.TestCase):
"""Represents results of a single test case.
See the base class for documentation of attributes not documented here.
"""
__slots__ = ['status', 'message', 'starttime', 'endtime']
keyword_class = Keyword
def __init__(self, name='', doc='', tags=None, timeout=None, status='FAIL',
message='', starttime=None, endtime=None):
model.TestCase.__init__(self, name, doc, tags, timeout)
#: Status as a string ``PASS`` or ``FAIL``. See also :attr:`passed`.
self.status = status
#: Test message. Typically a failure message but can be set also when
#: test passes.
self.message = message
#: Test case execution start time in format ``%Y%m%d %H:%M:%S.%f``.
self.starttime = starttime
#: Test case execution end time in format ``%Y%m%d %H:%M:%S.%f``.
self.endtime = endtime
@property
def elapsedtime(self):
"""Total execution time in milliseconds."""
return utils.get_elapsed_time(self.starttime, self.endtime)
@property
def passed(self):
"""``True/False`` depending on the :attr:`status`."""
return self.status == 'PASS'
@passed.setter
def passed(self, passed):
self.status = 'PASS' if passed else 'FAIL'
@property
def critical(self):
"""``True/False`` depending on is the test considered critical.
Criticality is determined based on test's :attr:`tags` and
:attr:`~TestSuite.criticality` of the :attr:`parent` suite.
"""
if not self.parent:
return True
return self.parent.criticality.test_is_critical(self)
class TestSuite(model.TestSuite):
"""Represents results of a single test suite.
See the base class for documentation of attributes not documented here.
"""
__slots__ = ['message', 'starttime', 'endtime', '_criticality']
test_class = TestCase
keyword_class = Keyword
def __init__(self, name='', doc='', metadata=None, source=None,
message='', starttime=None, endtime=None):
model.TestSuite.__init__(self, name, doc, metadata, source)
#: Possible suite setup or teardown error message.
self.message = message
#: Suite execution start time in format ``%Y%m%d %H:%M:%S.%f``.
self.starttime = starttime
#: Suite execution end time in format ``%Y%m%d %H:%M:%S.%f``.
self.endtime = endtime
self._criticality = None
@property
def passed(self):
"""``True`` if no critical test has failed, ``False`` otherwise."""
return not self.statistics.critical.failed
@property
def status(self):
"""``'PASS'`` if no critical test has failed, ``'FAIL'`` otherwise."""
return 'PASS' if self.passed else 'FAIL'
@property
def statistics(self):
"""Suite statistics as a :class:`~robot.model.totalstatistics.TotalStatistics` object.
Recreated every time this property is accessed, so saving the results
to a variable and inspecting it is often a good idea::
stats = suite.statistics
print stats.critical.failed
print stats.all.total
print stats.message
"""
return TotalStatisticsBuilder(self).stats
@property
def full_message(self):
"""Combination of :attr:`message` and :attr:`stat_message`."""
if not self.message:
return self.stat_message
return '%s\n\n%s' % (self.message, self.stat_message)
@property
def stat_message(self):
"""String representation of the :attr:`statistics`."""
return self.statistics.message
@property
def elapsedtime(self):
"""Total execution time in milliseconds."""
if self.starttime and self.endtime:
return utils.get_elapsed_time(self.starttime, self.endtime)
return sum(child.elapsedtime for child in
chain(self.suites, self.tests, self.keywords))
@property
def criticality(self):
"""Used by tests to determine are they considered critical or not.
Normally configured using ``--critical`` and ``--noncritical``
command line options. Can be set programmatically using
:meth:`set_criticality` of the root test suite.
"""
if self.parent:
return self.parent.criticality
if self._criticality is None:
self.set_criticality()
return self._criticality
def set_criticality(self, critical_tags=None, non_critical_tags=None):
"""Sets which tags are considered critical and which non-critical.
:param critical_tags: Tags or patterns considered critical. See
the documentation of the ``--critical`` option for more details.
:param non_critical_tags: Tags or patterns considered non-critical. See
the documentation of the ``--noncritical`` option for more details.
Tags can be given as lists of strings or, when giving only one,
as single strings. This information is used by tests to determine
are they considered critical or not.
Criticality can be set only to the root test suite.
"""
if self.parent:
raise TypeError('Criticality can only be set to the root suite.')
self._criticality = Criticality(critical_tags, non_critical_tags)
def remove_keywords(self, how):
"""Remove keywords based on the given condition.
:param how: What approach to use when removing keywords. Either
``ALL``, ``PASSED``, ``FOR``, ``WUKS``, or ``NAME:``.
For more information about the possible values see the documentation
of the ``--removekeywords`` command line option.
"""
self.visit(KeywordRemover(how))
def filter_messages(self, log_level='TRACE'):
"""Remove log messages below the specified ``log_level``."""
self.visit(MessageFilter(log_level))
def configure(self, **options):
"""A shortcut to configure a suite using one method call.
:param options: Passed to
:class:`~robot.result.configurer.SuiteConfigurer` that will then
set suite attributes, call :meth:`filter`, etc. as needed.
Example::
suite.configure(remove_keywords='PASSED',
critical_tags='smoke',
doc='Smoke test results.')
"""
self.visit(SuiteConfigurer(**options))
def handle_suite_teardown_failures(self):
"""Internal usage only."""
self.visit(SuiteTeardownFailureHandler())
def suite_teardown_failed(self, message):
"""Internal usage only."""
self.visit(SuiteTeardownFailed(message))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy