robotframework-pabot 4.0.6__py3-none-any.whl → 4.1.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
@@ -2,4 +2,4 @@ from __future__ import absolute_import
2
2
 
3
3
  from .pabotlib import PabotLib
4
4
 
5
- __version__ = "4.0.6"
5
+ __version__ = "4.1.0"
pabot/arguments.py CHANGED
@@ -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": _parse_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:
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
- depends_begin_index = line_name.find(self.depends_keyword)
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 depends_begin_index == -1
109
- else line_name[0:depends_begin_index].strip()
120
+ if len(depends_indexes) == 0
121
+ else line_name[0:depends_indexes[0]].strip()
110
122
  )
111
123
  self.depends = (
112
- line_name[depends_begin_index + len(self.depends_keyword) :].strip()
113
- if depends_begin_index != -1
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
- line_without_depends + " " + self.depends_keyword + " " + self.depends
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
- Supports all Robot Framework command line options and also following
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
- print(__doc__.replace("[PABOT_VERSION]", PABOT_VERSION))
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
- print(__doc__.replace("[PABOT_VERSION]", PABOT_VERSION))
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
- ordered_suites = _preserve_order(suite_names, pabot_args.get("ordering"))
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
- while True:
2087
- dependent_tests = list(filter(lambda suite: suite.depends, runnable_suites))
2088
- dependent_on_last_stage = list(
2089
- filter(
2090
- lambda suite: any(
2091
- test_in_tier_before.name == suite.depends
2092
- for test_in_tier_before in dependency_tree[-1]
2093
- ),
2094
- dependent_tests,
2095
- )
2096
- )
2097
- if not dependent_on_last_stage:
2098
- break
2099
- dependency_tree += [dependent_on_last_stage]
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(
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: robotframework-pabot
3
- Version: 4.0.6
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.
@@ -1,10 +1,10 @@
1
1
  pabot/SharedLibrary.py,sha256=mIipGs3ZhKYEakKprcbrMI4P_Un6qI8gE7086xpHaLY,2552
2
- pabot/__init__.py,sha256=elJbj_En-artFbF8FUbxiP7LhJwfmeOz_G2a4FTLO8c,94
3
- pabot/arguments.py,sha256=PQuJiRYwkfhzbQLat4rOflfAvDONuBAWhu1SOmVV858,7149
2
+ pabot/__init__.py,sha256=yBQqfUgp6NEiDxMJ80_JZcdX9DSOJKt_-R-hAiiT23c,94
3
+ pabot/arguments.py,sha256=F0EDLv2qYqmw2_kOSUIyJGGwwRSgEwyQp-uGdoTlAFo,6816
4
4
  pabot/clientwrapper.py,sha256=yz7battGs0exysnDeLDWJuzpb2Q-qSjitwxZMO2TlJw,231
5
5
  pabot/coordinatorwrapper.py,sha256=nQQ7IowD6c246y8y9nsx0HZbt8vS2XODhPVDjm-lyi0,195
6
- pabot/execution_items.py,sha256=ktPoGx0yXK6Q_4A8CYW3OFCnCyZQnJb8cPYZULmwyAU,8320
7
- pabot/pabot.py,sha256=QPzClIudUFWZIMRkWjiqGFFVDBXP-40bWUG5PbjJaX4,68695
6
+ pabot/execution_items.py,sha256=qs15SZjGE5CQAO0UpNuVWZ60wTLdWGDK1CkbBz7JtfQ,8892
7
+ pabot/pabot.py,sha256=GBP_LoO5BcvkX5A0Z4BX3fidoD0SGhBzjhQYajkeyf8,68417
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.0.6.dist-info/LICENSE.txt,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
18
- robotframework_pabot-4.0.6.dist-info/METADATA,sha256=jsSx4f-wusBvzPDEXAJzRkEmhGTkpSxbHJi7EuGJR9k,997
19
- robotframework_pabot-4.0.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
- robotframework_pabot-4.0.6.dist-info/entry_points.txt,sha256=JpAIFADTeFOQWdwmn56KpAil8V3-41ZC5ICXCYm3Ng0,43
21
- robotframework_pabot-4.0.6.dist-info/top_level.txt,sha256=t3OwfEAsSxyxrhjy_GCJYHKbV_X6AIsgeLhYeHvObG4,6
22
- robotframework_pabot-4.0.6.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5