robotframework-pabot 4.0.6__tar.gz → 4.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/PKG-INFO +3 -2
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/README.md +58 -47
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/__init__.py +1 -1
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/arguments.py +2 -12
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/execution_items.py +19 -7
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/pabot.py +70 -82
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/PKG-INFO +3 -2
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/SOURCES.txt +1 -0
- robotframework_pabot-4.1.0/tests/test_depends.py +219 -0
- robotframework_pabot-4.1.0/tests/test_pabotprerunmodifier.py +212 -0
- robotframework_pabot-4.0.6/tests/test_depends.py +0 -145
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/LICENSE.txt +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/MANIFEST.in +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/pyproject.toml +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/setup.cfg +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/setup.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/SharedLibrary.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/clientwrapper.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/coordinatorwrapper.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/pabotlib.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/__init__.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/client.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/coordinator.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/messages.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/py3/worker.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/result_merger.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/robotremoteserver.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/pabot/workerwrapper.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/requires.txt +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_arguments_output.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_functional.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_ordering.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabot.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabotlib.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_pabotsuitenames_io.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_prerunmodifier.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_resultmerger.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_stacktrace.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_testlevelsplit_include.py +0 -0
- {robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_testlevelsplit_output_task_order.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: robotframework-pabot
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Parallel test runner for Robot Framework
|
|
5
5
|
Home-page: https://pabot.org
|
|
6
6
|
Download-URL: https://pypi.python.org/pypi/robotframework-pabot
|
|
@@ -20,5 +20,6 @@ License-File: LICENSE.txt
|
|
|
20
20
|
Requires-Dist: robotframework>=3.2
|
|
21
21
|
Requires-Dist: robotframework-stacktrace>=0.4.1
|
|
22
22
|
Requires-Dist: natsort>=8.2.0
|
|
23
|
+
Dynamic: download-url
|
|
23
24
|
|
|
24
25
|
A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.
|
|
@@ -53,45 +53,45 @@ There are several ways you can help in improving this tool:
|
|
|
53
53
|
- Contribute by programming and making a pull request (easiest way is to work on an issue from the issue tracker)
|
|
54
54
|
|
|
55
55
|
## Command-line options
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
<!-- START DOCSTRING -->
|
|
57
|
+
pabot [--verbose|--testlevelsplit|--command .. --end-command|
|
|
58
|
+
--processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
|
|
59
|
+
--processtimeout num|
|
|
60
|
+
--shard i/n|
|
|
61
|
+
--artifacts extensions|--artifactsinsubfolders|
|
|
62
|
+
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file
|
|
63
|
+
--chunk
|
|
64
|
+
--pabotprerunmodifier modifier]
|
|
65
|
+
[robot options] [path ...]
|
|
66
|
+
|
|
67
|
+
PabotLib remote server is started by default to enable locking and resource distribution between parallel test executions.
|
|
64
68
|
|
|
65
69
|
Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following pabot options:
|
|
66
70
|
|
|
67
71
|
--verbose
|
|
68
|
-
|
|
72
|
+
More output from the parallel execution.
|
|
69
73
|
|
|
70
74
|
--testlevelsplit
|
|
71
|
-
Split execution on test level instead of default suite level.
|
|
72
|
-
|
|
73
|
-
will only affect new suites and
|
|
74
|
-
Leaving this flag out when both suites and tests in
|
|
75
|
-
.pabotsuitenames file will also only affect new suites and
|
|
76
|
-
add them as suite files.
|
|
75
|
+
Split execution on test level instead of default suite level. If .pabotsuitenames contains both tests and suites then
|
|
76
|
+
this will only affect new suites and split only them. Leaving this flag out when both suites and tests in
|
|
77
|
+
.pabotsuitenames file will also only affect new suites and add them as suite files.
|
|
77
78
|
|
|
78
79
|
--command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
|
|
79
|
-
RF script for situations where robot is not used directly
|
|
80
|
-
|
|
81
|
-
--processes [NUMBER OF PROCESSES]
|
|
82
|
-
How many parallel executors to use (default max of 2 and cpu count).
|
|
83
|
-
Special option "all" will use as many processes as there are
|
|
84
|
-
executable suites or tests.
|
|
80
|
+
RF script for situations where robot is not used directly.
|
|
85
81
|
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
--processes [NUMBER OF PROCESSES]
|
|
83
|
+
How many parallel executors to use (default max of 2 and cpu count). Special option "all" will use as many processes as
|
|
84
|
+
there are executable suites or tests.
|
|
88
85
|
|
|
89
|
-
--no-pabotlib
|
|
86
|
+
--no-pabotlib
|
|
90
87
|
Disable the PabotLib remote server if you don't need locking or resource distribution features.
|
|
91
88
|
|
|
92
|
-
--pabotlibhost
|
|
93
|
-
Connect to an already running instance of the PabotLib remote server at the given host
|
|
94
|
-
|
|
89
|
+
--pabotlibhost [HOSTNAME]
|
|
90
|
+
Connect to an already running instance of the PabotLib remote server at the given host (disables the local PabotLib
|
|
91
|
+
server start). For example, to connect to a remote PabotLib server running on another machine:
|
|
92
|
+
|
|
93
|
+
pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
|
|
94
|
+
|
|
95
95
|
The remote server can be also started and executed separately from pabot instances:
|
|
96
96
|
|
|
97
97
|
python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
|
|
@@ -99,21 +99,20 @@ between parallel test executions.
|
|
|
99
99
|
|
|
100
100
|
This enables sharing a resource with multiple Robot Framework instances.
|
|
101
101
|
|
|
102
|
-
--pabotlibport
|
|
103
|
-
Port number of the PabotLib remote server (default is 8270)
|
|
104
|
-
See --pabotlibhost for more information
|
|
102
|
+
--pabotlibport [PORT]
|
|
103
|
+
Port number of the PabotLib remote server (default is 8270). See --pabotlibhost for more information.
|
|
105
104
|
|
|
106
|
-
--processtimeout
|
|
105
|
+
--processtimeout [TIMEOUT]
|
|
107
106
|
Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
|
|
108
107
|
|
|
109
|
-
--
|
|
110
|
-
|
|
108
|
+
--shard [INDEX]/[TOTAL]
|
|
109
|
+
Optionally split execution into smaller pieces. This can be used for distributing testing to multiple machines.
|
|
111
110
|
|
|
112
111
|
--artifacts [FILE EXTENSIONS]
|
|
113
|
-
List of file extensions (comma separated).
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
List of file extensions (comma separated). Defines which files (screenshots, videos etc.) from separate reporting
|
|
113
|
+
directories would be copied and included in a final report. Possible links to copied files in RF log would be updated
|
|
114
|
+
(only relative paths supported). The default value is `png`.
|
|
115
|
+
|
|
117
116
|
Examples:
|
|
118
117
|
|
|
119
118
|
--artifacts png,mp4,txt
|
|
@@ -121,22 +120,33 @@ between parallel test executions.
|
|
|
121
120
|
--artifactsinsubfolders
|
|
122
121
|
Copy artifacts located not only directly in the RF output dir, but also in it's sub-folders.
|
|
123
122
|
|
|
124
|
-
--
|
|
123
|
+
--resourcefile [FILEPATH]
|
|
124
|
+
Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
|
|
125
|
+
pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
|
|
126
|
+
|
|
127
|
+
--argumentfile [INTEGER] [FILEPATH]
|
|
125
128
|
Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
|
|
129
|
+
|
|
126
130
|
For example:
|
|
127
131
|
|
|
128
132
|
--argumentfile1 arg1.txt --argumentfile2 arg2.txt
|
|
129
133
|
|
|
130
|
-
--suitesfrom
|
|
131
|
-
Optionally read suites from output.xml file. Failed suites will run
|
|
132
|
-
|
|
134
|
+
--suitesfrom [FILEPATH TO OUTPUTXML]
|
|
135
|
+
Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
|
|
136
|
+
before shorter ones.
|
|
137
|
+
|
|
138
|
+
--ordering [FILE PATH]
|
|
139
|
+
Optionally give execution order from a file.
|
|
133
140
|
|
|
134
|
-
--
|
|
135
|
-
Optionally
|
|
136
|
-
|
|
141
|
+
--chunk
|
|
142
|
+
Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
|
|
143
|
+
setups and teardowns.
|
|
137
144
|
|
|
138
|
-
--
|
|
139
|
-
|
|
145
|
+
--pabotprerunmodifier [PRERUNMODIFIER MODULE OR CLASS]
|
|
146
|
+
Like Robot Framework's --prerunmodifier, but executed only once in the pabot's main process after all other
|
|
147
|
+
--prerunmodifiers. But unlike the regular --prerunmodifier command, --pabotprerunmodifier is not executed again in each
|
|
148
|
+
pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
|
|
149
|
+
example, to modify the list of tests to be performed.
|
|
140
150
|
|
|
141
151
|
Example usages:
|
|
142
152
|
|
|
@@ -149,6 +159,7 @@ Example usages:
|
|
|
149
159
|
# To disable PabotLib:
|
|
150
160
|
pabot --no-pabotlib tests
|
|
151
161
|
|
|
162
|
+
<!-- END DOCSTRING -->
|
|
152
163
|
### PabotLib
|
|
153
164
|
|
|
154
165
|
pabot.PabotLib provides keywords that will help communication and data sharing between the executor processes.
|
|
@@ -219,7 +230,7 @@ There different possibilities to influence the execution:
|
|
|
219
230
|
* If the base suite name is changing with robot option [```--name / -N```](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#setting-the-name) you can also give partial suite name without the base suite.
|
|
220
231
|
* You can add a line with text `#WAIT` to force executor to wait until all previous suites have been executed.
|
|
221
232
|
* You can group suites and tests together to same executor process by adding line `{` before the group and `}`after.
|
|
222
|
-
* You can introduce dependencies using the word `#DEPENDS` after a test declaration. Please take care that in case of circular dependencies an exception will be thrown. An example could be.
|
|
233
|
+
* You can introduce dependencies using the word `#DEPENDS` after a test declaration. Can be used several times if it is necessary to refer to several different tests. Please take care that in case of circular dependencies an exception will be thrown. An example could be.
|
|
223
234
|
|
|
224
235
|
```
|
|
225
236
|
--test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
|
|
@@ -106,8 +106,9 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
106
106
|
"resourcefile": str,
|
|
107
107
|
"pabotlibhost": str,
|
|
108
108
|
"pabotlibport": int,
|
|
109
|
+
"pabotprerunmodifier": str,
|
|
109
110
|
"processtimeout": int,
|
|
110
|
-
"ordering":
|
|
111
|
+
"ordering": str,
|
|
111
112
|
"suitesfrom": str,
|
|
112
113
|
"artifacts": lambda x: x.split(","),
|
|
113
114
|
"shard": _parse_shard,
|
|
@@ -195,17 +196,6 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
195
196
|
return remaining_args, pabot_args
|
|
196
197
|
|
|
197
198
|
|
|
198
|
-
def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
|
|
199
|
-
try:
|
|
200
|
-
with open(filename, "r") as orderingfile:
|
|
201
|
-
return [
|
|
202
|
-
parse_execution_item_line(line.strip())
|
|
203
|
-
for line in orderingfile.readlines()
|
|
204
|
-
]
|
|
205
|
-
except:
|
|
206
|
-
raise DataError("Error parsing ordering file '%s'" % filename)
|
|
207
|
-
|
|
208
|
-
|
|
209
199
|
def _delete_none_keys(d): # type: (Dict[str, Optional[object]]) -> Dict[str, object]
|
|
210
200
|
keys = set()
|
|
211
201
|
for k in d:
|
|
@@ -5,6 +5,7 @@ from robot import __version__ as ROBOT_VERSION
|
|
|
5
5
|
from robot.errors import DataError
|
|
6
6
|
from robot.utils import PY2, is_unicode
|
|
7
7
|
|
|
8
|
+
import re
|
|
8
9
|
|
|
9
10
|
@total_ordering
|
|
10
11
|
class ExecutionItem(object):
|
|
@@ -97,20 +98,31 @@ class GroupItem(ExecutionItem):
|
|
|
97
98
|
class RunnableItem(ExecutionItem):
|
|
98
99
|
pass
|
|
99
100
|
|
|
100
|
-
depends = None # type: str
|
|
101
|
+
depends = None # type: List[str]
|
|
101
102
|
depends_keyword = "#DEPENDS"
|
|
102
103
|
|
|
104
|
+
def _split_dependencies(self, line_name, depends_indexes):
|
|
105
|
+
depends_lst = [] if len(depends_indexes) < 2 else [line_name[i + len(self.depends_keyword) : j].strip() for i, j in zip(depends_indexes, depends_indexes[1:])]
|
|
106
|
+
depends_lst.append(line_name[depends_indexes[-1] + len(self.depends_keyword) : ].strip())
|
|
107
|
+
return depends_lst
|
|
108
|
+
|
|
109
|
+
def _merge_dependencies(self, line_start):
|
|
110
|
+
output_line = line_start
|
|
111
|
+
for d in self.depends:
|
|
112
|
+
output_line = output_line + " " + self.depends_keyword + " " + d
|
|
113
|
+
return output_line
|
|
114
|
+
|
|
103
115
|
def set_name_and_depends(self, name):
|
|
104
116
|
line_name = name.encode("utf-8") if PY2 and is_unicode(name) else name
|
|
105
|
-
|
|
117
|
+
depends_indexes = [d.start() for d in re.finditer(self.depends_keyword, line_name)]
|
|
106
118
|
self.name = (
|
|
107
119
|
line_name
|
|
108
|
-
if
|
|
109
|
-
else line_name[0:
|
|
120
|
+
if len(depends_indexes) == 0
|
|
121
|
+
else line_name[0:depends_indexes[0]].strip()
|
|
110
122
|
)
|
|
111
123
|
self.depends = (
|
|
112
|
-
|
|
113
|
-
if
|
|
124
|
+
self._split_dependencies(line_name, depends_indexes)
|
|
125
|
+
if len(depends_indexes) != 0
|
|
114
126
|
else None
|
|
115
127
|
)
|
|
116
128
|
|
|
@@ -118,7 +130,7 @@ class RunnableItem(ExecutionItem):
|
|
|
118
130
|
# type: () -> str
|
|
119
131
|
line_without_depends = "--" + self.type + " " + self.name
|
|
120
132
|
return (
|
|
121
|
-
|
|
133
|
+
self._merge_dependencies(line_without_depends)
|
|
122
134
|
if self.depends
|
|
123
135
|
else line_without_depends
|
|
124
136
|
)
|
|
@@ -15,73 +15,12 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
#
|
|
17
17
|
# partly based on work by Nokia Solutions and Networks Oyj
|
|
18
|
+
|
|
19
|
+
# Help documentation from README.md:
|
|
18
20
|
"""A parallel executor for Robot Framework test cases.
|
|
19
21
|
Version [PABOT_VERSION]
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
options (these must be before normal RF options):
|
|
23
|
-
|
|
24
|
-
--verbose
|
|
25
|
-
more output
|
|
26
|
-
|
|
27
|
-
--command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
|
|
28
|
-
RF script for situations where pybot is not used directly
|
|
29
|
-
|
|
30
|
-
--processes [NUMBER OF PROCESSES]
|
|
31
|
-
How many parallel executors to use (default max of 2 and cpu count).
|
|
32
|
-
Special option "all" will use as many processes as there are
|
|
33
|
-
executable suites or tests.
|
|
34
|
-
|
|
35
|
-
--testlevelsplit
|
|
36
|
-
Split execution on test level instead of default suite level.
|
|
37
|
-
If .pabotsuitenames contains both tests and suites then this
|
|
38
|
-
will only affect new suites and split only them.
|
|
39
|
-
Leaving this flag out when both suites and tests in
|
|
40
|
-
.pabotsuitenames file will also only affect new suites and
|
|
41
|
-
add them as suite files.
|
|
42
|
-
|
|
43
|
-
--resourcefile [FILEPATH]
|
|
44
|
-
Indicator for a file that can contain shared variables for
|
|
45
|
-
distributing resources.
|
|
46
|
-
|
|
47
|
-
--no-pabotlib
|
|
48
|
-
Disable the PabotLib remote server if you don't need locking or resource distribution features.
|
|
49
|
-
PabotLib remote server is started by default.
|
|
50
|
-
|
|
51
|
-
--pabotlibhost [HOSTNAME]
|
|
52
|
-
Connect to an already running instance of the PabotLib remote server at the given host
|
|
53
|
-
(disables the local PabotLib server start). For example, to connect to a
|
|
54
|
-
remote PabotLib server running on another machine:
|
|
55
|
-
|
|
56
|
-
pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
|
|
57
|
-
|
|
58
|
-
The remote PabotLib server can be started separately using:
|
|
59
|
-
python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
|
|
60
|
-
python -m pabot.pabotlib resource.txt 192.168.1.123 8271
|
|
61
|
-
|
|
62
|
-
--pabotlibport [PORT]
|
|
63
|
-
Port number of the PabotLib remote server (default is 8270)
|
|
64
|
-
|
|
65
|
-
--processtimeout [TIMEOUT]
|
|
66
|
-
Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
|
|
67
|
-
|
|
68
|
-
--ordering [FILE PATH]
|
|
69
|
-
Optionally give execution order from a file.
|
|
70
|
-
|
|
71
|
-
--suitesfrom [FILEPATH TO OUTPUTXML]
|
|
72
|
-
Optionally read suites from output.xml file. Failed suites will run
|
|
73
|
-
first and longer running ones will be executed before shorter ones.
|
|
74
|
-
|
|
75
|
-
--argumentfile[INTEGER] [FILEPATH]
|
|
76
|
-
Run same suite with multiple argumentfile options.
|
|
77
|
-
For example "--argumentfile1 arg1.txt --argumentfile2 arg2.txt".
|
|
78
|
-
|
|
79
|
-
--shard [SHARD]/[SHARD COUNT]
|
|
80
|
-
Optionally split execution into smaller pieces. This can
|
|
81
|
-
be used for distributing testing to multiple machines.
|
|
82
|
-
|
|
83
|
-
--chunk
|
|
84
|
-
Optionally chunk tests to PROCESSES number of robot runs.
|
|
23
|
+
PLACEHOLDER_README.MD
|
|
85
24
|
|
|
86
25
|
Copyright 2022 Mikko Korpela - Apache 2 License
|
|
87
26
|
"""
|
|
@@ -184,6 +123,27 @@ _ROBOT_EXTENSIONS = [
|
|
|
184
123
|
_ALL_ELAPSED = [] # type: List[Union[int, float]]
|
|
185
124
|
|
|
186
125
|
|
|
126
|
+
def extract_section(filename, start_marker, end_marker):
|
|
127
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
128
|
+
lines = f.readlines()
|
|
129
|
+
|
|
130
|
+
inside_section = False
|
|
131
|
+
extracted_lines = []
|
|
132
|
+
|
|
133
|
+
for line in lines:
|
|
134
|
+
if start_marker in line:
|
|
135
|
+
inside_section = True
|
|
136
|
+
continue
|
|
137
|
+
if end_marker in line:
|
|
138
|
+
inside_section = False
|
|
139
|
+
break
|
|
140
|
+
if inside_section:
|
|
141
|
+
# Add line from README.md without [] and (https: address)
|
|
142
|
+
extracted_lines.append(re.sub(r'\[([^\]]+)\]\(https?://[^\)]+\)', r'\1', line))
|
|
143
|
+
|
|
144
|
+
return "".join(extracted_lines).strip()
|
|
145
|
+
|
|
146
|
+
|
|
187
147
|
class Color:
|
|
188
148
|
SUPPORTED_OSES = ["posix"]
|
|
189
149
|
|
|
@@ -880,6 +840,8 @@ def solve_shard_suites(suite_names, pabot_args):
|
|
|
880
840
|
|
|
881
841
|
|
|
882
842
|
def solve_suite_names(outs_dir, datasources, options, pabot_args):
|
|
843
|
+
if pabot_args.get("pabotprerunmodifier"):
|
|
844
|
+
options['prerunmodifier'].append(pabot_args['pabotprerunmodifier'])
|
|
883
845
|
h = Hashes(
|
|
884
846
|
dirs=get_hash_of_dirs(datasources),
|
|
885
847
|
cmd=get_hash_of_command(options, pabot_args),
|
|
@@ -915,7 +877,7 @@ def solve_suite_names(outs_dir, datasources, options, pabot_args):
|
|
|
915
877
|
for l in lines[4:]
|
|
916
878
|
)
|
|
917
879
|
execution_item_lines = [parse_execution_item_line(l) for l in lines[4:]]
|
|
918
|
-
if corrupted or h != file_h or file_hash != hash_of_file:
|
|
880
|
+
if corrupted or h != file_h or file_hash != hash_of_file or pabot_args.get("pabotprerunmodifier"):
|
|
919
881
|
return _regenerate(
|
|
920
882
|
file_h,
|
|
921
883
|
h,
|
|
@@ -1949,7 +1911,11 @@ def main_program(args):
|
|
|
1949
1911
|
_start_message_writer()
|
|
1950
1912
|
options, datasources, pabot_args, opts_for_run = parse_args(args)
|
|
1951
1913
|
if pabot_args["help"]:
|
|
1952
|
-
|
|
1914
|
+
help_print = __doc__.replace(
|
|
1915
|
+
"PLACEHOLDER_README.MD",
|
|
1916
|
+
extract_section("README.md", "<!-- START DOCSTRING -->", "<!-- END DOCSTRING -->")
|
|
1917
|
+
)
|
|
1918
|
+
print(help_print.replace("[PABOT_VERSION]", PABOT_VERSION))
|
|
1953
1919
|
return 0
|
|
1954
1920
|
if len(datasources) == 0:
|
|
1955
1921
|
print("[ " + _wrap_with(Color.RED, "ERROR") + " ]: No datasources given.")
|
|
@@ -1988,7 +1954,8 @@ def main_program(args):
|
|
|
1988
1954
|
)
|
|
1989
1955
|
return result_code if not _ABNORMAL_EXIT_HAPPENED else 252
|
|
1990
1956
|
except Information as i:
|
|
1991
|
-
|
|
1957
|
+
version_print = __doc__.replace("\nPLACEHOLDER_README.MD\n", "")
|
|
1958
|
+
print(version_print.replace("[PABOT_VERSION]", PABOT_VERSION))
|
|
1992
1959
|
print(i.message)
|
|
1993
1960
|
except DataError as err:
|
|
1994
1961
|
print(err.message)
|
|
@@ -2010,10 +1977,24 @@ def main_program(args):
|
|
|
2010
1977
|
_stop_message_writer()
|
|
2011
1978
|
|
|
2012
1979
|
|
|
1980
|
+
def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
|
|
1981
|
+
try:
|
|
1982
|
+
with open(filename, "r") as orderingfile:
|
|
1983
|
+
return [
|
|
1984
|
+
parse_execution_item_line(line.strip())
|
|
1985
|
+
for line in orderingfile.readlines()
|
|
1986
|
+
]
|
|
1987
|
+
except FileNotFoundError:
|
|
1988
|
+
raise DataError("Error: File '%s' not found." % filename)
|
|
1989
|
+
except:
|
|
1990
|
+
raise DataError("Error parsing ordering file '%s'" % filename)
|
|
1991
|
+
|
|
1992
|
+
|
|
2013
1993
|
def _group_suites(outs_dir, datasources, options, pabot_args):
|
|
2014
1994
|
suite_names = solve_suite_names(outs_dir, datasources, options, pabot_args)
|
|
2015
1995
|
_verify_depends(suite_names)
|
|
2016
|
-
|
|
1996
|
+
ordering_arg = _parse_ordering(pabot_args.get("ordering")) if (pabot_args.get("ordering")) is not None else None
|
|
1997
|
+
ordered_suites = _preserve_order(suite_names, ordering_arg)
|
|
2017
1998
|
shard_suites = solve_shard_suites(ordered_suites, pabot_args)
|
|
2018
1999
|
grouped_suites = (
|
|
2019
2000
|
_chunked_suite_names(shard_suites, pabot_args["processes"])
|
|
@@ -2083,20 +2064,27 @@ def _group_by_depend(suite_names):
|
|
|
2083
2064
|
return [suite_names]
|
|
2084
2065
|
independent_tests = list(filter(lambda suite: not suite.depends, runnable_suites))
|
|
2085
2066
|
dependency_tree = [independent_tests]
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2067
|
+
dependent_tests = list(filter(lambda suite: suite.depends, runnable_suites))
|
|
2068
|
+
unknown_dependent_tests = dependent_tests
|
|
2069
|
+
while len(unknown_dependent_tests) > 0:
|
|
2070
|
+
run_in_this_stage, run_later = [], []
|
|
2071
|
+
for d in unknown_dependent_tests:
|
|
2072
|
+
stage_indexes = []
|
|
2073
|
+
for i, stage in enumerate(dependency_tree):
|
|
2074
|
+
for test in stage:
|
|
2075
|
+
if test.name in d.depends:
|
|
2076
|
+
stage_indexes.append(i)
|
|
2077
|
+
# All #DEPENDS test are already run:
|
|
2078
|
+
if len(stage_indexes) == len(d.depends):
|
|
2079
|
+
run_in_this_stage.append(d)
|
|
2080
|
+
else:
|
|
2081
|
+
run_later.append(d)
|
|
2082
|
+
unknown_dependent_tests = run_later
|
|
2083
|
+
if len(run_in_this_stage) == 0:
|
|
2084
|
+
text = "There are circular or unmet dependencies using #DEPENDS. Check this/these test(s): " + str(run_later)
|
|
2085
|
+
raise DataError(text)
|
|
2086
|
+
else:
|
|
2087
|
+
dependency_tree.append(run_in_this_stage)
|
|
2100
2088
|
flattened_dependency_tree = sum(dependency_tree, [])
|
|
2101
2089
|
if len(flattened_dependency_tree) != len(runnable_suites):
|
|
2102
2090
|
raise DataError(
|
{robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/src/robotframework_pabot.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: robotframework-pabot
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Parallel test runner for Robot Framework
|
|
5
5
|
Home-page: https://pabot.org
|
|
6
6
|
Download-URL: https://pypi.python.org/pypi/robotframework-pabot
|
|
@@ -20,5 +20,6 @@ License-File: LICENSE.txt
|
|
|
20
20
|
Requires-Dist: robotframework>=3.2
|
|
21
21
|
Requires-Dist: robotframework-stacktrace>=0.4.1
|
|
22
22
|
Requires-Dist: natsort>=8.2.0
|
|
23
|
+
Dynamic: download-url
|
|
23
24
|
|
|
24
25
|
A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import textwrap
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _string_convert(byte_string):
|
|
10
|
+
legacy_python = sys.version_info < (3, 0)
|
|
11
|
+
return byte_string.decode() if legacy_python else byte_string
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DependsTest(unittest.TestCase):
|
|
15
|
+
test_file = """
|
|
16
|
+
*** Settings ***
|
|
17
|
+
Test Template Test1
|
|
18
|
+
*** Test Cases ***
|
|
19
|
+
The Test S1Test 01 1
|
|
20
|
+
The Test S1Test 02 1
|
|
21
|
+
The Test S1Test 03 1
|
|
22
|
+
The Test S1Test 04 1
|
|
23
|
+
The Test S1Test 05 1
|
|
24
|
+
The Test S1Test 06 1
|
|
25
|
+
The Test S1Test 07 1
|
|
26
|
+
The Test S1Test 08 1
|
|
27
|
+
The Test S1Test 09 1
|
|
28
|
+
The Test S1Test 10 1
|
|
29
|
+
The Test S1Test 11 1
|
|
30
|
+
The Test S1Test 12 1
|
|
31
|
+
*** Keywords ***
|
|
32
|
+
Test1
|
|
33
|
+
[Arguments] ${arg}
|
|
34
|
+
Log Test
|
|
35
|
+
"""
|
|
36
|
+
passed = _string_convert(b"PASSED")
|
|
37
|
+
failed = _string_convert(b"FAILED")
|
|
38
|
+
test_01 = _string_convert(b"S1Test 01")
|
|
39
|
+
test_02 = _string_convert(b"S1Test 02")
|
|
40
|
+
test_03 = _string_convert(b"S1Test 03")
|
|
41
|
+
test_04 = _string_convert(b"S1Test 04")
|
|
42
|
+
test_05 = _string_convert(b"S1Test 05")
|
|
43
|
+
test_06 = _string_convert(b"S1Test 06")
|
|
44
|
+
test_07 = _string_convert(b"S1Test 07")
|
|
45
|
+
test_08 = _string_convert(b"S1Test 08")
|
|
46
|
+
test_09 = _string_convert(b"S1Test 09")
|
|
47
|
+
test_10 = _string_convert(b"S1Test 10")
|
|
48
|
+
test_11 = _string_convert(b"S1Test 11")
|
|
49
|
+
test_12 = _string_convert(b"S1Test 12")
|
|
50
|
+
|
|
51
|
+
def setUp(self):
|
|
52
|
+
self.tmpdir = tempfile.mkdtemp()
|
|
53
|
+
|
|
54
|
+
def tearDown(self):
|
|
55
|
+
shutil.rmtree(self.tmpdir)
|
|
56
|
+
|
|
57
|
+
def _run_tests_with(self, testfile, orderfile):
|
|
58
|
+
robot_file = open("{}/test.robot".format(self.tmpdir), "w")
|
|
59
|
+
robot_file.write(textwrap.dedent(testfile))
|
|
60
|
+
robot_file.close()
|
|
61
|
+
with open("{}/order.dat".format(self.tmpdir), "w") as f:
|
|
62
|
+
f.write(textwrap.dedent(orderfile))
|
|
63
|
+
process = subprocess.Popen(
|
|
64
|
+
[
|
|
65
|
+
sys.executable,
|
|
66
|
+
"-m" "pabot.pabot",
|
|
67
|
+
"--testlevelsplit",
|
|
68
|
+
"--ordering",
|
|
69
|
+
"{}/order.dat".format(self.tmpdir),
|
|
70
|
+
"{}/test.robot".format(self.tmpdir),
|
|
71
|
+
],
|
|
72
|
+
cwd=self.tmpdir,
|
|
73
|
+
stdout=subprocess.PIPE,
|
|
74
|
+
stderr=subprocess.PIPE,
|
|
75
|
+
)
|
|
76
|
+
return process.communicate()
|
|
77
|
+
|
|
78
|
+
def test_dependency_ok(self):
|
|
79
|
+
stdout, stderr = self._run_tests_with(
|
|
80
|
+
self.test_file,
|
|
81
|
+
"""
|
|
82
|
+
--test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
|
|
83
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
|
|
84
|
+
--test Test.The Test S1Test 08
|
|
85
|
+
""",
|
|
86
|
+
)
|
|
87
|
+
self.assertIn(self.passed, stdout, stderr)
|
|
88
|
+
self.assertNotIn(self.failed, stdout, stderr)
|
|
89
|
+
self.assertEqual(stdout.count(self.passed), 12)
|
|
90
|
+
test_01_index = stdout.find(self.test_01)
|
|
91
|
+
test_02_index = stdout.find(self.test_02)
|
|
92
|
+
test_08_index = stdout.find(self.test_08)
|
|
93
|
+
self.assertNotEqual(test_01_index, -1)
|
|
94
|
+
self.assertNotEqual(test_02_index, -1)
|
|
95
|
+
self.assertNotEqual(test_08_index, -1)
|
|
96
|
+
self.assertTrue(test_08_index < test_02_index)
|
|
97
|
+
self.assertTrue(test_02_index < test_01_index)
|
|
98
|
+
|
|
99
|
+
def test_multiple_dependencies_ok(self):
|
|
100
|
+
stdout, stderr = self._run_tests_with(
|
|
101
|
+
self.test_file,
|
|
102
|
+
"""
|
|
103
|
+
--test Test.The Test S1Test 01
|
|
104
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
|
|
105
|
+
--test Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 02
|
|
106
|
+
--test Test.The Test S1Test 04
|
|
107
|
+
--test Test.The Test S1Test 05 #DEPENDS Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 04
|
|
108
|
+
--test Test.The Test S1Test 06 #DEPENDS Test.The Test S1Test 05
|
|
109
|
+
--test Test.The Test S1Test 07
|
|
110
|
+
--test Test.The Test S1Test 08
|
|
111
|
+
--test Test.The Test S1Test 09 #DEPENDS Test.The Test S1Test 07 #DEPENDS Test.The Test S1Test 08 #DEPENDS Test.The Test S1Test 06
|
|
112
|
+
--test Test.The Test S1Test 10 #DEPENDS Test.The Test S1Test 12
|
|
113
|
+
--test Test.The Test S1Test 11 #DEPENDS Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 06 #DEPENDS Test.The Test S1Test 10 #DEPENDS Test.The Test S1Test 09
|
|
114
|
+
--test Test.The Test S1Test 12
|
|
115
|
+
""",
|
|
116
|
+
)
|
|
117
|
+
self.assertIn(self.passed, stdout, stderr)
|
|
118
|
+
self.assertNotIn(self.failed, stdout, stderr)
|
|
119
|
+
self.assertEqual(stdout.count(self.passed), 12)
|
|
120
|
+
test_01_index = stdout.find(self.test_01)
|
|
121
|
+
test_02_index = stdout.find(self.test_02)
|
|
122
|
+
test_03_index = stdout.find(self.test_03)
|
|
123
|
+
test_04_index = stdout.find(self.test_04)
|
|
124
|
+
test_05_index = stdout.find(self.test_05)
|
|
125
|
+
test_06_index = stdout.find(self.test_06)
|
|
126
|
+
test_07_index = stdout.find(self.test_07)
|
|
127
|
+
test_08_index = stdout.find(self.test_08)
|
|
128
|
+
test_09_index = stdout.find(self.test_09)
|
|
129
|
+
test_10_index = stdout.find(self.test_10)
|
|
130
|
+
test_11_index = stdout.find(self.test_11)
|
|
131
|
+
test_12_index = stdout.find(self.test_12)
|
|
132
|
+
self.assertNotEqual(test_01_index, -1)
|
|
133
|
+
self.assertNotEqual(test_02_index, -1)
|
|
134
|
+
self.assertNotEqual(test_03_index, -1)
|
|
135
|
+
self.assertNotEqual(test_04_index, -1)
|
|
136
|
+
self.assertNotEqual(test_05_index, -1)
|
|
137
|
+
self.assertNotEqual(test_06_index, -1)
|
|
138
|
+
self.assertNotEqual(test_07_index, -1)
|
|
139
|
+
self.assertNotEqual(test_08_index, -1)
|
|
140
|
+
self.assertNotEqual(test_09_index, -1)
|
|
141
|
+
self.assertNotEqual(test_10_index, -1)
|
|
142
|
+
self.assertNotEqual(test_11_index, -1)
|
|
143
|
+
self.assertNotEqual(test_12_index, -1)
|
|
144
|
+
self.assertTrue(test_02_index > test_01_index)
|
|
145
|
+
self.assertTrue(test_03_index > test_02_index)
|
|
146
|
+
self.assertTrue(test_05_index > test_01_index)
|
|
147
|
+
self.assertTrue(test_05_index > test_04_index)
|
|
148
|
+
self.assertTrue(test_06_index > test_05_index)
|
|
149
|
+
self.assertTrue(test_09_index > test_07_index)
|
|
150
|
+
self.assertTrue(test_09_index > test_08_index)
|
|
151
|
+
self.assertTrue(test_09_index > test_06_index)
|
|
152
|
+
self.assertTrue(test_10_index > test_12_index)
|
|
153
|
+
self.assertTrue(test_11_index > test_03_index)
|
|
154
|
+
self.assertTrue(test_11_index > test_06_index)
|
|
155
|
+
self.assertTrue(test_11_index > test_10_index)
|
|
156
|
+
self.assertTrue(test_11_index > test_09_index)
|
|
157
|
+
|
|
158
|
+
def test_circular_dependency(self):
|
|
159
|
+
stdout, stderr = self._run_tests_with(
|
|
160
|
+
self.test_file,
|
|
161
|
+
"""
|
|
162
|
+
--test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
|
|
163
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
|
|
164
|
+
--test Test.The Test S1Test 08
|
|
165
|
+
""",
|
|
166
|
+
)
|
|
167
|
+
self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 01>, <test:Test.The Test S1Test 02>]", stdout)
|
|
168
|
+
self.assertEqual(b"", stderr)
|
|
169
|
+
|
|
170
|
+
def test_circular_dependency_with_multiple_depends(self):
|
|
171
|
+
stdout, stderr = self._run_tests_with(
|
|
172
|
+
self.test_file,
|
|
173
|
+
"""
|
|
174
|
+
--test Test.The Test S1Test 01
|
|
175
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
|
|
176
|
+
--test Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 01
|
|
177
|
+
--test Test.The Test S1Test 08 #DEPENDS Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 03 #DEPENDS Test.The Test S1Test 09
|
|
178
|
+
--test Test.The Test S1Test 09 #DEPENDS Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 08
|
|
179
|
+
""",
|
|
180
|
+
)
|
|
181
|
+
self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 08>, <test:Test.The Test S1Test 09>]", stdout)
|
|
182
|
+
self.assertEqual(b"", stderr)
|
|
183
|
+
|
|
184
|
+
def test_unmet_dependency(self):
|
|
185
|
+
stdout, stderr = self._run_tests_with(
|
|
186
|
+
self.test_file,
|
|
187
|
+
"""
|
|
188
|
+
--test Test.The Test S1Test 01
|
|
189
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 23
|
|
190
|
+
--test Test.The Test S1Test 08
|
|
191
|
+
""",
|
|
192
|
+
)
|
|
193
|
+
self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
|
|
194
|
+
self.assertEqual(b"", stderr)
|
|
195
|
+
|
|
196
|
+
def test_same_reference(self):
|
|
197
|
+
stdout, stderr = self._run_tests_with(
|
|
198
|
+
self.test_file,
|
|
199
|
+
"""
|
|
200
|
+
--test Test.The Test S1Test 01
|
|
201
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 02
|
|
202
|
+
--test Test.The Test S1Test 08
|
|
203
|
+
""",
|
|
204
|
+
)
|
|
205
|
+
self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
|
|
206
|
+
self.assertEqual(b"", stderr)
|
|
207
|
+
|
|
208
|
+
def test_wait(self):
|
|
209
|
+
stdout, stderr = self._run_tests_with(
|
|
210
|
+
self.test_file,
|
|
211
|
+
"""
|
|
212
|
+
--test Test.The Test S1Test 01
|
|
213
|
+
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
|
|
214
|
+
#WAIT
|
|
215
|
+
--test Test.The Test S1Test 08
|
|
216
|
+
""",
|
|
217
|
+
)
|
|
218
|
+
self.assertIn(b"circular or unmet dependencies using #DEPENDS. Check this/these test(s): [<test:Test.The Test S1Test 02>]", stdout)
|
|
219
|
+
self.assertEqual(b"", stderr)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import tempfile
|
|
3
|
+
import textwrap
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import robot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_robot_version_and_return_name():
|
|
13
|
+
version = robot.__version__
|
|
14
|
+
major_version = int(version.split('.')[0])
|
|
15
|
+
|
|
16
|
+
if major_version >= 7:
|
|
17
|
+
return "full_name"
|
|
18
|
+
else:
|
|
19
|
+
return "longname"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PabotPrerunModifierTests(unittest.TestCase):
|
|
23
|
+
@classmethod
|
|
24
|
+
def setUpClass(self):
|
|
25
|
+
self.tmpdir = tempfile.mkdtemp()
|
|
26
|
+
self.tmpdir_name = (re.sub(r'(\d)([a-z])', lambda m: m.group(1) + m.group(2).upper(),
|
|
27
|
+
" ".join(word.capitalize().strip() for word in os.path.basename(self.tmpdir).split("_")))).strip()
|
|
28
|
+
|
|
29
|
+
# robot case file 1
|
|
30
|
+
self.robot_file_path_1 = f'{self.tmpdir}/test_1.robot'
|
|
31
|
+
with open(self.robot_file_path_1, 'w') as robot_file:
|
|
32
|
+
robot_file.write(
|
|
33
|
+
textwrap.dedent("""
|
|
34
|
+
*** Test Cases ***
|
|
35
|
+
Testing 1
|
|
36
|
+
[Tags] tag test3
|
|
37
|
+
Log hello
|
|
38
|
+
|
|
39
|
+
Testing 2
|
|
40
|
+
[Tags] tag test test2 test3
|
|
41
|
+
Log world
|
|
42
|
+
|
|
43
|
+
Testing 3
|
|
44
|
+
[Tags] tag
|
|
45
|
+
Log world
|
|
46
|
+
"""))
|
|
47
|
+
|
|
48
|
+
# robot case file 2
|
|
49
|
+
self.robot_file_path_2 = f'{self.tmpdir}/test_2.robot'
|
|
50
|
+
with open(self.robot_file_path_2, 'w') as robot_file:
|
|
51
|
+
robot_file.write(
|
|
52
|
+
textwrap.dedent("""
|
|
53
|
+
*** Test Cases ***
|
|
54
|
+
Testing 4
|
|
55
|
+
[Tags] tag
|
|
56
|
+
Log hello
|
|
57
|
+
|
|
58
|
+
Testing 5
|
|
59
|
+
[Tags] tag test2 test3
|
|
60
|
+
Log world
|
|
61
|
+
|
|
62
|
+
Testing 6
|
|
63
|
+
[Tags] tag
|
|
64
|
+
Log world
|
|
65
|
+
"""))
|
|
66
|
+
|
|
67
|
+
# pabotprerunmodifier script. Works like Robot Framework --include command
|
|
68
|
+
self.modifier_file_path = f'{self.tmpdir}/Modifier.py'
|
|
69
|
+
correct_full_name_call = check_robot_version_and_return_name()
|
|
70
|
+
with open(self.modifier_file_path, 'w') as modifier_file:
|
|
71
|
+
modifier_file.write(
|
|
72
|
+
textwrap.dedent(f"""
|
|
73
|
+
from robot.api import SuiteVisitor
|
|
74
|
+
|
|
75
|
+
class Modifier(SuiteVisitor):
|
|
76
|
+
def __init__(self, tag):
|
|
77
|
+
self.list_of_test_names = []
|
|
78
|
+
self.tag = tag
|
|
79
|
+
|
|
80
|
+
def start_suite(self, suite):
|
|
81
|
+
if suite.tests:
|
|
82
|
+
for test in suite.tests:
|
|
83
|
+
if self.tag in test.tags:
|
|
84
|
+
self.list_of_test_names.append(test.{correct_full_name_call})
|
|
85
|
+
|
|
86
|
+
def end_suite(self, suite):
|
|
87
|
+
suite.tests = [t for t in suite.tests if t.{correct_full_name_call} in self.list_of_test_names]
|
|
88
|
+
suite.suites = [s for s in suite.suites if s.test_count > 0]
|
|
89
|
+
"""))
|
|
90
|
+
|
|
91
|
+
# prerunmodifier script
|
|
92
|
+
self.modifier2_file_path = f'{self.tmpdir}/Modifier2.py'
|
|
93
|
+
with open(self.modifier2_file_path, 'w') as modifier_file:
|
|
94
|
+
modifier_file.write(
|
|
95
|
+
textwrap.dedent("""
|
|
96
|
+
from robot.api import SuiteVisitor
|
|
97
|
+
|
|
98
|
+
class Modifier2(SuiteVisitor):
|
|
99
|
+
def start_suite(self, suite):
|
|
100
|
+
if suite.tests:
|
|
101
|
+
for test in suite.tests:
|
|
102
|
+
if '2' in test.name:
|
|
103
|
+
test.name = 'new-name-2'
|
|
104
|
+
test.tags.add(['tag2'])
|
|
105
|
+
"""))
|
|
106
|
+
|
|
107
|
+
# ordering file
|
|
108
|
+
self.ordering_file_path = f'{self.tmpdir}/ordering.txt'
|
|
109
|
+
with open(self.ordering_file_path, 'w') as ordering_file:
|
|
110
|
+
ordering_file.write(
|
|
111
|
+
textwrap.dedent(f"""
|
|
112
|
+
{{
|
|
113
|
+
--test {self.tmpdir_name}.Test 1.Testing 2
|
|
114
|
+
--test {self.tmpdir_name}.Test 2.Testing 5
|
|
115
|
+
}}
|
|
116
|
+
--test {self.tmpdir_name}.Test 1.Testing 1
|
|
117
|
+
"""))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_pabotprerunmodifier(self):
|
|
121
|
+
process = subprocess.Popen(
|
|
122
|
+
[
|
|
123
|
+
sys.executable,
|
|
124
|
+
"-m", "pabot.pabot",
|
|
125
|
+
"--pabotprerunmodifier",
|
|
126
|
+
self.modifier_file_path + ":test",
|
|
127
|
+
self.tmpdir
|
|
128
|
+
],
|
|
129
|
+
cwd=self.tmpdir,
|
|
130
|
+
stdout=subprocess.PIPE,
|
|
131
|
+
stderr=subprocess.PIPE
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
stdout, stderr = process.communicate()
|
|
135
|
+
# without testlevelsplit argument whole test suite 1 will be executed.
|
|
136
|
+
self.assertIn(f'PASSED {self.tmpdir_name}.Test 1'.encode('utf-8'), stdout)
|
|
137
|
+
self.assertIn(b'3 tests, 3 passed, 0 failed, 0 skipped.', stdout)
|
|
138
|
+
self.assertEqual(b"", stderr)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_pabotprerunmodifier_with_testlevelsplit(self):
|
|
142
|
+
process = subprocess.Popen(
|
|
143
|
+
[
|
|
144
|
+
sys.executable,
|
|
145
|
+
"-m", "pabot.pabot",
|
|
146
|
+
"--testlevelsplit",
|
|
147
|
+
"--pabotprerunmodifier",
|
|
148
|
+
self.modifier_file_path + ":test2",
|
|
149
|
+
self.tmpdir
|
|
150
|
+
],
|
|
151
|
+
cwd=self.tmpdir,
|
|
152
|
+
stdout=subprocess.PIPE,
|
|
153
|
+
stderr=subprocess.PIPE
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
stdout, stderr = process.communicate()
|
|
157
|
+
self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.Testing 2'.encode('utf-8'), stdout)
|
|
158
|
+
self.assertIn(f'PASSED {self.tmpdir_name}.Test 2.Testing 5'.encode('utf-8'), stdout)
|
|
159
|
+
self.assertIn(b'2 tests, 2 passed, 0 failed, 0 skipped.', stdout)
|
|
160
|
+
self.assertEqual(b"", stderr)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_pabotprerunmodifier_with_testlevelsplit_and_ordering(self):
|
|
164
|
+
process = subprocess.Popen(
|
|
165
|
+
[
|
|
166
|
+
sys.executable,
|
|
167
|
+
"-m", "pabot.pabot",
|
|
168
|
+
"--testlevelsplit",
|
|
169
|
+
"--pabotprerunmodifier",
|
|
170
|
+
self.modifier_file_path + ":test3",
|
|
171
|
+
"--ordering",
|
|
172
|
+
self.ordering_file_path,
|
|
173
|
+
self.tmpdir
|
|
174
|
+
],
|
|
175
|
+
cwd=self.tmpdir,
|
|
176
|
+
stdout=subprocess.PIPE,
|
|
177
|
+
stderr=subprocess.PIPE
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
stdout, stderr = process.communicate()
|
|
181
|
+
self.assertIn(f'PASSED Group_{self.tmpdir_name}.Test 1.Testing 2_{self.tmpdir_name}.Test 2.Testing 5'.encode('utf-8'), stdout)
|
|
182
|
+
self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.Testing 1'.encode('utf-8'), stdout)
|
|
183
|
+
self.assertIn(b'3 tests, 3 passed, 0 failed, 0 skipped.', stdout)
|
|
184
|
+
self.assertEqual(b"", stderr)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_pabotprerunmodifier_with_prerunmodifier(self):
|
|
188
|
+
process = subprocess.Popen(
|
|
189
|
+
[
|
|
190
|
+
sys.executable,
|
|
191
|
+
"-m", "pabot.pabot",
|
|
192
|
+
"--testlevelsplit",
|
|
193
|
+
"--pabotprerunmodifier",
|
|
194
|
+
self.modifier_file_path + ":tag2",
|
|
195
|
+
"--prerunmodifier",
|
|
196
|
+
self.modifier2_file_path,
|
|
197
|
+
self.tmpdir
|
|
198
|
+
],
|
|
199
|
+
cwd=self.tmpdir,
|
|
200
|
+
stdout=subprocess.PIPE,
|
|
201
|
+
stderr=subprocess.PIPE
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
stdout, stderr = process.communicate()
|
|
205
|
+
self.assertIn(f'PASSED {self.tmpdir_name}.Test 1.new-name-2'.encode('utf-8'), stdout)
|
|
206
|
+
self.assertIn(b'1 tests, 1 passed, 0 failed, 0 skipped.', stdout)
|
|
207
|
+
self.assertEqual(b"", stderr)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def tearDownClass(self):
|
|
212
|
+
shutil.rmtree(self.tmpdir)
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import sys
|
|
3
|
-
import textwrap
|
|
4
|
-
import shutil
|
|
5
|
-
import tempfile
|
|
6
|
-
import unittest
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _string_convert(byte_string):
|
|
10
|
-
legacy_python = sys.version_info < (3, 0)
|
|
11
|
-
return byte_string.decode() if legacy_python else byte_string
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class DependsTest(unittest.TestCase):
|
|
15
|
-
test_file = """
|
|
16
|
-
*** Settings ***
|
|
17
|
-
Test Template Test1
|
|
18
|
-
*** Test Cases ***
|
|
19
|
-
The Test S1Test 01 1
|
|
20
|
-
The Test S1Test 02 1
|
|
21
|
-
The Test S1Test 03 1
|
|
22
|
-
The Test S1Test 04 1
|
|
23
|
-
The Test S1Test 05 1
|
|
24
|
-
The Test S1Test 06 1
|
|
25
|
-
The Test S1Test 07 1
|
|
26
|
-
The Test S1Test 08 1
|
|
27
|
-
The Test S1Test 09 1
|
|
28
|
-
The Test S1Test 10 1
|
|
29
|
-
The Test S1Test 11 1
|
|
30
|
-
The Test S1Test 12 1
|
|
31
|
-
*** Keywords ***
|
|
32
|
-
Test1
|
|
33
|
-
[Arguments] ${arg}
|
|
34
|
-
Log Test
|
|
35
|
-
"""
|
|
36
|
-
passed = _string_convert(b"PASSED")
|
|
37
|
-
failed = _string_convert(b"FAILED")
|
|
38
|
-
test_01 = _string_convert(b"S1Test 01")
|
|
39
|
-
test_02 = _string_convert(b"S1Test 02")
|
|
40
|
-
test_08 = _string_convert(b"S1Test 08")
|
|
41
|
-
|
|
42
|
-
def setUp(self):
|
|
43
|
-
self.tmpdir = tempfile.mkdtemp()
|
|
44
|
-
|
|
45
|
-
def tearDown(self):
|
|
46
|
-
shutil.rmtree(self.tmpdir)
|
|
47
|
-
|
|
48
|
-
def _run_tests_with(self, testfile, orderfile):
|
|
49
|
-
robot_file = open("{}/test.robot".format(self.tmpdir), "w")
|
|
50
|
-
robot_file.write(textwrap.dedent(testfile))
|
|
51
|
-
robot_file.close()
|
|
52
|
-
with open("{}/order.dat".format(self.tmpdir), "w") as f:
|
|
53
|
-
f.write(textwrap.dedent(orderfile))
|
|
54
|
-
process = subprocess.Popen(
|
|
55
|
-
[
|
|
56
|
-
sys.executable,
|
|
57
|
-
"-m" "pabot.pabot",
|
|
58
|
-
"--testlevelsplit",
|
|
59
|
-
"--ordering",
|
|
60
|
-
"{}/order.dat".format(self.tmpdir),
|
|
61
|
-
"{}/test.robot".format(self.tmpdir),
|
|
62
|
-
],
|
|
63
|
-
cwd=self.tmpdir,
|
|
64
|
-
stdout=subprocess.PIPE,
|
|
65
|
-
stderr=subprocess.PIPE,
|
|
66
|
-
)
|
|
67
|
-
return process.communicate()
|
|
68
|
-
|
|
69
|
-
def test_dependency_ok(self):
|
|
70
|
-
stdout, stderr = self._run_tests_with(
|
|
71
|
-
self.test_file,
|
|
72
|
-
"""
|
|
73
|
-
--test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
|
|
74
|
-
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
|
|
75
|
-
--test Test.The Test S1Test 08
|
|
76
|
-
""",
|
|
77
|
-
)
|
|
78
|
-
self.assertIn(self.passed, stdout, stderr)
|
|
79
|
-
self.assertNotIn(self.failed, stdout, stderr)
|
|
80
|
-
self.assertEqual(stdout.count(self.passed), 12)
|
|
81
|
-
test_01_index = stdout.find(self.test_01)
|
|
82
|
-
test_02_index = stdout.find(self.test_02)
|
|
83
|
-
test_08_index = stdout.find(self.test_08)
|
|
84
|
-
self.assertNotEqual(test_01_index, -1)
|
|
85
|
-
self.assertNotEqual(test_02_index, -1)
|
|
86
|
-
self.assertNotEqual(test_08_index, -1)
|
|
87
|
-
self.assertTrue(test_08_index < test_02_index)
|
|
88
|
-
self.assertTrue(test_02_index < test_01_index)
|
|
89
|
-
|
|
90
|
-
def test_circular_dependency(self):
|
|
91
|
-
stdout, stderr = self._run_tests_with(
|
|
92
|
-
self.test_file,
|
|
93
|
-
"""
|
|
94
|
-
--test Test.The Test S1Test 01 #DEPENDS Test.The Test S1Test 02
|
|
95
|
-
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 01
|
|
96
|
-
--test Test.The Test S1Test 08
|
|
97
|
-
""",
|
|
98
|
-
)
|
|
99
|
-
self.assertIn(
|
|
100
|
-
b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
|
|
101
|
-
stdout,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
def test_unmet_dependency(self):
|
|
105
|
-
stdout, stderr = self._run_tests_with(
|
|
106
|
-
self.test_file,
|
|
107
|
-
"""
|
|
108
|
-
--test Test.The Test S1Test 01
|
|
109
|
-
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 23
|
|
110
|
-
--test Test.The Test S1Test 08
|
|
111
|
-
""",
|
|
112
|
-
)
|
|
113
|
-
self.assertIn(
|
|
114
|
-
b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
|
|
115
|
-
stdout,
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
def test_same_reference(self):
|
|
119
|
-
stdout, stderr = self._run_tests_with(
|
|
120
|
-
self.test_file,
|
|
121
|
-
"""
|
|
122
|
-
--test Test.The Test S1Test 01
|
|
123
|
-
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 02
|
|
124
|
-
--test Test.The Test S1Test 08
|
|
125
|
-
""",
|
|
126
|
-
)
|
|
127
|
-
self.assertIn(
|
|
128
|
-
b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.",
|
|
129
|
-
stdout,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
def test_wait(self):
|
|
133
|
-
stdout, stderr = self._run_tests_with(
|
|
134
|
-
self.test_file,
|
|
135
|
-
"""
|
|
136
|
-
--test Test.The Test S1Test 01
|
|
137
|
-
--test Test.The Test S1Test 02 #DEPENDS Test.The Test S1Test 08
|
|
138
|
-
#WAIT
|
|
139
|
-
--test Test.The Test S1Test 08
|
|
140
|
-
""",
|
|
141
|
-
)
|
|
142
|
-
self.assertIn(
|
|
143
|
-
b"Invalid test configuration: Circular or unmet dependencies detected between test suites",
|
|
144
|
-
stdout,
|
|
145
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotframework_pabot-4.0.6 → robotframework_pabot-4.1.0}/tests/test_testlevelsplit_include.py
RENAMED
|
File without changes
|
|
File without changes
|