robotframework-pabot 4.1.0__py3-none-any.whl → 4.2.0__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 CHANGED
@@ -1,5 +1,10 @@
1
1
  from __future__ import absolute_import
2
2
 
3
- from .pabotlib import PabotLib
3
+ # Avoid import errors during setup/build
4
+ try:
5
+ from .pabotlib import PabotLib
6
+ __all__ = ["PabotLib"]
7
+ except ImportError:
8
+ pass
4
9
 
5
- __version__ = "4.1.0"
10
+ __version__ = "4.2.0"
pabot/arguments.py CHANGED
@@ -16,6 +16,7 @@ from .execution_items import (
16
16
  SuiteItem,
17
17
  TestItem,
18
18
  WaitItem,
19
+ SleepItem,
19
20
  )
20
21
 
21
22
  ARGSMATCHER = re.compile(r"--argumentfile(\d+)")
@@ -79,6 +80,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
79
80
  "command": ["pybot" if ROBOT_VERSION < "3.1" else "robot"],
80
81
  "verbose": False,
81
82
  "help": False,
83
+ "version": False,
82
84
  "testlevelsplit": False,
83
85
  "pabotlib": True,
84
86
  "pabotlibhost": "127.0.0.1",
@@ -90,6 +92,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
90
92
  "shardindex": 0,
91
93
  "shardcount": 1,
92
94
  "chunk": False,
95
+ "no-rebot": False,
93
96
  }
94
97
  # Explicitly define argument types for validation
95
98
  flag_args = {
@@ -99,6 +102,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
99
102
  "pabotlib",
100
103
  "artifactsinsubfolders",
101
104
  "chunk",
105
+ "no-rebot"
102
106
  }
103
107
  value_args = {
104
108
  "hive": str,
@@ -216,6 +220,8 @@ def parse_execution_item_line(text): # type: (str) -> ExecutionItem
216
220
  if text.startswith("DYNAMICTEST"):
217
221
  suite, test = text[12:].split(" :: ")
218
222
  return DynamicTestItem(test, suite)
223
+ if text.startswith("#SLEEP "):
224
+ return SleepItem(text[7:])
219
225
  if text == "#WAIT":
220
226
  return WaitItem()
221
227
  if text == "{":
pabot/execution_items.py CHANGED
@@ -12,6 +12,7 @@ class ExecutionItem(object):
12
12
  isWait = False
13
13
  type = None # type: str
14
14
  name = None # type: str
15
+ sleep = 0 # type: int
15
16
 
16
17
  def top_name(self):
17
18
  # type: () -> str
@@ -29,6 +30,14 @@ class ExecutionItem(object):
29
30
  # type: () -> str
30
31
  return ""
31
32
 
33
+ def set_sleep(self, sleep_time):
34
+ # type: (int) -> None
35
+ self.sleep = sleep_time
36
+
37
+ def get_sleep(self):
38
+ # type: () -> int
39
+ return self.sleep
40
+
32
41
  def modify_options_for_executor(self, options):
33
42
  options[self.type] = self.name
34
43
 
@@ -82,6 +91,8 @@ class GroupItem(ExecutionItem):
82
91
  )
83
92
  if len(self._items) > 0:
84
93
  self.name += "_"
94
+ if self.get_sleep() < item.get_sleep(): # TODO: check!
95
+ self.set_sleep(item.get_sleep())
85
96
  self.name += item.name
86
97
  self._element_type = item.type
87
98
  self._items.append(item)
@@ -274,6 +285,23 @@ class WaitItem(ExecutionItem):
274
285
  return self.name
275
286
 
276
287
 
288
+ class SleepItem(ExecutionItem):
289
+ type = "sleep"
290
+
291
+ def __init__(self, time):
292
+ try:
293
+ assert 3600 >= int(time) >= 0 # 1 h max.
294
+ self.name = time
295
+ self.sleep = int(time)
296
+ except ValueError:
297
+ raise ValueError("#SLEEP value %s is not integer" % time)
298
+ except AssertionError:
299
+ raise ValueError("#SLEEP value %s is not in between 0 and 3600" % time)
300
+
301
+ def line(self):
302
+ return "#SLEEP " + self.name
303
+
304
+
277
305
  class GroupStartItem(ExecutionItem):
278
306
  type = "group"
279
307
 
