Skip to content

Commit ce4d5fa

Browse files
committed
Enhanced visitor interface documentation.
New model modifiers (robotframework#1976) are visitors so API docs related to it need to be better. Also exposed SuiteVisitor via robot.api.
1 parent 8f74ce3 commit ce4d5fa

File tree

6 files changed

+148
-45
lines changed

6 files changed

+148
-45
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from robot.api import SuiteVisitor
2+
3+
4+
class SelectEveryXthTest(SuiteVisitor):
5+
6+
def __init__(self, x, start=0):
7+
self.x = int(x)
8+
self.start = int(start)
9+
10+
def start_suite(self, suite):
11+
"""Modify suite's tests to contain only every Xth."""
12+
suite.tests = suite.tests[self.start::self.x]
13+
14+
def end_suite(self, suite):
15+
"""Remove suites that are empty after removing tests."""
16+
suite.suites = [s for s in suite.suites if s.test_count > 0]
17+
18+
def visit_test(self, test):
19+
"""Save time to avoid visiting tests and their keywords."""
20+
pass

src/robot/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
via the :mod:`robot` root package.
6161
"""
6262

63+
from robot.model import SuiteVisitor
6364
from robot.parsing import TestCaseFile, TestDataDirectory, ResourceFile, TestData
6465
from robot.reporting import ResultWriter
6566
from robot.result import ExecutionResult, ResultVisitor

src/robot/model/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
functionality, such as :mod:`visitors <robot.model.visitor>`.
2222
2323
These classes are extended both in :mod:`robot.result` and :mod:`robot.running`
24-
packages and used also elsewhere. There should, however, be no need to
25-
externally use these classes directly, and they are not part of the public API.
24+
packages and used also elsewhere.
2625
2726
This package is considered stable.
2827
"""
@@ -36,7 +35,7 @@
3635
from .tags import Tags, TagPattern, TagPatterns
3736
from .criticality import Criticality
3837
from .namepatterns import SuiteNamePatterns, TestNamePatterns
39-
from .visitor import SuiteVisitor, SkipAllVisitor
38+
from .visitor import SuiteVisitor
4039
from .totalstatistics import TotalStatisticsBuilder
4140
from .statistics import Statistics
4241
from .imports import Imports

src/robot/model/visitor.py

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,65 +12,150 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""Interface to ease traversing through a test suite structure.
16+
17+
Visitors make it easy to modify test suite structures or to collect information
18+
from them. They work both with the :class:`executable suite <robot.running.model.TestSuite>`
19+
and the :class:`result suite <robot.result.testsuite.TestSuite>`, but the
20+
objects passed to the visitor methods are slightly different. The main
21+
difference is that the result objects have attributes like :attr:`status` and
22+
:attr:`starttime` that do not exist in the executable objects.
23+
24+
This module contains :class:`SuiteVisitor` that implements the core logic to
25+
visit a test suite structure, and the :mod:`~robot.result` package contains
26+
:class:`~robot.result.visitor.ResultVisitor` that supports visiting the whole
27+
test execution result structure. Both of these visitors should be imported
28+
via the :mod:`robot.api` package when used by external code.
29+
30+
Visitor algorithm
31+
-----------------
32+
33+
All suite, test, keyword and message objects have a :meth:`visit` method that
34+
accepts a visitor instance. These methods will then call the correct visitor
35+
method :meth:`~SuiteVisitor.visit_suite`, :meth:`~SuiteVisitor.visit_test`,
36+
:meth:`~SuiteVisitor.visit_keyword` or :meth:`~SuiteVisitor.visit_message`,
37+
depending on the instance where the :meth:`visit` method exists.
38+
39+
The recommended and definitely easiest way to implement a visitor is extending
40+
the :class:`SuiteVisitor` base class. The default implementation of its
41+
:meth:`visit_x` methods take care of traversing child elements of the object
42+
:obj:`x` recursively. A :meth:`visit_x` method first calls a corresponding
43+
:meth:`start_x` method (e.g. :meth:`visit_suite` calls :meth:`start_suite`),
44+
then calls :meth:`visit` for all child objects of the :obj:`x` object, and
45+
finally calls the corresponding :meth:`end_x` method. The default
46+
implementations of :meth:`start_x` and :meth:`end_x` do nothing.
47+
48+
Custom visitors can stop visiting at a certain level either by overriding
49+
suitable :meth:`visit_x` method or by returning an explicit ``False`` from
50+
any :meth:`start_x` method.
51+
52+
Examples
53+
--------
54+
55+
The following example modifies the test suite structure so that it keeps only
56+
every Xth test. It could be used, for example, with Robot Framework's
57+
``--prerunmodifier`` option to modify test data before execution.
58+
59+
.. literalinclude:: /../../doc/api/code_examples/select_every_xth_test.py
60+
61+
For more examples it is possible to look at the source code of visitors used
62+
internally by Robot Framework itself. Some good examples are
63+
:class:`~robot.model.tagsetter.TagSetter` and
64+
:mod:`keyword removers <robot.result.keywordremover>`.
65+
"""
66+
67+
1568
class SuiteVisitor(object):
69+
"""Abstract class to ease traversing through the test suite structure.
70+
71+
See the :mod:`module level <robot.model.visitor>` documentation for more
72+
information and an example.
73+
"""
1674

1775
def visit_suite(self, suite):
76+
"""Implements traversing through the suite and its direct children.
77+
78+
Can be overridden to allow modifying the passed in ``suite`` without
79+
calling :func:`start_suite` or :func:`end_suite` nor visiting child
80+
suites, tests or keywords (setup and teardown) at all.
81+
"""
1882
if self.start_suite(suite) is not False:
1983
suite.keywords.visit(self)
2084
suite.suites.visit(self)
2185
suite.tests.visit(self)
2286
self.end_suite(suite)
2387

2488
def start_suite(self, suite):
89+
"""Called when suite starts. Default implementation does nothing.
90+
91+
Can return explicit ``False`` to stop visiting.
92+
"""
2593
pass
2694

2795
def end_suite(self, suite):
96+
"""Called when suite ends. Default implementation does nothing."""
2897
pass
2998

3099
def visit_test(self, test):
100+
"""Implements traversing through the test and its keywords.
101+
102+
Can be overridden to allow modifying the passed in ``test`` without
103+
calling :func:`start_test` or :func:`end_test` nor visiting keywords.
104+
"""
31105
if self.start_test(test) is not False:
32106
test.keywords.visit(self)
33107
self.end_test(test)
34108

35109
def start_test(self, test):
110+
"""Called when test starts. Default implementation does nothing.
111+
112+
Can return explicit ``False`` to stop visiting.
113+
"""
36114
pass
37115

38116
def end_test(self, test):
117+
"""Called when test ends. Default implementation does nothing."""
39118
pass
40119

41120
def visit_keyword(self, kw):
121+
"""Implements traversing through the keyword and its child keywords.
122+
123+
Can be overridden to allow modifying the passed in ``kw`` without
124+
calling :func:`start_keyword` or :func:`end_keyword` nor visiting
125+
child keywords.
126+
"""
42127
if self.start_keyword(kw) is not False:
43128
kw.keywords.visit(self)
44129
kw.messages.visit(self)
45130
self.end_keyword(kw)
46131

47132
def start_keyword(self, keyword):
133+
"""Called when keyword starts. Default implementation does nothing.
134+
135+
Can return explicit ``False`` to stop visiting.
136+
"""
48137
pass
49138

50139
def end_keyword(self, keyword):
140+
"""Called when keyword ends. Default implementation does nothing."""
51141
pass
52142

53143
def visit_message(self, msg):
144+
"""Implements visiting the message.
145+
146+
Can be overridden to allow modifying the passed in ``msg`` without
147+
calling :func:`start_message` or :func:`end_message`.
148+
"""
54149
if self.start_message(msg) is not False:
55150
self.end_message(msg)
56151

57152
def start_message(self, msg):
58-
pass
59-
60-
def end_message(self, msg):
61-
pass
62-
63-
64-
class SkipAllVisitor(SuiteVisitor):
65-
"""Travels suite and it's sub-suites without doing anything."""
66-
def visit_suite(self, suite):
67-
pass
153+
"""Called when message starts. Default implementation does nothing.
68154
69-
def visit_keyword(self, kw):
70-
pass
71-
72-
def visit_test(self, test):
155+
Can return explicit ``False`` to stop visiting.
156+
"""
73157
pass
74158

75-
def visit_message(self, msg):
159+
def end_message(self, msg):
160+
"""Called when message ends. Default implementation does nothing."""
76161
pass

src/robot/result/visitor.py

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,31 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Visitors can be used to easily travel test suites, test cases and keywords."""
15+
"""Visitors can be used to easily traverse result structures.
16+
17+
This module contains :class:`ResultVisitor` for traversing the whole
18+
:class:`~robot.result.executionresult.Result` object. It extends
19+
:class:`~robot.model.visitor.SuiteVisitor` that contains visiting logic
20+
for the test suite structure.
21+
"""
1622

1723
from robot.model import SuiteVisitor
1824

1925

2026
class ResultVisitor(SuiteVisitor):
21-
"""Abstract class to conveniently travel
22-
:class:`~robot.result.executionresult.Result` objects.
23-
24-
An implementation of visitor can be given to the visit method of result
25-
object. This will cause the result object to be traversed and the visitor
26-
object's ``visit_x``, ``start_x``, and ``end_x`` methods to be called for
27-
each test suite, test case, and keyword, as well as for errors, statistics,
28-
and other information in the result object. See methods below for a full
29-
list of available visitor methods.
30-
31-
The start and end method are called for each element and their child
32-
elements. The visitor implementation can override only those that it is
33-
interested in. If any of the ``start_x`` methods returns False for
34-
a certain element, its children are not visited.
35-
36-
If the visitor implements a ``visit_x`` method for element x, then the
37-
children of that element will not be visited, unless the visitor calls them
38-
explicitly. For example, if the visitor implements method :meth:`visit_test`,
39-
the :meth:`start_test`, :meth:`end_test`, :meth:`visit_keyword`,
40-
:meth:`start_keyword`, and :meth:`end_keyword` methods are not called for
41-
tests at all.
42-
43-
See the package documentation for :mod:`a usage example <robot.result>`.
44-
Visitors are also very widely used internally in Robot Framework. For
45-
an example, see the source code of :class:`robot.model.tagsetter.TagSetter`.
27+
"""Abstract class to conveniently travel :class:`~robot.result.executionresult.Result` objects.
28+
29+
A visitor implementation can be given to the :meth:`visit` method of a
30+
result object. This will cause the result object to be traversed and the
31+
visitor's :meth:`visit_x`, :meth:`start_x`, and :meth:`end_x` methods to
32+
be called for each suite, test, keyword and message, as well as for errors,
33+
statistics, and other information in the result object. See methods below
34+
for a full list of available visitor methods.
35+
36+
See the :mod:`result package level <robot.result>` documentation for
37+
more information about handling results and a concrete visitor example.
38+
For more information about the visitor algorithm see documentation in
39+
:mod:`robot.model.visitor` module.
4640
"""
4741
def visit_result(self, result):
4842
if self.start_result(result) is not False:

utest/api/test_exposed_api.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from os.path import abspath, join
44

5-
from robot import api, parsing, reporting, result, running
5+
from robot import api, model, parsing, reporting, result, running
66

77
from robot.utils.asserts import assert_equals
88

@@ -30,6 +30,10 @@ def test_test_suite(self):
3030
def test_result_writer(self):
3131
assert_equals(api.ResultWriter, reporting.ResultWriter)
3232

33+
def test_visitors(self):
34+
assert_equals(api.SuiteVisitor, model.SuiteVisitor)
35+
assert_equals(api.ResultVisitor, result.ResultVisitor)
36+
3337

3438
class TestTestSuiteBuilder(unittest.TestCase):
3539
misc = join(abspath(__file__), '..', '..', '..', 'atest', 'testdata', 'misc')

0 commit comments

Comments
 (0)