robotframework-pabot 4.0.6__py3-none-any.whl → 4.1.1__py3-none-any.whl
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.
- pabot/__init__.py +1 -1
- pabot/arguments.py +3 -12
- pabot/execution_items.py +19 -7
- pabot/pabot.py +127 -82
- robotframework_pabot-4.1.1.dist-info/METADATA +314 -0
- {robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/RECORD +10 -10
- {robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/WHEEL +1 -1
- robotframework_pabot-4.0.6.dist-info/METADATA +0 -24
- {robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/LICENSE.txt +0 -0
- {robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/entry_points.txt +0 -0
- {robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/top_level.txt +0 -0
pabot/__init__.py
CHANGED
pabot/arguments.py
CHANGED
|
@@ -79,6 +79,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
79
79
|
"command": ["pybot" if ROBOT_VERSION < "3.1" else "robot"],
|
|
80
80
|
"verbose": False,
|
|
81
81
|
"help": False,
|
|
82
|
+
"version": False,
|
|
82
83
|
"testlevelsplit": False,
|
|
83
84
|
"pabotlib": True,
|
|
84
85
|
"pabotlibhost": "127.0.0.1",
|
|
@@ -106,8 +107,9 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
106
107
|
"resourcefile": str,
|
|
107
108
|
"pabotlibhost": str,
|
|
108
109
|
"pabotlibport": int,
|
|
110
|
+
"pabotprerunmodifier": str,
|
|
109
111
|
"processtimeout": int,
|
|
110
|
-
"ordering":
|
|
112
|
+
"ordering": str,
|
|
111
113
|
"suitesfrom": str,
|
|
112
114
|
"artifacts": lambda x: x.split(","),
|
|
113
115
|
"shard": _parse_shard,
|
|
@@ -195,17 +197,6 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
195
197
|
return remaining_args, pabot_args
|
|
196
198
|
|
|
197
199
|
|
|
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
200
|
def _delete_none_keys(d): # type: (Dict[str, Optional[object]]) -> Dict[str, object]
|
|
210
201
|
keys = set()
|
|
211
202
|
for k in d:
|
pabot/execution_items.py
CHANGED
|
@@ -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
|
)
|
pabot/pabot.py
CHANGED
|
@@ -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
|
"""
|
|
@@ -152,6 +91,12 @@ try:
|
|
|
152
91
|
except ImportError:
|
|
153
92
|
from pipes import quote # type: ignore
|
|
154
93
|
|
|
94
|
+
try:
|
|
95
|
+
import importlib.metadata
|
|
96
|
+
METADATA_AVAILABLE = True
|
|
97
|
+
except ImportError:
|
|
98
|
+
METADATA_AVAILABLE = False
|
|
99
|
+
|
|
155
100
|
from typing import IO, Any, Dict, List, Optional, Tuple, Union
|
|
156
101
|
|
|
157
102
|
CTRL_C_PRESSED = False
|
|
@@ -183,6 +128,78 @@ _ROBOT_EXTENSIONS = [
|
|
|
183
128
|
]
|
|
184
129
|
_ALL_ELAPSED = [] # type: List[Union[int, float]]
|
|
185
130
|
|
|
131
|
+
# Python version check for supporting importlib.metadata (requires Python 3.8+)
|
|
132
|
+
IS_PYTHON_3_8_OR_NEWER = sys.version_info >= (3, 8)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def read_args_from_readme():
|
|
136
|
+
"""Reads a specific section from package METADATA or development README.md if available."""
|
|
137
|
+
|
|
138
|
+
# 1. Try to read from METADATA (only if available and Python version is compatible)
|
|
139
|
+
metadata_section = read_from_metadata()
|
|
140
|
+
if metadata_section:
|
|
141
|
+
return f"Extracted from METADATA:\n\n{metadata_section}"
|
|
142
|
+
|
|
143
|
+
# 2. If METADATA is not available, fall back to development environment README.md
|
|
144
|
+
dev_readme_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "README.md"))
|
|
145
|
+
if os.path.exists(dev_readme_path):
|
|
146
|
+
with open(dev_readme_path, encoding="utf-8") as f:
|
|
147
|
+
lines = f.readlines()
|
|
148
|
+
help_args = extract_section(lines)
|
|
149
|
+
if help_args:
|
|
150
|
+
return f"Extracted from README.md ({dev_readme_path}):\n\n{help_args}"
|
|
151
|
+
|
|
152
|
+
if not IS_PYTHON_3_8_OR_NEWER:
|
|
153
|
+
return (
|
|
154
|
+
"Warning: Your Python version is too old and does not support importlib.metadata.\n"
|
|
155
|
+
"Please consider upgrading to Python 3.8 or newer for better compatibility.\n\n"
|
|
156
|
+
"To view any possible arguments, please kindly read the README.md here:\n"
|
|
157
|
+
"https://github.com/mkorpela/pabot"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
"Error: README.md or METADATA long_description not found.\n"
|
|
162
|
+
"If you believe this is an issue, please report it at:\n"
|
|
163
|
+
"https://github.com/mkorpela/pabot/issues"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def read_from_metadata():
|
|
168
|
+
"""Reads the long_description section from package METADATA if available."""
|
|
169
|
+
if not METADATA_AVAILABLE:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
metadata = importlib.metadata.metadata("robotframework-pabot")
|
|
174
|
+
description = metadata.get("Description", "")
|
|
175
|
+
|
|
176
|
+
if not description:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
lines = description.splitlines(True)
|
|
180
|
+
return extract_section(lines)
|
|
181
|
+
|
|
182
|
+
except importlib.metadata.PackageNotFoundError:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def extract_section(lines, start_marker="<!-- START DOCSTRING -->", end_marker="<!-- END DOCSTRING -->"):
|
|
187
|
+
"""Extracts content between two markers in a list of lines."""
|
|
188
|
+
inside_section = False
|
|
189
|
+
extracted_lines = []
|
|
190
|
+
|
|
191
|
+
for line in lines:
|
|
192
|
+
if start_marker in line:
|
|
193
|
+
inside_section = True
|
|
194
|
+
continue
|
|
195
|
+
if end_marker in line:
|
|
196
|
+
break
|
|
197
|
+
if inside_section:
|
|
198
|
+
# Remove Markdown links but keep the text
|
|
199
|
+
extracted_lines.append(re.sub(r'\[([^\]]+)\]\(https?://[^\)]+\)', r'\1', line))
|
|
200
|
+
|
|
201
|
+
return "".join(extracted_lines).strip()
|
|
202
|
+
|
|
186
203
|
|
|
187
204
|
class Color:
|
|
188
205
|
SUPPORTED_OSES = ["posix"]
|
|
@@ -880,6 +897,8 @@ def solve_shard_suites(suite_names, pabot_args):
|
|
|
880
897
|
|
|
881
898
|
|
|
882
899
|
def solve_suite_names(outs_dir, datasources, options, pabot_args):
|
|
900
|
+
if pabot_args.get("pabotprerunmodifier"):
|
|
901
|
+
options['prerunmodifier'].append(pabot_args['pabotprerunmodifier'])
|
|
883
902
|
h = Hashes(
|
|
884
903
|
dirs=get_hash_of_dirs(datasources),
|
|
885
904
|
cmd=get_hash_of_command(options, pabot_args),
|
|
@@ -915,7 +934,7 @@ def solve_suite_names(outs_dir, datasources, options, pabot_args):
|
|
|
915
934
|
for l in lines[4:]
|
|
916
935
|
)
|
|
917
936
|
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:
|
|
937
|
+
if corrupted or h != file_h or file_hash != hash_of_file or pabot_args.get("pabotprerunmodifier"):
|
|
919
938
|
return _regenerate(
|
|
920
939
|
file_h,
|
|
921
940
|
h,
|
|
@@ -1949,7 +1968,11 @@ def main_program(args):
|
|
|
1949
1968
|
_start_message_writer()
|
|
1950
1969
|
options, datasources, pabot_args, opts_for_run = parse_args(args)
|
|
1951
1970
|
if pabot_args["help"]:
|
|
1952
|
-
|
|
1971
|
+
help_print = __doc__.replace(
|
|
1972
|
+
"PLACEHOLDER_README.MD",
|
|
1973
|
+
read_args_from_readme()
|
|
1974
|
+
)
|
|
1975
|
+
print(help_print.replace("[PABOT_VERSION]", PABOT_VERSION))
|
|
1953
1976
|
return 0
|
|
1954
1977
|
if len(datasources) == 0:
|
|
1955
1978
|
print("[ " + _wrap_with(Color.RED, "ERROR") + " ]: No datasources given.")
|
|
@@ -1988,7 +2011,8 @@ def main_program(args):
|
|
|
1988
2011
|
)
|
|
1989
2012
|
return result_code if not _ABNORMAL_EXIT_HAPPENED else 252
|
|
1990
2013
|
except Information as i:
|
|
1991
|
-
|
|
2014
|
+
version_print = __doc__.replace("\nPLACEHOLDER_README.MD\n", "")
|
|
2015
|
+
print(version_print.replace("[PABOT_VERSION]", PABOT_VERSION))
|
|
1992
2016
|
print(i.message)
|
|
1993
2017
|
except DataError as err:
|
|
1994
2018
|
print(err.message)
|
|
@@ -2010,10 +2034,24 @@ def main_program(args):
|
|
|
2010
2034
|
_stop_message_writer()
|
|
2011
2035
|
|
|
2012
2036
|
|
|
2037
|
+
def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
|
|
2038
|
+
try:
|
|
2039
|
+
with open(filename, "r") as orderingfile:
|
|
2040
|
+
return [
|
|
2041
|
+
parse_execution_item_line(line.strip())
|
|
2042
|
+
for line in orderingfile.readlines()
|
|
2043
|
+
]
|
|
2044
|
+
except FileNotFoundError:
|
|
2045
|
+
raise DataError("Error: File '%s' not found." % filename)
|
|
2046
|
+
except:
|
|
2047
|
+
raise DataError("Error parsing ordering file '%s'" % filename)
|
|
2048
|
+
|
|
2049
|
+
|
|
2013
2050
|
def _group_suites(outs_dir, datasources, options, pabot_args):
|
|
2014
2051
|
suite_names = solve_suite_names(outs_dir, datasources, options, pabot_args)
|
|
2015
2052
|
_verify_depends(suite_names)
|
|
2016
|
-
|
|
2053
|
+
ordering_arg = _parse_ordering(pabot_args.get("ordering")) if (pabot_args.get("ordering")) is not None else None
|
|
2054
|
+
ordered_suites = _preserve_order(suite_names, ordering_arg)
|
|
2017
2055
|
shard_suites = solve_shard_suites(ordered_suites, pabot_args)
|
|
2018
2056
|
grouped_suites = (
|
|
2019
2057
|
_chunked_suite_names(shard_suites, pabot_args["processes"])
|
|
@@ -2083,20 +2121,27 @@ def _group_by_depend(suite_names):
|
|
|
2083
2121
|
return [suite_names]
|
|
2084
2122
|
independent_tests = list(filter(lambda suite: not suite.depends, runnable_suites))
|
|
2085
2123
|
dependency_tree = [independent_tests]
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2124
|
+
dependent_tests = list(filter(lambda suite: suite.depends, runnable_suites))
|
|
2125
|
+
unknown_dependent_tests = dependent_tests
|
|
2126
|
+
while len(unknown_dependent_tests) > 0:
|
|
2127
|
+
run_in_this_stage, run_later = [], []
|
|
2128
|
+
for d in unknown_dependent_tests:
|
|
2129
|
+
stage_indexes = []
|
|
2130
|
+
for i, stage in enumerate(dependency_tree):
|
|
2131
|
+
for test in stage:
|
|
2132
|
+
if test.name in d.depends:
|
|
2133
|
+
stage_indexes.append(i)
|
|
2134
|
+
# All #DEPENDS test are already run:
|
|
2135
|
+
if len(stage_indexes) == len(d.depends):
|
|
2136
|
+
run_in_this_stage.append(d)
|
|
2137
|
+
else:
|
|
2138
|
+
run_later.append(d)
|
|
2139
|
+
unknown_dependent_tests = run_later
|
|
2140
|
+
if len(run_in_this_stage) == 0:
|
|
2141
|
+
text = "There are circular or unmet dependencies using #DEPENDS. Check this/these test(s): " + str(run_later)
|
|
2142
|
+
raise DataError(text)
|
|
2143
|
+
else:
|
|
2144
|
+
dependency_tree.append(run_in_this_stage)
|
|
2100
2145
|
flattened_dependency_tree = sum(dependency_tree, [])
|
|
2101
2146
|
if len(flattened_dependency_tree) != len(runnable_suites):
|
|
2102
2147
|
raise DataError(
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: robotframework-pabot
|
|
3
|
+
Version: 4.1.1
|
|
4
|
+
Summary: Parallel test runner for Robot Framework
|
|
5
|
+
Home-page: https://pabot.org
|
|
6
|
+
Download-URL: https://pypi.python.org/pypi/robotframework-pabot
|
|
7
|
+
Author: Mikko Korpela
|
|
8
|
+
Author-email: mikko.korpela@gmail.com
|
|
9
|
+
License: Apache License, Version 2.0
|
|
10
|
+
Project-URL: Source, https://github.com/mkorpela/pabot
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Testing
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
17
|
+
Classifier: Framework :: Robot Framework
|
|
18
|
+
Requires-Python: >=3.6
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.txt
|
|
21
|
+
Requires-Dist: robotframework>=3.2
|
|
22
|
+
Requires-Dist: robotframework-stacktrace>=0.4.1
|
|
23
|
+
Requires-Dist: natsort>=8.2.0
|
|
24
|
+
Dynamic: download-url
|
|
25
|
+
|
|
26
|
+
# Pabot
|
|
27
|
+
|
|
28
|
+
[中文版](README_zh.md)
|
|
29
|
+
|
|
30
|
+
[](https://pypi.python.org/pypi/robotframework-pabot)
|
|
31
|
+
[](http://pepy.tech/project/robotframework-pabot)
|
|
32
|
+
|
|
33
|
+
<img src="https://raw.githubusercontent.com/mkorpela/pabot/master/pabot.png" width="100">
|
|
34
|
+
|
|
35
|
+
----
|
|
36
|
+
|
|
37
|
+
A parallel executor for [Robot Framework](http://www.robotframework.org) tests. With Pabot you can split one execution into multiple and save test execution time.
|
|
38
|
+
|
|
39
|
+
[](https://youtu.be/i0RV6SJSIn8 "Pabot presentation at robocon.io 2018")
|
|
40
|
+
|
|
41
|
+
## Installation:
|
|
42
|
+
|
|
43
|
+
From PyPi:
|
|
44
|
+
|
|
45
|
+
pip install -U robotframework-pabot
|
|
46
|
+
|
|
47
|
+
OR clone this repository and run:
|
|
48
|
+
|
|
49
|
+
setup.py install
|
|
50
|
+
|
|
51
|
+
OR clone this repository and run:
|
|
52
|
+
|
|
53
|
+
pip install --editable .
|
|
54
|
+
|
|
55
|
+
## Basic use
|
|
56
|
+
|
|
57
|
+
Split execution to suite files.
|
|
58
|
+
|
|
59
|
+
pabot [path to tests]
|
|
60
|
+
|
|
61
|
+
Split execution on test level.
|
|
62
|
+
|
|
63
|
+
pabot --testlevelsplit [path to tests]
|
|
64
|
+
|
|
65
|
+
Run same tests with two different configurations.
|
|
66
|
+
|
|
67
|
+
pabot --argumentfile1 first.args --argumentfile2 second.args [path to tests]
|
|
68
|
+
|
|
69
|
+
For more complex cases please read onward.
|
|
70
|
+
|
|
71
|
+
## Contact
|
|
72
|
+
|
|
73
|
+
Join [Pabot Slack channel](https://robotframework.slack.com/messages/C7HKR2L6L) in Robot Framework slack.
|
|
74
|
+
[Get invite to Robot Framework slack](https://robotframework-slack-invite.herokuapp.com/).
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
## Contributing to the project
|
|
78
|
+
|
|
79
|
+
There are several ways you can help in improving this tool:
|
|
80
|
+
|
|
81
|
+
- Report an issue or an improvement idea to the [issue tracker](https://github.com/mkorpela/pabot/issues)
|
|
82
|
+
- Contribute by programming and making a pull request (easiest way is to work on an issue from the issue tracker)
|
|
83
|
+
|
|
84
|
+
## Command-line options
|
|
85
|
+
<!-- START DOCSTRING -->
|
|
86
|
+
pabot [--verbose|--testlevelsplit|--command .. --end-command|
|
|
87
|
+
--processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
|
|
88
|
+
--processtimeout num|
|
|
89
|
+
--shard i/n|
|
|
90
|
+
--artifacts extensions|--artifactsinsubfolders|
|
|
91
|
+
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file
|
|
92
|
+
--chunk
|
|
93
|
+
--pabotprerunmodifier modifier
|
|
94
|
+
--help|--version]
|
|
95
|
+
[robot options] [path ...]
|
|
96
|
+
|
|
97
|
+
PabotLib remote server is started by default to enable locking and resource distribution between parallel test executions.
|
|
98
|
+
|
|
99
|
+
Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following pabot options:
|
|
100
|
+
|
|
101
|
+
--verbose
|
|
102
|
+
More output from the parallel execution.
|
|
103
|
+
|
|
104
|
+
--testlevelsplit
|
|
105
|
+
Split execution on test level instead of default suite level. If .pabotsuitenames contains both tests and suites then
|
|
106
|
+
this will only affect new suites and split only them. Leaving this flag out when both suites and tests in
|
|
107
|
+
.pabotsuitenames file will also only affect new suites and add them as suite files.
|
|
108
|
+
|
|
109
|
+
--command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
|
|
110
|
+
RF script for situations where robot is not used directly.
|
|
111
|
+
|
|
112
|
+
--processes [NUMBER OF PROCESSES]
|
|
113
|
+
How many parallel executors to use (default max of 2 and cpu count). Special option "all" will use as many processes as
|
|
114
|
+
there are executable suites or tests.
|
|
115
|
+
|
|
116
|
+
--no-pabotlib
|
|
117
|
+
Disable the PabotLib remote server if you don't need locking or resource distribution features.
|
|
118
|
+
|
|
119
|
+
--pabotlibhost [HOSTNAME]
|
|
120
|
+
Connect to an already running instance of the PabotLib remote server at the given host (disables the local PabotLib
|
|
121
|
+
server start). For example, to connect to a remote PabotLib server running on another machine:
|
|
122
|
+
|
|
123
|
+
pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
|
|
124
|
+
|
|
125
|
+
The remote server can be also started and executed separately from pabot instances:
|
|
126
|
+
|
|
127
|
+
python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
|
|
128
|
+
python -m pabot.pabotlib resource.txt 192.168.1.123 8271
|
|
129
|
+
|
|
130
|
+
This enables sharing a resource with multiple Robot Framework instances.
|
|
131
|
+
|
|
132
|
+
--pabotlibport [PORT]
|
|
133
|
+
Port number of the PabotLib remote server (default is 8270). See --pabotlibhost for more information.
|
|
134
|
+
|
|
135
|
+
--processtimeout [TIMEOUT]
|
|
136
|
+
Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
|
|
137
|
+
|
|
138
|
+
--shard [INDEX]/[TOTAL]
|
|
139
|
+
Optionally split execution into smaller pieces. This can be used for distributing testing to multiple machines.
|
|
140
|
+
|
|
141
|
+
--artifacts [FILE EXTENSIONS]
|
|
142
|
+
List of file extensions (comma separated). Defines which files (screenshots, videos etc.) from separate reporting
|
|
143
|
+
directories would be copied and included in a final report. Possible links to copied files in RF log would be updated
|
|
144
|
+
(only relative paths supported). The default value is `png`.
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
|
|
148
|
+
--artifacts png,mp4,txt
|
|
149
|
+
|
|
150
|
+
--artifactsinsubfolders
|
|
151
|
+
Copy artifacts located not only directly in the RF output dir, but also in it's sub-folders.
|
|
152
|
+
|
|
153
|
+
--resourcefile [FILEPATH]
|
|
154
|
+
Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
|
|
155
|
+
pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
|
|
156
|
+
|
|
157
|
+
--argumentfile [INTEGER] [FILEPATH]
|
|
158
|
+
Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
|
|
159
|
+
|
|
160
|
+
For example:
|
|
161
|
+
|
|
162
|
+
--argumentfile1 arg1.txt --argumentfile2 arg2.txt
|
|
163
|
+
|
|
164
|
+
--suitesfrom [FILEPATH TO OUTPUTXML]
|
|
165
|
+
Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
|
|
166
|
+
before shorter ones.
|
|
167
|
+
|
|
168
|
+
--ordering [FILE PATH]
|
|
169
|
+
Optionally give execution order from a file.
|
|
170
|
+
|
|
171
|
+
--chunk
|
|
172
|
+
Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
|
|
173
|
+
setups and teardowns.
|
|
174
|
+
|
|
175
|
+
--pabotprerunmodifier [PRERUNMODIFIER MODULE OR CLASS]
|
|
176
|
+
Like Robot Framework's --prerunmodifier, but executed only once in the pabot's main process after all other
|
|
177
|
+
--prerunmodifiers. But unlike the regular --prerunmodifier command, --pabotprerunmodifier is not executed again in each
|
|
178
|
+
pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
|
|
179
|
+
example, to modify the list of tests to be performed.
|
|
180
|
+
|
|
181
|
+
--help
|
|
182
|
+
Print usage instructions.
|
|
183
|
+
|
|
184
|
+
--version
|
|
185
|
+
Print version information.
|
|
186
|
+
|
|
187
|
+
Example usages:
|
|
188
|
+
|
|
189
|
+
pabot test_directory
|
|
190
|
+
pabot --exclude FOO directory_to_tests
|
|
191
|
+
pabot --command java -jar robotframework.jar --end-command --include SMOKE tests
|
|
192
|
+
pabot --processes 10 tests
|
|
193
|
+
pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 --processes 10 tests
|
|
194
|
+
pabot --artifacts png,mp4,txt --artifactsinsubfolders directory_to_tests
|
|
195
|
+
# To disable PabotLib:
|
|
196
|
+
pabot --no-pabotlib tests
|
|
197
|
+
|
|
198
|
+
<!-- END DOCSTRING -->
|
|
199
|
+
### PabotLib
|
|
200
|
+
|
|
201
|
+
pabot.PabotLib provides keywords that will help communication and data sharing between the executor processes.
|
|
202
|
+
These can be helpful when you must ensure that only one of the processes uses some piece of data or operates on some part of the system under test at a time.
|
|
203
|
+
|
|
204
|
+
PabotLib Docs are located at https://pabot.org/PabotLib.html.
|
|
205
|
+
|
|
206
|
+
### PabotLib example:
|
|
207
|
+
|
|
208
|
+
test.robot
|
|
209
|
+
|
|
210
|
+
*** Settings ***
|
|
211
|
+
Library pabot.PabotLib
|
|
212
|
+
|
|
213
|
+
*** Test Case ***
|
|
214
|
+
Testing PabotLib
|
|
215
|
+
Acquire Lock MyLock
|
|
216
|
+
Log This part is critical section
|
|
217
|
+
Release Lock MyLock
|
|
218
|
+
${valuesetname}= Acquire Value Set admin-server
|
|
219
|
+
${host}= Get Value From Set host
|
|
220
|
+
${username}= Get Value From Set username
|
|
221
|
+
${password}= Get Value From Set password
|
|
222
|
+
Log Do something with the values (for example access host with username and password)
|
|
223
|
+
Release Value Set
|
|
224
|
+
Log After value set release others can obtain the variable values
|
|
225
|
+
|
|
226
|
+
valueset.dat
|
|
227
|
+
|
|
228
|
+
[Server1]
|
|
229
|
+
tags=admin-server
|
|
230
|
+
HOST=123.123.123.123
|
|
231
|
+
USERNAME=user1
|
|
232
|
+
PASSWORD=password1
|
|
233
|
+
|
|
234
|
+
[Server2]
|
|
235
|
+
tags=server
|
|
236
|
+
HOST=121.121.121.121
|
|
237
|
+
USERNAME=user2
|
|
238
|
+
PASSWORD=password2
|
|
239
|
+
|
|
240
|
+
[Server3]
|
|
241
|
+
tags=admin-server
|
|
242
|
+
HOST=222.222.222.222
|
|
243
|
+
USERNAME=user3
|
|
244
|
+
PASSWORD=password4
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
pabot call using resources from valueset.dat
|
|
248
|
+
|
|
249
|
+
pabot --pabotlib --resourcefile valueset.dat test.robot
|
|
250
|
+
|
|
251
|
+
### Controlling execution order and level of parallelism
|
|
252
|
+
|
|
253
|
+
.pabotsuitenames file contains the list of suites that will be executed.
|
|
254
|
+
File is created during pabot execution if not already there.
|
|
255
|
+
The file is a cache that pabot uses when re-executing same tests to speed up processing.
|
|
256
|
+
This file can be partially manually edited but easier option is to use ```--ordering FILENAME```.
|
|
257
|
+
First 4 rows contain information that should not be edited - pabot will edit these when something changes.
|
|
258
|
+
After this come the suite names.
|
|
259
|
+
|
|
260
|
+
With ```--ordering FILENAME``` you can have a list that controls order also. The syntax is same as .pabotsuitenames file syntax but does not contain 4 hash rows that are present in .pabotsuitenames.
|
|
261
|
+
|
|
262
|
+
There different possibilities to influence the execution:
|
|
263
|
+
|
|
264
|
+
* The order of suites can be changed.
|
|
265
|
+
* If a directory (or a directory structure) should be executed sequentially, add the directory suite name to a row as a ```--suite``` option.
|
|
266
|
+
* 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.
|
|
267
|
+
* You can add a line with text `#WAIT` to force executor to wait until all previous suites have been executed.
|
|
268
|
+
* You can group suites and tests together to same executor process by adding line `{` before the group and `}`after.
|
|
269
|
+
* 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.
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
--test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
|
|
273
|
+
--test robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
|
|
274
|
+
--test robotTest.2 Lists.Test with Keywords and a list
|
|
275
|
+
#WAIT
|
|
276
|
+
--test robotTest.2 Lists.Test with a Keyword that accepts multiple arguments
|
|
277
|
+
--test robotTest.2 Lists.Test with some Collections keywords
|
|
278
|
+
--test robotTest.2 Lists.Test to access list entries
|
|
279
|
+
--test robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
280
|
+
--test robotTest.3 Dictionary.Dictionaries for named arguments #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
281
|
+
--test robotTest.1 Scalar.Test Case With Variables #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
282
|
+
--test robotTest.1 Scalar.Test with Numbers #DEPENDS robotTest.1 Scalar.Test With Arguments and Return Values
|
|
283
|
+
--test robotTest.1 Scalar.Test Case with Return Values #DEPENDS robotTest.1 Scalar.Test with Numbers
|
|
284
|
+
--test robotTest.1 Scalar.Test With Arguments and Return Values
|
|
285
|
+
--test robotTest.3 Dictionary.Test with Dictionaries as Arguments
|
|
286
|
+
--test robotTest.3 Dictionary.Test with FOR loops and Dictionaries #DEPENDS robotTest.1 Scalar.Test Case with Return Values
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Programmatic use
|
|
290
|
+
|
|
291
|
+
Library offers an endpoint `main_program` that will not call `sys.exit`. This can help in developing your own python program around pabot.
|
|
292
|
+
|
|
293
|
+
```Python
|
|
294
|
+
import sys
|
|
295
|
+
from pabot.pabot import main_program
|
|
296
|
+
|
|
297
|
+
def amazing_new_program():
|
|
298
|
+
print("Before calling pabot")
|
|
299
|
+
exit_code = main_program(['tests'])
|
|
300
|
+
print(f"After calling pabot (return code {exit_code})")
|
|
301
|
+
sys.exit(exit_code)
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Global variables
|
|
306
|
+
|
|
307
|
+
Pabot will insert following global variables to Robot Framework namespace. These are here to enable PabotLib functionality and for custom listeners etc. to get some information on the overall execution of pabot.
|
|
308
|
+
|
|
309
|
+
PABOTQUEUEINDEX - this contains a unique index number for the execution. Indexes start from 0.
|
|
310
|
+
PABOTLIBURI - this contains the URI for the running PabotLib server
|
|
311
|
+
PABOTEXECUTIONPOOLID - this contains the pool id (an integer) for the current Robot Framework executor. This is helpful for example when visualizing the execution flow from your own listener.
|
|
312
|
+
PABOTNUMBEROFPROCESSES - max number of concurrent processes that pabot may use in execution.
|
|
313
|
+
CALLER_ID - a universally unique identifier for this execution.
|
|
314
|
+
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
pabot/SharedLibrary.py,sha256=mIipGs3ZhKYEakKprcbrMI4P_Un6qI8gE7086xpHaLY,2552
|
|
2
|
-
pabot/__init__.py,sha256=
|
|
3
|
-
pabot/arguments.py,sha256=
|
|
2
|
+
pabot/__init__.py,sha256=KIz2lUOAX4LIQ5wjdtv4z7UJ4g2DDn7QCTslKSdY4NA,94
|
|
3
|
+
pabot/arguments.py,sha256=XBaWujhuw_5dnZnEwAJcl4mArgzMkjNfNHBty6jqZRo,6842
|
|
4
4
|
pabot/clientwrapper.py,sha256=yz7battGs0exysnDeLDWJuzpb2Q-qSjitwxZMO2TlJw,231
|
|
5
5
|
pabot/coordinatorwrapper.py,sha256=nQQ7IowD6c246y8y9nsx0HZbt8vS2XODhPVDjm-lyi0,195
|
|
6
|
-
pabot/execution_items.py,sha256=
|
|
7
|
-
pabot/pabot.py,sha256=
|
|
6
|
+
pabot/execution_items.py,sha256=qs15SZjGE5CQAO0UpNuVWZ60wTLdWGDK1CkbBz7JtfQ,8892
|
|
7
|
+
pabot/pabot.py,sha256=WrQsrgUVQZmzmfYLu0m01c2nbyc_Aj-Kz8sTpPo2jC8,70522
|
|
8
8
|
pabot/pabotlib.py,sha256=FRZKaKy1ybyRkE-0SpaCsUWzxZAzNNU5dAywSm1QoPk,22324
|
|
9
9
|
pabot/result_merger.py,sha256=8iIptBn5MdgiW-OdhwVR2DZ0hUYuQeQXwIHAEPkMTuw,9095
|
|
10
10
|
pabot/robotremoteserver.py,sha256=L3O2QRKSGSE4ux5M1ip5XJMaelqaxQWJxd9wLLdtpzM,22272
|
|
@@ -14,9 +14,9 @@ pabot/py3/client.py,sha256=Od9L4vZ0sozMHq_W_ITQHBBt8kAej40DG58wnxmbHGM,1434
|
|
|
14
14
|
pabot/py3/coordinator.py,sha256=kBshCzA_1QX_f0WNk42QBJyDYSwSlNM-UEBxOReOj6E,2313
|
|
15
15
|
pabot/py3/messages.py,sha256=7mFr4_0x1JHm5sW8TvKq28Xs_JoeIGku2bX7AyO0kng,2557
|
|
16
16
|
pabot/py3/worker.py,sha256=5rfp4ZiW6gf8GRz6eC0-KUkfx847A91lVtRYpLAv2sg,1612
|
|
17
|
-
robotframework_pabot-4.
|
|
18
|
-
robotframework_pabot-4.
|
|
19
|
-
robotframework_pabot-4.
|
|
20
|
-
robotframework_pabot-4.
|
|
21
|
-
robotframework_pabot-4.
|
|
22
|
-
robotframework_pabot-4.
|
|
17
|
+
robotframework_pabot-4.1.1.dist-info/LICENSE.txt,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
18
|
+
robotframework_pabot-4.1.1.dist-info/METADATA,sha256=5cHJ-pp3VvOWIjwvrCbBdcVt2_wtBdZHnBvXlD_x-aw,13484
|
|
19
|
+
robotframework_pabot-4.1.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
20
|
+
robotframework_pabot-4.1.1.dist-info/entry_points.txt,sha256=JpAIFADTeFOQWdwmn56KpAil8V3-41ZC5ICXCYm3Ng0,43
|
|
21
|
+
robotframework_pabot-4.1.1.dist-info/top_level.txt,sha256=t3OwfEAsSxyxrhjy_GCJYHKbV_X6AIsgeLhYeHvObG4,6
|
|
22
|
+
robotframework_pabot-4.1.1.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: robotframework-pabot
|
|
3
|
-
Version: 4.0.6
|
|
4
|
-
Summary: Parallel test runner for Robot Framework
|
|
5
|
-
Home-page: https://pabot.org
|
|
6
|
-
Download-URL: https://pypi.python.org/pypi/robotframework-pabot
|
|
7
|
-
Author: Mikko Korpela
|
|
8
|
-
Author-email: mikko.korpela@gmail.com
|
|
9
|
-
License: Apache License, Version 2.0
|
|
10
|
-
Project-URL: Source, https://github.com/mkorpela/pabot
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Natural Language :: English
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Topic :: Software Development :: Testing
|
|
15
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
17
|
-
Classifier: Framework :: Robot Framework
|
|
18
|
-
Requires-Python: >=3.6
|
|
19
|
-
License-File: LICENSE.txt
|
|
20
|
-
Requires-Dist: robotframework>=3.2
|
|
21
|
-
Requires-Dist: robotframework-stacktrace>=0.4.1
|
|
22
|
-
Requires-Dist: natsort>=8.2.0
|
|
23
|
-
|
|
24
|
-
A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.
|
|
File without changes
|
{robotframework_pabot-4.0.6.dist-info → robotframework_pabot-4.1.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|