pabot/pabot.py CHANGED
@@ -41,6 +41,7 @@ import threading
41
41
  import time
42
42
  import traceback
43
43
  import uuid
44
+ import copy
44
45
  from collections import namedtuple
45
46
  from contextlib import closing
46
47
  from glob import glob
@@ -78,6 +79,7 @@ from .execution_items import (
78
79
  SuiteItems,
79
80
  TestItem,
80
81
  RunnableItem,
82
+ SleepItem,
81
83
  )
82
84
  from .result_merger import merge
83
85
 
@@ -91,6 +93,12 @@ try:
91
93
  except ImportError:
92
94
  from pipes import quote # type: ignore
93
95
 
96
+ try:
97
+ import importlib.metadata
98
+ METADATA_AVAILABLE = True
99
+ except ImportError:
100
+ METADATA_AVAILABLE = False
101
+
94
102
  from typing import IO, Any, Dict, List, Optional, Tuple, Union
95
103
 
96
104
  CTRL_C_PRESSED = False
@@ -122,11 +130,63 @@ _ROBOT_EXTENSIONS = [
122
130
  ]
123
131
  _ALL_ELAPSED = [] # type: List[Union[int, float]]
124
132
 
133
+ # Python version check for supporting importlib.metadata (requires Python 3.8+)
134
+ IS_PYTHON_3_8_OR_NEWER = sys.version_info >= (3, 8)
135
+
136
+
137
+ def read_args_from_readme():
138
+ """Reads a specific section from package METADATA or development README.md if available."""
139
+
140
+ # 1. Try to read from METADATA (only if available and Python version is compatible)
141
+ metadata_section = read_from_metadata()
142
+ if metadata_section:
143
+ return f"Extracted from METADATA:\n\n{metadata_section}"
144
+
145
+ # 2. If METADATA is not available, fall back to development environment README.md
146
+ dev_readme_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "README.md"))
147
+ if os.path.exists(dev_readme_path):
148
+ with open(dev_readme_path, encoding="utf-8") as f:
149
+ lines = f.readlines()
150
+ help_args = extract_section(lines)
151
+ if help_args:
152
+ return f"Extracted from README.md ({dev_readme_path}):\n\n{help_args}"
153
+
154
+ if not IS_PYTHON_3_8_OR_NEWER:
155
+ return (
156
+ "Warning: Your Python version is too old and does not support importlib.metadata.\n"
157
+ "Please consider upgrading to Python 3.8 or newer for better compatibility.\n\n"
158
+ "To view any possible arguments, please kindly read the README.md here:\n"
159
+ "https://github.com/mkorpela/pabot"
160
+ )
161
+
162
+ return (
163
+ "Error: README.md or METADATA long_description not found.\n"
164
+ "If you believe this is an issue, please report it at:\n"
165
+ "https://github.com/mkorpela/pabot/issues"
166
+ )
167
+
168
+
169
+ def read_from_metadata():
170
+ """Reads the long_description section from package METADATA if available."""
171
+ if not METADATA_AVAILABLE:
172
+ return None
173
+
174
+ try:
175
+ metadata = importlib.metadata.metadata("robotframework-pabot")
176
+ description = metadata.get("Description", "")
125
177
 
126
- def extract_section(filename, start_marker, end_marker):
127
- with open(filename, "r", encoding="utf-8") as f:
128
- lines = f.readlines()
178
+ if not description:
179
+ return None
129
180
 
181
+ lines = description.splitlines(True)
182
+ return extract_section(lines)
183
+
184
+ except importlib.metadata.PackageNotFoundError:
185
+ return None
186
+
187
+
188
+ def extract_section(lines, start_marker="<!-- START DOCSTRING -->", end_marker="<!-- END DOCSTRING -->"):
189
+ """Extracts content between two markers in a list of lines."""
130
190
  inside_section = False
131
191
  extracted_lines = []
132
192
 
@@ -135,10 +195,9 @@ def extract_section(filename, start_marker, end_marker):
135
195
  inside_section = True
136
196
  continue
137
197
  if end_marker in line:
138
- inside_section = False
139
198
  break
140
199
  if inside_section:
141
- # Add line from README.md without [] and (https: address)
200
+ # Remove Markdown links but keep the text
142
201
  extracted_lines.append(re.sub(r'\[([^\]]+)\]\(https?://[^\)]+\)', r'\1', line))
143
202
 
144
203
  return "".join(extracted_lines).strip()
@@ -205,6 +264,7 @@ def execute_and_wait_with(item):
205
264
  item.index,
206
265
  item.execution_item.type != "test",
207
266
  process_timeout=item.timeout,
267
+ sleep_before_start=item.sleep_before_start
208
268
  )
209
269
  outputxml_preprocessing(
210
270
  item.options, outs_dir, name, item.verbose, _make_id(), caller_id
@@ -263,8 +323,9 @@ def _try_execute_and_wait(
263
323
  my_index=-1,
264
324
  show_stdout_on_failure=False,
265
325
  process_timeout=None,
326
+ sleep_before_start=0
266
327
  ):
267
- # type: (List[str], str, str, bool, int, str, int, bool, Optional[int]) -> None
328
+ # type: (List[str], str, str, bool, int, str, int, bool, Optional[int], int) -> None
268
329
  plib = None
269
330
  is_ignored = False
270
331
  if _pabotlib_in_use():
@@ -282,6 +343,7 @@ def _try_execute_and_wait(
282
343
  my_index,
283
344
  outs_dir,
284
345
  process_timeout,
346
+ sleep_before_start
285
347
  )
286
348
  except:
287
349
  _write(traceback.format_exc())
@@ -464,8 +526,16 @@ def _run(
464
526
  item_index,
465
527
  outs_dir,
466
528
  process_timeout,
529
+ sleep_before_start,
467
530
  ):
468
- # type: (List[str], IO[Any], IO[Any], str, bool, int, int, str, Optional[int]) -> Tuple[Union[subprocess.Popen[bytes], subprocess.Popen], Tuple[int, float]]
531
+ # type: (List[str], IO[Any], IO[Any], str, bool, int, int, str, Optional[int], int) -> Tuple[Union[subprocess.Popen[bytes], subprocess.Popen], Tuple[int, float]]
532
+ timestamp = datetime.datetime.now()
533
+ if sleep_before_start > 0:
534
+ _write(
535
+ "%s [%s] [ID:%s] SLEEPING %s SECONDS BEFORE STARTING %s"
536
+ % (timestamp, pool_id, item_index, sleep_before_start, item_name),
537
+ )
538
+ time.sleep(sleep_before_start)
469
539
  timestamp = datetime.datetime.now()
470
540
  cmd = " ".join(command)
471
541
  if PY2:
@@ -700,6 +770,7 @@ def _group_by_groups(tokens):
700
770
  "Ordering: Group can not contain a group. Encoutered '{'"
701
771
  )
702
772
  group = GroupItem()
773
+ group.set_sleep(token.get_sleep())
703
774
  result.append(group)
704
775
  continue
705
776
  if isinstance(token, GroupEndItem):
@@ -878,6 +949,13 @@ def solve_suite_names(outs_dir, datasources, options, pabot_args):
878
949
  )
879
950
  execution_item_lines = [parse_execution_item_line(l) for l in lines[4:]]
880
951
  if corrupted or h != file_h or file_hash != hash_of_file or pabot_args.get("pabotprerunmodifier"):
952
+ if file_h[0] != h[0] and file_h[2] == h[2]:
953
+ suite_names = _levelsplit(
954
+ generate_suite_names_with_builder(outs_dir, datasources, options),
955
+ pabot_args,
956
+ )
957
+ store_suite_names(h, suite_names)
958
+ return suite_names
881
959
  return _regenerate(
882
960
  file_h,
883
961
  h,
@@ -1678,6 +1756,7 @@ class QueueItem(object):
1678
1756
  self.hive = hive
1679
1757
  self.processes = processes
1680
1758
  self.timeout = timeout
1759
+ self.sleep_before_start = execution_item.get_sleep()
1681
1760
 
1682
1761
  @property
1683
1762
  def index(self):
@@ -1913,7 +1992,7 @@ def main_program(args):
1913
1992
  if pabot_args["help"]:
1914
1993
  help_print = __doc__.replace(
1915
1994
  "PLACEHOLDER_README.MD",
1916
- extract_section("README.md", "<!-- START DOCSTRING -->", "<!-- END DOCSTRING -->")
1995
+ read_args_from_readme()
1917
1996
  )
1918
1997
  print(help_print.replace("[PABOT_VERSION]", PABOT_VERSION))
1919
1998
  return 0
@@ -1945,6 +2024,14 @@ def main_program(args):
1945
2024
  opts_for_run,
1946
2025
  pabot_args,
1947
2026
  )
2027
+ if pabot_args["no-rebot"]:
2028
+ _write((
2029
+ "All tests were executed, but the --no-rebot argument was given, "
2030
+ "so the results were not compiled, and no summary was generated. "
2031
+ f"All results have been saved in the {os.path.join(os.path.curdir, 'pabot_results')} folder."
2032
+ ))
2033
+ _write("===================================================")
2034
+ return 0 if not _ABNORMAL_EXIT_HAPPENED else 252
1948
2035
  result_code = _report_results(
1949
2036
  outs_dir,
1950
2037
  pabot_args,
@@ -1986,6 +2073,8 @@ def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
1986
2073
  ]
1987
2074
  except FileNotFoundError:
1988
2075
  raise DataError("Error: File '%s' not found." % filename)
2076
+ except ValueError as e:
2077
+ raise DataError("Error in ordering file: %s: %s" % (filename, e))
1989
2078
  except:
1990
2079
  raise DataError("Error parsing ordering file '%s'" % filename)
1991
2080
 
@@ -1994,7 +2083,8 @@ def _group_suites(outs_dir, datasources, options, pabot_args):
1994
2083
  suite_names = solve_suite_names(outs_dir, datasources, options, pabot_args)
1995
2084
  _verify_depends(suite_names)
1996
2085
  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)
2086
+ ordering_arg_with_sleep = _set_sleep_times(ordering_arg)
2087
+ ordered_suites = _preserve_order(suite_names, ordering_arg_with_sleep)
1998
2088
  shard_suites = solve_shard_suites(ordered_suites, pabot_args)
1999
2089
  grouped_suites = (
2000
2090
  _chunked_suite_names(shard_suites, pabot_args["processes"])
@@ -2005,6 +2095,29 @@ def _group_suites(outs_dir, datasources, options, pabot_args):
2005
2095
  return grouped_by_depend
2006
2096
 
2007
2097
 
2098
+ def _set_sleep_times(ordering_arg):
2099
+ # type: (List[ExecutionItem]) -> List[ExecutionItem]
2100
+ set_sleep_value = 0
2101
+ in_group = False
2102
+ output = copy.deepcopy(ordering_arg)
2103
+ if output is not None:
2104
+ if len(output) >= 2:
2105
+ for i in range(len(output) - 1):
2106
+ if isinstance(output[i], SleepItem):
2107
+ set_sleep_value = output[i].get_sleep()
2108
+ else:
2109
+ set_sleep_value = 0
2110
+ if isinstance(output[i], GroupStartItem):
2111
+ in_group = True
2112
+ if isinstance(output[i], GroupEndItem):
2113
+ in_group = False
2114
+ if isinstance(output[i + 1], GroupStartItem) and set_sleep_value > 0:
2115
+ output[i + 1].set_sleep(set_sleep_value)
2116
+ if isinstance(output[i + 1], RunnableItem) and set_sleep_value > 0 and not in_group:
2117
+ output[i + 1].set_sleep(set_sleep_value)
2118
+ return output
2119
+
2120
+
2008
2121
  def _chunked_suite_names(suite_names, processes):
2009
2122
  q, r = divmod(len(suite_names), processes)
2010
2123
  result = []
@@ -0,0 +1,365 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotframework-pabot
3
+ Version: 4.2.0
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
+ Dynamic: license-file
26
+
27
+ # Pabot
28
+
29
+ [中文版](README_zh.md)
30
+
31
+ [![Version](https://img.shields.io/pypi/v/robotframework-pabot.svg)](https://pypi.python.org/pypi/robotframework-pabot)
32
+ [![Downloads](http://pepy.tech/badge/robotframework-pabot)](http://pepy.tech/project/robotframework-pabot)
33
+
34
+ <img src="https://raw.githubusercontent.com/mkorpela/pabot/master/pabot.png" width="100">
35
+
36
+ ----
37
+
38
+ 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.
39
+
40
+ [![Pabot presentation at robocon.io 2018](http://img.youtube.com/vi/i0RV6SJSIn8/0.jpg)](https://youtu.be/i0RV6SJSIn8 "Pabot presentation at robocon.io 2018")
41
+
42
+ ## Installation:
43
+
44
+ From PyPi:
45
+
46
+ pip install -U robotframework-pabot
47
+
48
+ OR clone this repository and run:
49
+
50
+ setup.py install
51
+
52
+ OR clone this repository and run:
53
+
54
+ pip install --editable .
55
+
56
+ ## Basic use
57
+
58
+ Split execution to suite files.
59
+
60
+ pabot [path to tests]
61
+
62
+ Split execution on test level.
63
+
64
+ pabot --testlevelsplit [path to tests]
65
+
66
+ Run same tests with two different configurations.
67
+
68
+ pabot --argumentfile1 first.args --argumentfile2 second.args [path to tests]
69
+
70
+ For more complex cases please read onward.
71
+
72
+ ## Contact
73
+
74
+ Join [Pabot Slack channel](https://robotframework.slack.com/messages/C7HKR2L6L) in Robot Framework slack.
75
+ [Get invite to Robot Framework slack](https://robotframework-slack-invite.herokuapp.com/).
76
+
77
+
78
+ ## Contributing to the project
79
+
80
+ There are several ways you can help in improving this tool:
81
+
82
+ - Report an issue or an improvement idea to the [issue tracker](https://github.com/mkorpela/pabot/issues)
83
+ - Contribute by programming and making a pull request (easiest way is to work on an issue from the issue tracker)
84
+
85
+ ## Command-line options
86
+ <!-- START DOCSTRING -->
87
+ pabot [--verbose|--testlevelsplit|--command .. --end-command|
88
+ --processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
89
+ --processtimeout num|
90
+ --shard i/n|
91
+ --artifacts extensions|--artifactsinsubfolders|
92
+ --resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file|
93
+ --chunk|
94
+ --pabotprerunmodifier modifier|
95
+ --no-rebot|
96
+ --help|--version]
97
+ [robot options] [path ...]
98
+
99
+ PabotLib remote server is started by default to enable locking and resource distribution between parallel test executions.
100
+
101
+ Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following pabot options:
102
+
103
+ --verbose
104
+ More output from the parallel execution.
105
+
106
+ --testlevelsplit
107
+ Split execution on test level instead of default suite level. If .pabotsuitenames contains both tests and suites then
108
+ this will only affect new suites and split only them. Leaving this flag out when both suites and tests in
109
+ .pabotsuitenames file will also only affect new suites and add them as suite files.
110
+
111
+ --command [ACTUAL COMMANDS TO START ROBOT EXECUTOR] --end-command
112
+ RF script for situations where robot is not used directly.
113
+
114
+ --processes [NUMBER OF PROCESSES]
115
+ How many parallel executors to use (default max of 2 and cpu count). Special option "all" will use as many processes as
116
+ there are executable suites or tests.
117
+
118
+ --no-pabotlib
119
+ Disable the PabotLib remote server if you don't need locking or resource distribution features.
120
+
121
+ --pabotlibhost [HOSTNAME]
122
+ Connect to an already running instance of the PabotLib remote server at the given host (disables the local PabotLib
123
+ server start). For example, to connect to a remote PabotLib server running on another machine:
124
+
125
+ pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 tests/
126
+
127
+ The remote server can be also started and executed separately from pabot instances:
128
+
129
+ python -m pabot.pabotlib <path_to_resourcefile> <host> <port>
130
+ python -m pabot.pabotlib resource.txt 192.168.1.123 8271
131
+
132
+ This enables sharing a resource with multiple Robot Framework instances.
133
+
134
+ --pabotlibport [PORT]
135
+ Port number of the PabotLib remote server (default is 8270). See --pabotlibhost for more information.
136
+
137
+ --processtimeout [TIMEOUT]
138
+ Maximum time in seconds to wait for a process before killing it. If not set, there's no timeout.
139
+
140
+ --shard [INDEX]/[TOTAL]
141
+ Optionally split execution into smaller pieces. This can be used for distributing testing to multiple machines.
142
+
143
+ --artifacts [FILE EXTENSIONS]
144
+ List of file extensions (comma separated). Defines which files (screenshots, videos etc.) from separate reporting
145
+ directories would be copied and included in a final report. Possible links to copied files in RF log would be updated
146
+ (only relative paths supported). The default value is `png`.
147
+
148
+ Examples:
149
+
150
+ --artifacts png,mp4,txt
151
+
152
+ --artifactsinsubfolders
153
+ Copy artifacts located not only directly in the RF output dir, but also in it's sub-folders.
154
+
155
+ --resourcefile [FILEPATH]
156
+ Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
157
+ pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
158
+
159
+ --argumentfile [INTEGER] [FILEPATH]
160
+ Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
161
+
162
+ For example:
163
+
164
+ --argumentfile1 arg1.txt --argumentfile2 arg2.txt
165
+
166
+ --suitesfrom [FILEPATH TO OUTPUTXML]
167
+ Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
168
+ before shorter ones.
169
+
170
+ --ordering [FILE PATH]
171
+ Optionally give execution order from a file.
172
+
173
+ --chunk
174
+ Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
175
+ setups and teardowns.
176
+
177
+ --pabotprerunmodifier [PRERUNMODIFIER MODULE OR CLASS]
178
+ Like Robot Framework's --prerunmodifier, but executed only once in the pabot's main process after all other
179
+ --prerunmodifiers. But unlike the regular --prerunmodifier command, --pabotprerunmodifier is not executed again in each
180
+ pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
181
+ example, to modify the list of tests to be performed.
182
+
183
+ --no-rebot
184
+ If specified, the tests will execute as usual, but Rebot will not be called to merge the logs. This option is designed
185
+ for scenarios where Rebot should be run later due to large log files, ensuring better memory and resource availability.
186
+ Subprocess results are stored in the pabot_results folder.
187
+
188
+ --help
189
+ Print usage instructions.
190
+
191
+ --version
192
+ Print version information.
193
+
194
+ Example usages:
195
+
196
+ pabot test_directory
197
+ pabot --exclude FOO directory_to_tests
198
+ pabot --command java -jar robotframework.jar --end-command --include SMOKE tests
199
+ pabot --processes 10 tests
200
+ pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 --processes 10 tests
201
+ pabot --artifacts png,mp4,txt --artifactsinsubfolders directory_to_tests
202
+ # To disable PabotLib:
203
+ pabot --no-pabotlib tests
204
+
205
+ <!-- END DOCSTRING -->
206
+ ### PabotLib
207
+
208
+ pabot.PabotLib provides keywords that will help communication and data sharing between the executor processes.
209
+ 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.
210
+
211
+ PabotLib Docs are located at https://pabot.org/PabotLib.html.
212
+
213
+ ### PabotLib example:
214
+
215
+ test.robot
216
+
217
+ *** Settings ***
218
+ Library pabot.PabotLib
219
+
220
+ *** Test Case ***
221
+ Testing PabotLib
222
+ Acquire Lock MyLock
223
+ Log This part is critical section
224
+ Release Lock MyLock
225
+ ${valuesetname}= Acquire Value Set admin-server
226
+ ${host}= Get Value From Set host
227
+ ${username}= Get Value From Set username
228
+ ${password}= Get Value From Set password
229
+ Log Do something with the values (for example access host with username and password)
230
+ Release Value Set
231
+ Log After value set release others can obtain the variable values
232
+
233
+ valueset.dat
234
+
235
+ [Server1]
236
+ tags=admin-server
237
+ HOST=123.123.123.123
238
+ USERNAME=user1
239
+ PASSWORD=password1
240
+
241
+ [Server2]
242
+ tags=server
243
+ HOST=121.121.121.121
244
+ USERNAME=user2
245
+ PASSWORD=password2
246
+
247
+ [Server3]
248
+ tags=admin-server
249
+ HOST=222.222.222.222
250
+ USERNAME=user3
251
+ PASSWORD=password4
252
+
253
+
254
+ pabot call using resources from valueset.dat
255
+
256
+ pabot --pabotlib --resourcefile valueset.dat test.robot
257
+
258
+ ### Controlling execution order and level of parallelism
259
+
260
+ .pabotsuitenames file contains the list of suites that will be executed.
261
+ File is created during pabot execution if not already there.
262
+ The file is a cache that pabot uses when re-executing same tests to speed up processing.
263
+ This file can be partially manually edited but easier option is to use ```--ordering FILENAME```.
264
+ First 4 rows contain information that should not be edited - pabot will edit these when something changes.
265
+ After this come the suite names.
266
+
267
+ 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.
268
+
269
+ There different possibilities to influence the execution:
270
+
271
+ * The order of suites can be changed.
272
+ * If a directory (or a directory structure) should be executed sequentially, add the directory suite name to a row as a ```--suite``` option.
273
+ * 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.
274
+ * You can add a line with text `#WAIT` to force executor to wait until all previous suites have been executed.
275
+ * You can group suites and tests together to same executor process by adding line `{` before the group and `}`after.
276
+ * 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.
277
+
278
+ ```
279
+ --test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
280
+ --test robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
281
+ --test robotTest.2 Lists.Test with Keywords and a list
282
+ #WAIT
283
+ --test robotTest.2 Lists.Test with a Keyword that accepts multiple arguments
284
+ --test robotTest.2 Lists.Test with some Collections keywords
285
+ --test robotTest.2 Lists.Test to access list entries
286
+ --test robotTest.3 Dictionary.Test that accesses Dictionaries
287
+ --test robotTest.3 Dictionary.Dictionaries for named arguments #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
288
+ --test robotTest.1 Scalar.Test Case With Variables #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
289
+ --test robotTest.1 Scalar.Test with Numbers #DEPENDS robotTest.1 Scalar.Test With Arguments and Return Values
290
+ --test robotTest.1 Scalar.Test Case with Return Values #DEPENDS robotTest.1 Scalar.Test with Numbers
291
+ --test robotTest.1 Scalar.Test With Arguments and Return Values
292
+ --test robotTest.3 Dictionary.Test with Dictionaries as Arguments
293
+ --test robotTest.3 Dictionary.Test with FOR loops and Dictionaries #DEPENDS robotTest.1 Scalar.Test Case with Return Values
294
+ ```
295
+
296
+ * By using the command `#SLEEP X`, where `X` is an integer in the range [0-3600] (in seconds), you can
297
+ define a startup delay for each subprocess. `#SLEEP` affects the next line unless the next line starts a
298
+ group with `{`, in which case the delay applies to the entire group. If the next line begins with `--test`
299
+ or `--suite`, the delay is applied to that specific item. Any other occurrences of `#SLEEP` are ignored.
300
+
301
+ The following example clarifies the behavior:
302
+
303
+ ```sh
304
+ pabot --process 2 --ordering order.txt data_1
305
+ ```
306
+
307
+ where order.txt is:
308
+
309
+ ```
310
+ #SLEEP 1
311
+ {
312
+ #SLEEP 2
313
+ --suite Data 1.suite A
314
+ #SLEEP 3
315
+ --suite Data 1.suite B
316
+ #SLEEP 4
317
+ }
318
+ #SLEEP 5
319
+ #SLEEP 6
320
+ --suite Data 1.suite C
321
+ #SLEEP 7
322
+ --suite Data 1.suite D
323
+ #SLEEP 8
324
+ ```
325
+
326
+ prints something like this:
327
+
328
+ ```
329
+ 2025-02-15 19:15:00.408321 [0] [ID:1] SLEEPING 6 SECONDS BEFORE STARTING Data 1.suite C
330
+ 2025-02-15 19:15:00.408321 [1] [ID:0] SLEEPING 1 SECONDS BEFORE STARTING Group_Data 1.suite A_Data 1.suite B
331
+ 2025-02-15 19:15:01.409389 [PID:52008] [1] [ID:0] EXECUTING Group_Data 1.suite A_Data 1.suite B
332
+ 2025-02-15 19:15:06.409024 [PID:1528] [0] [ID:1] EXECUTING Data 1.suite C
333
+ 2025-02-15 19:15:09.257564 [PID:52008] [1] [ID:0] PASSED Group_Data 1.suite A_Data 1.suite B in 7.8 seconds
334
+ 2025-02-15 19:15:09.259067 [1] [ID:2] SLEEPING 7 SECONDS BEFORE STARTING Data 1.suite D
335
+ 2025-02-15 19:15:09.647342 [PID:1528] [0] [ID:1] PASSED Data 1.suite C in 3.2 seconds
336
+ 2025-02-15 19:15:16.260432 [PID:48156] [1] [ID:2] EXECUTING Data 1.suite D
337
+ 2025-02-15 19:15:18.696420 [PID:48156] [1] [ID:2] PASSED Data 1.suite D in 2.4 seconds
338
+ ```
339
+
340
+ ### Programmatic use
341
+
342
+ Library offers an endpoint `main_program` that will not call `sys.exit`. This can help in developing your own python program around pabot.
343
+
344
+ ```Python
345
+ import sys
346
+ from pabot.pabot import main_program
347
+
348
+ def amazing_new_program():
349
+ print("Before calling pabot")
350
+ exit_code = main_program(['tests'])
351
+ print(f"After calling pabot (return code {exit_code})")
352
+ sys.exit(exit_code)
353
+
354
+ ```
355
+
356
+ ### Global variables
357
+
358
+ 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.
359
+
360
+ PABOTQUEUEINDEX - this contains a unique index number for the execution. Indexes start from 0.
361
+ PABOTLIBURI - this contains the URI for the running PabotLib server
362
+ 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.
363
+ PABOTNUMBEROFPROCESSES - max number of concurrent processes that pabot may use in execution.
364
+ CALLER_ID - a universally unique identifier for this execution.
365
+
@@ -1,10 +1,10 @@
1
1
  pabot/SharedLibrary.py,sha256=mIipGs3ZhKYEakKprcbrMI4P_Un6qI8gE7086xpHaLY,2552
2
- pabot/__init__.py,sha256=yBQqfUgp6NEiDxMJ80_JZcdX9DSOJKt_-R-hAiiT23c,94
3
- pabot/arguments.py,sha256=F0EDLv2qYqmw2_kOSUIyJGGwwRSgEwyQp-uGdoTlAFo,6816
2
+ pabot/__init__.py,sha256=tl2NUIH_fh3hf0eeIfL8G-zT-rzPKeBtyYVBzqUc-0g,200
3
+ pabot/arguments.py,sha256=1r_rZKzgi8BY4NqSkgWveR0A0dkAR62Epq3jY1JFjsM,6973
4
4
  pabot/clientwrapper.py,sha256=yz7battGs0exysnDeLDWJuzpb2Q-qSjitwxZMO2TlJw,231
5
5
  pabot/coordinatorwrapper.py,sha256=nQQ7IowD6c246y8y9nsx0HZbt8vS2XODhPVDjm-lyi0,195
6
- pabot/execution_items.py,sha256=qs15SZjGE5CQAO0UpNuVWZ60wTLdWGDK1CkbBz7JtfQ,8892
7
- pabot/pabot.py,sha256=GBP_LoO5BcvkX5A0Z4BX3fidoD0SGhBzjhQYajkeyf8,68417
6
+ pabot/execution_items.py,sha256=hw8s44JkHOFW-iIktaLmW1zt456GvmDCEG9yk-bqG2Y,9694
7
+ pabot/pabot.py,sha256=Hh_6tPy2vSIasrxu5E6XustcvsfFZirTMPFlfFiVFIE,73074
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.1.0.dist-info/LICENSE.txt,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
18
- robotframework_pabot-4.1.0.dist-info/METADATA,sha256=jP70LRXHbIDdkpV8h0x1H31nh7Gk59qIGc5IdsJmMA4,1019
19
- robotframework_pabot-4.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
- robotframework_pabot-4.1.0.dist-info/entry_points.txt,sha256=JpAIFADTeFOQWdwmn56KpAil8V3-41ZC5ICXCYm3Ng0,43
21
- robotframework_pabot-4.1.0.dist-info/top_level.txt,sha256=t3OwfEAsSxyxrhjy_GCJYHKbV_X6AIsgeLhYeHvObG4,6
22
- robotframework_pabot-4.1.0.dist-info/RECORD,,
17
+ robotframework_pabot-4.2.0.dist-info/licenses/LICENSE.txt,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
18
+ robotframework_pabot-4.2.0.dist-info/METADATA,sha256=koUhxphmN3h_7iSn2JF7oS25LXOAy4H-n84yq5CQZPY,15434
19
+ robotframework_pabot-4.2.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
20
+ robotframework_pabot-4.2.0.dist-info/entry_points.txt,sha256=JpAIFADTeFOQWdwmn56KpAil8V3-41ZC5ICXCYm3Ng0,43
21
+ robotframework_pabot-4.2.0.dist-info/top_level.txt,sha256=t3OwfEAsSxyxrhjy_GCJYHKbV_X6AIsgeLhYeHvObG4,6
22
+ robotframework_pabot-4.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,25 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: robotframework-pabot
3
- Version: 4.1.0
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
- Dynamic: download-url
24
-
25
- A parallel executor for Robot Framework tests. With Pabot you can split one execution into multiple and save test execution time.