robotframework-pabot 4.1.1__py3-none-any.whl → 4.3.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 +7 -2
- pabot/arguments.py +72 -0
- pabot/execution_items.py +81 -1
- pabot/pabot.py +154 -69
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/METADATA +71 -13
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/RECORD +10 -10
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/WHEEL +1 -1
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/entry_points.txt +0 -0
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info/licenses}/LICENSE.txt +0 -0
- {robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/top_level.txt +0 -0
pabot/__init__.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
10
|
+
__version__ = "4.3.0"
|
pabot/arguments.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import multiprocessing
|
|
3
|
+
import os
|
|
4
|
+
import glob
|
|
2
5
|
import re
|
|
6
|
+
import tempfile
|
|
3
7
|
from typing import Dict, List, Optional, Tuple
|
|
4
8
|
|
|
5
9
|
from robot import __version__ as ROBOT_VERSION
|
|
@@ -16,6 +20,7 @@ from .execution_items import (
|
|
|
16
20
|
SuiteItem,
|
|
17
21
|
TestItem,
|
|
18
22
|
WaitItem,
|
|
23
|
+
SleepItem,
|
|
19
24
|
)
|
|
20
25
|
|
|
21
26
|
ARGSMATCHER = re.compile(r"--argumentfile(\d+)")
|
|
@@ -65,9 +70,72 @@ def parse_args(
|
|
|
65
70
|
options_for_subprocesses["name"] = "Suites"
|
|
66
71
|
opts = _delete_none_keys(options)
|
|
67
72
|
opts_sub = _delete_none_keys(options_for_subprocesses)
|
|
73
|
+
_replace_arg_files(pabot_args, opts_sub)
|
|
68
74
|
return opts, datasources, pabot_args, opts_sub
|
|
69
75
|
|
|
70
76
|
|
|
77
|
+
# remove options from argumentfile according to different scenarios.
|
|
78
|
+
# -t/--test/--task shall be removed if --testlevelsplit options exists
|
|
79
|
+
# -s/--suite shall be removed if --testlevelsplit options does not exist
|
|
80
|
+
def _replace_arg_files(pabot_args, opts_sub):
|
|
81
|
+
_cleanup_old_pabot_temp_files()
|
|
82
|
+
if not opts_sub.get('argumentfile') or not opts_sub['argumentfile']:
|
|
83
|
+
return
|
|
84
|
+
arg_file_list = opts_sub['argumentfile']
|
|
85
|
+
temp_file_list = []
|
|
86
|
+
test_level = pabot_args.get('testlevelsplit')
|
|
87
|
+
|
|
88
|
+
for arg_file_path in arg_file_list:
|
|
89
|
+
with open(arg_file_path, 'r') as arg_file:
|
|
90
|
+
arg_file_lines = arg_file.readlines()
|
|
91
|
+
if not arg_file_lines:
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
fd, temp_path = tempfile.mkstemp(prefix="pabot_temp_", suffix=".txt")
|
|
95
|
+
with os.fdopen(fd, 'wb') as temp_file:
|
|
96
|
+
for line in arg_file_lines:
|
|
97
|
+
if test_level and _is_test_option(line):
|
|
98
|
+
continue
|
|
99
|
+
elif not test_level and _is_suite_option(line):
|
|
100
|
+
continue
|
|
101
|
+
temp_file.write(line.encode('utf-8'))
|
|
102
|
+
|
|
103
|
+
temp_file_list.append(temp_path)
|
|
104
|
+
|
|
105
|
+
opts_sub['argumentfile'] = temp_file_list
|
|
106
|
+
atexit.register(cleanup_temp_file, temp_file_list)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _is_suite_option(line):
|
|
110
|
+
return line.startswith('-s ') or line.startswith('--suite ')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _is_test_option(line):
|
|
114
|
+
return line.startswith('-t ') or line.startswith('--test ') or line.startswith('--task ')
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# clean the temp argument files before exiting the pabot process
|
|
118
|
+
def cleanup_temp_file(temp_file_list):
|
|
119
|
+
for temp_file in temp_file_list:
|
|
120
|
+
if os.path.exists(temp_file):
|
|
121
|
+
try:
|
|
122
|
+
os.remove(temp_file)
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Deletes all possible pabot_temp_ files from os temp directory
|
|
128
|
+
def _cleanup_old_pabot_temp_files():
|
|
129
|
+
temp_dir = tempfile.gettempdir()
|
|
130
|
+
pattern = os.path.join(temp_dir, "pabot_temp_*.txt")
|
|
131
|
+
old_files = glob.glob(pattern)
|
|
132
|
+
for file_path in old_files:
|
|
133
|
+
try:
|
|
134
|
+
os.remove(file_path)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
|
|
71
139
|
def _parse_shard(arg):
|
|
72
140
|
# type: (str) -> Tuple[int, int]
|
|
73
141
|
parts = arg.split("/")
|
|
@@ -91,6 +159,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
91
159
|
"shardindex": 0,
|
|
92
160
|
"shardcount": 1,
|
|
93
161
|
"chunk": False,
|
|
162
|
+
"no-rebot": False,
|
|
94
163
|
}
|
|
95
164
|
# Explicitly define argument types for validation
|
|
96
165
|
flag_args = {
|
|
@@ -100,6 +169,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
|
|
|
100
169
|
"pabotlib",
|
|
101
170
|
"artifactsinsubfolders",
|
|
102
171
|
"chunk",
|
|
172
|
+
"no-rebot"
|
|
103
173
|
}
|
|
104
174
|
value_args = {
|
|
105
175
|
"hive": str,
|
|
@@ -217,6 +287,8 @@ def parse_execution_item_line(text): # type: (str) -> ExecutionItem
|
|
|
217
287
|
if text.startswith("DYNAMICTEST"):
|
|
218
288
|
suite, test = text[12:].split(" :: ")
|
|
219
289
|
return DynamicTestItem(test, suite)
|
|
290
|
+
if text.startswith("#SLEEP "):
|
|
291
|
+
return SleepItem(text[7:])
|
|
220
292
|
if text == "#WAIT":
|
|
221
293
|
return WaitItem()
|
|
222
294
|
if text == "{":
|
pabot/execution_items.py
CHANGED
|
@@ -7,11 +7,46 @@ from robot.utils import PY2, is_unicode
|
|
|
7
7
|
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
def create_dependency_tree(items):
|
|
12
|
+
# type: (List[ExecutionItem]) -> List[List[ExecutionItem]]
|
|
13
|
+
independent_tests = list(filter(lambda item: not item.depends, items))
|
|
14
|
+
dependency_tree = [independent_tests]
|
|
15
|
+
dependent_tests = list(filter(lambda item: item.depends, items))
|
|
16
|
+
unknown_dependent_tests = dependent_tests
|
|
17
|
+
while len(unknown_dependent_tests) > 0:
|
|
18
|
+
run_in_this_stage, run_later = [], []
|
|
19
|
+
for d in unknown_dependent_tests:
|
|
20
|
+
stage_indexes = []
|
|
21
|
+
for i, stage in enumerate(dependency_tree):
|
|
22
|
+
for test in stage:
|
|
23
|
+
if test.name in d.depends:
|
|
24
|
+
stage_indexes.append(i)
|
|
25
|
+
# All #DEPENDS test are already run:
|
|
26
|
+
if len(stage_indexes) == len(d.depends):
|
|
27
|
+
run_in_this_stage.append(d)
|
|
28
|
+
else:
|
|
29
|
+
run_later.append(d)
|
|
30
|
+
unknown_dependent_tests = run_later
|
|
31
|
+
if len(run_in_this_stage) == 0:
|
|
32
|
+
text = "There are circular or unmet dependencies using #DEPENDS. Check this/these test(s): " + str(run_later)
|
|
33
|
+
raise DataError(text)
|
|
34
|
+
else:
|
|
35
|
+
dependency_tree.append(run_in_this_stage)
|
|
36
|
+
flattened_dependency_tree = sum(dependency_tree, [])
|
|
37
|
+
if len(flattened_dependency_tree) != len(items):
|
|
38
|
+
raise DataError(
|
|
39
|
+
"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions."
|
|
40
|
+
)
|
|
41
|
+
return dependency_tree
|
|
42
|
+
|
|
43
|
+
|
|
10
44
|
@total_ordering
|
|
11
45
|
class ExecutionItem(object):
|
|
12
46
|
isWait = False
|
|
13
47
|
type = None # type: str
|
|
14
48
|
name = None # type: str
|
|
49
|
+
sleep = 0 # type: int
|
|
15
50
|
|
|
16
51
|
def top_name(self):
|
|
17
52
|
# type: () -> str
|
|
@@ -29,6 +64,14 @@ class ExecutionItem(object):
|
|
|
29
64
|
# type: () -> str
|
|
30
65
|
return ""
|
|
31
66
|
|
|
67
|
+
def set_sleep(self, sleep_time):
|
|
68
|
+
# type: (int) -> None
|
|
69
|
+
self.sleep = sleep_time
|
|
70
|
+
|
|
71
|
+
def get_sleep(self):
|
|
72
|
+
# type: () -> int
|
|
73
|
+
return self.sleep
|
|
74
|
+
|
|
32
75
|
def modify_options_for_executor(self, options):
|
|
33
76
|
options[self.type] = self.name
|
|
34
77
|
|
|
@@ -69,7 +112,7 @@ class GroupItem(ExecutionItem):
|
|
|
69
112
|
type = "group"
|
|
70
113
|
|
|
71
114
|
def __init__(self):
|
|
72
|
-
self.name = "
|
|
115
|
+
self.name = "Group"
|
|
73
116
|
self._items = []
|
|
74
117
|
self._element_type = None
|
|
75
118
|
|
|
@@ -82,11 +125,26 @@ class GroupItem(ExecutionItem):
|
|
|
82
125
|
)
|
|
83
126
|
if len(self._items) > 0:
|
|
84
127
|
self.name += "_"
|
|
128
|
+
if self.get_sleep() < item.get_sleep():
|
|
129
|
+
self.set_sleep(item.get_sleep())
|
|
85
130
|
self.name += item.name
|
|
86
131
|
self._element_type = item.type
|
|
87
132
|
self._items.append(item)
|
|
133
|
+
|
|
134
|
+
def change_items_order_by_depends(self):
|
|
135
|
+
ordered_name = "Group"
|
|
136
|
+
dependency_tree = create_dependency_tree(self._items)
|
|
137
|
+
ordered = [item for sublist in dependency_tree for item in sublist]
|
|
138
|
+
for item in ordered:
|
|
139
|
+
ordered_name += f"_{item.name}"
|
|
140
|
+
self.name = ordered_name
|
|
141
|
+
self._items = ordered
|
|
88
142
|
|
|
89
143
|
def modify_options_for_executor(self, options):
|
|
144
|
+
# Since a GroupItem contains either tests or suites, options are cleared
|
|
145
|
+
# and only the Group's content is used to avoid duplicate execution.
|
|
146
|
+
options['suite'] = []
|
|
147
|
+
options['test'] = []
|
|
90
148
|
for item in self._items:
|
|
91
149
|
if item.type not in options:
|
|
92
150
|
options[item.type] = []
|
|
@@ -120,6 +178,7 @@ class RunnableItem(ExecutionItem):
|
|
|
120
178
|
if len(depends_indexes) == 0
|
|
121
179
|
else line_name[0:depends_indexes[0]].strip()
|
|
122
180
|
)
|
|
181
|
+
assert len(self.name) != 0, f"Suite or test name cannot be empty and then contain #DEPENDS like: {name}"
|
|
123
182
|
self.depends = (
|
|
124
183
|
self._split_dependencies(line_name, depends_indexes)
|
|
125
184
|
if len(depends_indexes) != 0
|
|
@@ -197,6 +256,8 @@ class TestItem(RunnableItem):
|
|
|
197
256
|
def modify_options_for_executor(self, options):
|
|
198
257
|
if "rerunfailed" in options:
|
|
199
258
|
del options["rerunfailed"]
|
|
259
|
+
if "rerunfailedsuites" in options:
|
|
260
|
+
del options["rerunfailedsuites"]
|
|
200
261
|
name = self.name
|
|
201
262
|
for char in ["[", "?", "*"]:
|
|
202
263
|
name = name.replace(char, "[" + char + "]")
|
|
@@ -207,6 +268,8 @@ class TestItem(RunnableItem):
|
|
|
207
268
|
def modify_options_for_executor(self, options):
|
|
208
269
|
if "rerunfailed" in options:
|
|
209
270
|
del options["rerunfailed"]
|
|
271
|
+
if "rerunfailedsuites" in options:
|
|
272
|
+
del options["rerunfailedsuites"]
|
|
210
273
|
|
|
211
274
|
def difference(self, from_items):
|
|
212
275
|
# type: (List[ExecutionItem]) -> List[ExecutionItem]
|
|
@@ -274,6 +337,23 @@ class WaitItem(ExecutionItem):
|
|
|
274
337
|
return self.name
|
|
275
338
|
|
|
276
339
|
|
|
340
|
+
class SleepItem(ExecutionItem):
|
|
341
|
+
type = "sleep"
|
|
342
|
+
|
|
343
|
+
def __init__(self, time):
|
|
344
|
+
try:
|
|
345
|
+
assert 3600 >= int(time) >= 0 # 1 h max.
|
|
346
|
+
self.name = time
|
|
347
|
+
self.sleep = int(time)
|
|
348
|
+
except ValueError:
|
|
349
|
+
raise ValueError("#SLEEP value %s is not integer" % time)
|
|
350
|
+
except AssertionError:
|
|
351
|
+
raise ValueError("#SLEEP value %s is not in between 0 and 3600" % time)
|
|
352
|
+
|
|
353
|
+
def line(self):
|
|
354
|
+
return "#SLEEP " + self.name
|
|
355
|
+
|
|
356
|
+
|
|
277
357
|
class GroupStartItem(ExecutionItem):
|
|
278
358
|
type = "group"
|
|
279
359
|
|
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,8 @@ from .execution_items import (
|
|
|
78
79
|
SuiteItems,
|
|
79
80
|
TestItem,
|
|
80
81
|
RunnableItem,
|
|
82
|
+
SleepItem,
|
|
83
|
+
create_dependency_tree,
|
|
81
84
|
)
|
|
82
85
|
from .result_merger import merge
|
|
83
86
|
|
|
@@ -262,6 +265,7 @@ def execute_and_wait_with(item):
|
|
|
262
265
|
item.index,
|
|
263
266
|
item.execution_item.type != "test",
|
|
264
267
|
process_timeout=item.timeout,
|
|
268
|
+
sleep_before_start=item.sleep_before_start
|
|
265
269
|
)
|
|
266
270
|
outputxml_preprocessing(
|
|
267
271
|
item.options, outs_dir, name, item.verbose, _make_id(), caller_id
|
|
@@ -287,7 +291,9 @@ def _create_command_for_execution(caller_id, datasources, is_last, item, outs_di
|
|
|
287
291
|
item.last_level,
|
|
288
292
|
item.processes,
|
|
289
293
|
)
|
|
290
|
-
|
|
294
|
+
# If the datasource ends with a backslash '\', it is deleted to ensure
|
|
295
|
+
# correct handling of the escape character later on.
|
|
296
|
+
+ [os.path.normpath(s) for s in datasources]
|
|
291
297
|
)
|
|
292
298
|
return _mapOptionalQuote(cmd)
|
|
293
299
|
|
|
@@ -320,8 +326,9 @@ def _try_execute_and_wait(
|
|
|
320
326
|
my_index=-1,
|
|
321
327
|
show_stdout_on_failure=False,
|
|
322
328
|
process_timeout=None,
|
|
329
|
+
sleep_before_start=0
|
|
323
330
|
):
|
|
324
|
-
# type: (List[str], str, str, bool, int, str, int, bool, Optional[int]) -> None
|
|
331
|
+
# type: (List[str], str, str, bool, int, str, int, bool, Optional[int], int) -> None
|
|
325
332
|
plib = None
|
|
326
333
|
is_ignored = False
|
|
327
334
|
if _pabotlib_in_use():
|
|
@@ -339,6 +346,7 @@ def _try_execute_and_wait(
|
|
|
339
346
|
my_index,
|
|
340
347
|
outs_dir,
|
|
341
348
|
process_timeout,
|
|
349
|
+
sleep_before_start
|
|
342
350
|
)
|
|
343
351
|
except:
|
|
344
352
|
_write(traceback.format_exc())
|
|
@@ -521,8 +529,16 @@ def _run(
|
|
|
521
529
|
item_index,
|
|
522
530
|
outs_dir,
|
|
523
531
|
process_timeout,
|
|
532
|
+
sleep_before_start,
|
|
524
533
|
):
|
|
525
|
-
# type: (List[str], IO[Any], IO[Any], str, bool, int, int, str, Optional[int]) -> Tuple[Union[subprocess.Popen[bytes], subprocess.Popen], Tuple[int, float]]
|
|
534
|
+
# 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]]
|
|
535
|
+
timestamp = datetime.datetime.now()
|
|
536
|
+
if sleep_before_start > 0:
|
|
537
|
+
_write(
|
|
538
|
+
"%s [%s] [ID:%s] SLEEPING %s SECONDS BEFORE STARTING %s"
|
|
539
|
+
% (timestamp, pool_id, item_index, sleep_before_start, item_name),
|
|
540
|
+
)
|
|
541
|
+
time.sleep(sleep_before_start)
|
|
526
542
|
timestamp = datetime.datetime.now()
|
|
527
543
|
cmd = " ".join(command)
|
|
528
544
|
if PY2:
|
|
@@ -748,6 +764,7 @@ def _options_to_cli_arguments(opts): # type: (dict) -> List[str]
|
|
|
748
764
|
|
|
749
765
|
|
|
750
766
|
def _group_by_groups(tokens):
|
|
767
|
+
# type: (List[ExecutionItem]) -> List[ExecutionItem]
|
|
751
768
|
result = []
|
|
752
769
|
group = None
|
|
753
770
|
for token in tokens:
|
|
@@ -757,6 +774,7 @@ def _group_by_groups(tokens):
|
|
|
757
774
|
"Ordering: Group can not contain a group. Encoutered '{'"
|
|
758
775
|
)
|
|
759
776
|
group = GroupItem()
|
|
777
|
+
group.set_sleep(token.get_sleep())
|
|
760
778
|
result.append(group)
|
|
761
779
|
continue
|
|
762
780
|
if isinstance(token, GroupEndItem):
|
|
@@ -764,6 +782,7 @@ def _group_by_groups(tokens):
|
|
|
764
782
|
raise DataError(
|
|
765
783
|
"Ordering: Group end tag '}' encountered before start '{'"
|
|
766
784
|
)
|
|
785
|
+
group.change_items_order_by_depends()
|
|
767
786
|
group = None
|
|
768
787
|
continue
|
|
769
788
|
if group is not None:
|
|
@@ -935,6 +954,13 @@ def solve_suite_names(outs_dir, datasources, options, pabot_args):
|
|
|
935
954
|
)
|
|
936
955
|
execution_item_lines = [parse_execution_item_line(l) for l in lines[4:]]
|
|
937
956
|
if corrupted or h != file_h or file_hash != hash_of_file or pabot_args.get("pabotprerunmodifier"):
|
|
957
|
+
if file_h is not None and file_h[0] != h[0] and file_h[2] == h[2]:
|
|
958
|
+
suite_names = _levelsplit(
|
|
959
|
+
generate_suite_names_with_builder(outs_dir, datasources, options),
|
|
960
|
+
pabot_args,
|
|
961
|
+
)
|
|
962
|
+
store_suite_names(h, suite_names)
|
|
963
|
+
return suite_names
|
|
938
964
|
return _regenerate(
|
|
939
965
|
file_h,
|
|
940
966
|
h,
|
|
@@ -964,7 +990,8 @@ def _levelsplit(
|
|
|
964
990
|
|
|
965
991
|
|
|
966
992
|
def _group_by_wait(lines):
|
|
967
|
-
|
|
993
|
+
# type: (List[ExecutionItem]) -> List[List[ExecutionItem]]
|
|
994
|
+
suites = [[]]
|
|
968
995
|
for suite in lines:
|
|
969
996
|
if not suite.isWait:
|
|
970
997
|
if suite:
|
|
@@ -1067,8 +1094,6 @@ def _fix_items(items): # type: (List[ExecutionItem]) -> List[ExecutionItem]
|
|
|
1067
1094
|
_remove_empty_groups(result)
|
|
1068
1095
|
if result and result[0].isWait:
|
|
1069
1096
|
result = result[1:]
|
|
1070
|
-
if result and result[-1].isWait:
|
|
1071
|
-
result = result[:-1]
|
|
1072
1097
|
return result
|
|
1073
1098
|
|
|
1074
1099
|
|
|
@@ -1313,6 +1338,8 @@ def _options_for_rebot(options, start_time_string, end_time_string):
|
|
|
1313
1338
|
rebot_options["test"] = []
|
|
1314
1339
|
rebot_options["exclude"] = []
|
|
1315
1340
|
rebot_options["include"] = []
|
|
1341
|
+
if rebot_options.get("runemptysuite"):
|
|
1342
|
+
rebot_options["processemptysuite"] = True
|
|
1316
1343
|
if ROBOT_VERSION >= "2.8":
|
|
1317
1344
|
options["monitormarkers"] = "off"
|
|
1318
1345
|
for key in [
|
|
@@ -1337,6 +1364,7 @@ def _options_for_rebot(options, start_time_string, end_time_string):
|
|
|
1337
1364
|
"randomize",
|
|
1338
1365
|
"runemptysuite",
|
|
1339
1366
|
"rerunfailed",
|
|
1367
|
+
"rerunfailedsuites",
|
|
1340
1368
|
"skip",
|
|
1341
1369
|
"skiponfailure",
|
|
1342
1370
|
"skipteardownonexit",
|
|
@@ -1455,6 +1483,19 @@ def _copy_output_artifacts(options, file_extensions=None, include_subfolders=Fal
|
|
|
1455
1483
|
return copied_artifacts
|
|
1456
1484
|
|
|
1457
1485
|
|
|
1486
|
+
def _check_pabot_results_for_missing_xml(base_dir):
|
|
1487
|
+
missing = []
|
|
1488
|
+
for root, dirs, _ in os.walk(base_dir):
|
|
1489
|
+
if root == base_dir:
|
|
1490
|
+
for subdir in dirs:
|
|
1491
|
+
subdir_path = os.path.join(base_dir, subdir)
|
|
1492
|
+
has_xml = any(fname.endswith('.xml') for fname in os.listdir(subdir_path))
|
|
1493
|
+
if not has_xml:
|
|
1494
|
+
missing.append(os.path.join(subdir_path, 'robot_stderr.out'))
|
|
1495
|
+
break
|
|
1496
|
+
return missing
|
|
1497
|
+
|
|
1498
|
+
|
|
1458
1499
|
def _report_results(outs_dir, pabot_args, options, start_time_string, tests_root_name):
|
|
1459
1500
|
if "pythonpath" in options:
|
|
1460
1501
|
del options["pythonpath"]
|
|
@@ -1470,6 +1511,7 @@ def _report_results(outs_dir, pabot_args, options, start_time_string, tests_root
|
|
|
1470
1511
|
"failed": 0,
|
|
1471
1512
|
"skipped": 0,
|
|
1472
1513
|
}
|
|
1514
|
+
missing_outputs = []
|
|
1473
1515
|
if pabot_args["argumentfiles"]:
|
|
1474
1516
|
outputs = [] # type: List[str]
|
|
1475
1517
|
for index, _ in pabot_args["argumentfiles"]:
|
|
@@ -1486,14 +1528,27 @@ def _report_results(outs_dir, pabot_args, options, start_time_string, tests_root
|
|
|
1486
1528
|
outputfile=os.path.join("pabot_results", "output%s.xml" % index),
|
|
1487
1529
|
)
|
|
1488
1530
|
]
|
|
1531
|
+
missing_outputs.extend(_check_pabot_results_for_missing_xml(os.path.join(outs_dir, index)))
|
|
1489
1532
|
if "output" not in options:
|
|
1490
1533
|
options["output"] = "output.xml"
|
|
1491
1534
|
_write_stats(stats)
|
|
1492
|
-
|
|
1535
|
+
exit_code = rebot(*outputs, **_options_for_rebot(options, start_time_string, _now()))
|
|
1493
1536
|
else:
|
|
1494
|
-
|
|
1537
|
+
exit_code = _report_results_for_one_run(
|
|
1495
1538
|
outs_dir, pabot_args, options, start_time_string, tests_root_name, stats
|
|
1496
1539
|
)
|
|
1540
|
+
missing_outputs.extend(_check_pabot_results_for_missing_xml(outs_dir))
|
|
1541
|
+
if missing_outputs:
|
|
1542
|
+
_write(("[ " + _wrap_with(Color.YELLOW, 'WARNING') + " ] "
|
|
1543
|
+
"One or more subprocesses encountered an error and the "
|
|
1544
|
+
"internal .xml files could not be generated. Please check the "
|
|
1545
|
+
"following stderr files to identify the cause:"))
|
|
1546
|
+
for missing in missing_outputs:
|
|
1547
|
+
_write(repr(missing))
|
|
1548
|
+
_write((f"[ " + _wrap_with(Color.RED, 'ERROR') + " ] "
|
|
1549
|
+
"The output, log and report files produced by Pabot are "
|
|
1550
|
+
"incomplete and do not contain all test cases."))
|
|
1551
|
+
return exit_code if not missing_outputs else 252
|
|
1497
1552
|
|
|
1498
1553
|
|
|
1499
1554
|
def _write_stats(stats):
|
|
@@ -1528,9 +1583,9 @@ def _report_results_for_one_run(
|
|
|
1528
1583
|
_write_stats(stats)
|
|
1529
1584
|
if (
|
|
1530
1585
|
"report" in options
|
|
1531
|
-
and options["report"] == "NONE"
|
|
1586
|
+
and options["report"].upper() == "NONE"
|
|
1532
1587
|
and "log" in options
|
|
1533
|
-
and options["log"] == "NONE"
|
|
1588
|
+
and options["log"].upper() == "NONE"
|
|
1534
1589
|
):
|
|
1535
1590
|
options[
|
|
1536
1591
|
"output"
|
|
@@ -1735,6 +1790,7 @@ class QueueItem(object):
|
|
|
1735
1790
|
self.hive = hive
|
|
1736
1791
|
self.processes = processes
|
|
1737
1792
|
self.timeout = timeout
|
|
1793
|
+
self.sleep_before_start = execution_item.get_sleep()
|
|
1738
1794
|
|
|
1739
1795
|
@property
|
|
1740
1796
|
def index(self):
|
|
@@ -1845,19 +1901,28 @@ def _chunk_items(items, chunk_size):
|
|
|
1845
1901
|
base_item = chunked_items[0]
|
|
1846
1902
|
if not base_item:
|
|
1847
1903
|
continue
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
execution_items
|
|
1854
|
-
base_item
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1904
|
+
if isinstance(base_item.execution_item, TestItem):
|
|
1905
|
+
for item in chunked_items:
|
|
1906
|
+
chunked_item = _queue_item(base_item, item.execution_item)
|
|
1907
|
+
yield chunked_item
|
|
1908
|
+
else:
|
|
1909
|
+
execution_items = SuiteItems([item.execution_item for item in chunked_items])
|
|
1910
|
+
chunked_item = _queue_item(base_item, execution_items)
|
|
1911
|
+
yield chunked_item
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
def _queue_item(base_item, execution_items):
|
|
1915
|
+
return QueueItem(
|
|
1916
|
+
base_item.datasources,
|
|
1917
|
+
base_item.outs_dir,
|
|
1918
|
+
base_item.options,
|
|
1919
|
+
execution_items,
|
|
1920
|
+
base_item.command,
|
|
1921
|
+
base_item.verbose,
|
|
1922
|
+
(base_item.argfile_index, base_item.argfile),
|
|
1923
|
+
processes=base_item.processes,
|
|
1924
|
+
timeout=base_item.timeout,
|
|
1925
|
+
)
|
|
1861
1926
|
|
|
1862
1927
|
|
|
1863
1928
|
def _find_ending_level(name, group):
|
|
@@ -1969,8 +2034,8 @@ def main_program(args):
|
|
|
1969
2034
|
options, datasources, pabot_args, opts_for_run = parse_args(args)
|
|
1970
2035
|
if pabot_args["help"]:
|
|
1971
2036
|
help_print = __doc__.replace(
|
|
1972
|
-
|
|
1973
|
-
|
|
2037
|
+
"PLACEHOLDER_README.MD",
|
|
2038
|
+
read_args_from_readme()
|
|
1974
2039
|
)
|
|
1975
2040
|
print(help_print.replace("[PABOT_VERSION]", PABOT_VERSION))
|
|
1976
2041
|
return 0
|
|
@@ -2002,6 +2067,14 @@ def main_program(args):
|
|
|
2002
2067
|
opts_for_run,
|
|
2003
2068
|
pabot_args,
|
|
2004
2069
|
)
|
|
2070
|
+
if pabot_args["no-rebot"]:
|
|
2071
|
+
_write((
|
|
2072
|
+
"All tests were executed, but the --no-rebot argument was given, "
|
|
2073
|
+
"so the results were not compiled, and no summary was generated. "
|
|
2074
|
+
f"All results have been saved in the {outs_dir} folder."
|
|
2075
|
+
))
|
|
2076
|
+
_write("===================================================")
|
|
2077
|
+
return 0 if not _ABNORMAL_EXIT_HAPPENED else 252
|
|
2005
2078
|
result_code = _report_results(
|
|
2006
2079
|
outs_dir,
|
|
2007
2080
|
pabot_args,
|
|
@@ -2039,19 +2112,40 @@ def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
|
|
|
2039
2112
|
with open(filename, "r") as orderingfile:
|
|
2040
2113
|
return [
|
|
2041
2114
|
parse_execution_item_line(line.strip())
|
|
2042
|
-
for line in orderingfile.readlines()
|
|
2115
|
+
for line in orderingfile.readlines() if line.strip() != ""
|
|
2043
2116
|
]
|
|
2044
2117
|
except FileNotFoundError:
|
|
2045
2118
|
raise DataError("Error: File '%s' not found." % filename)
|
|
2046
|
-
except:
|
|
2119
|
+
except (ValueError, AssertionError) as e:
|
|
2120
|
+
raise DataError("Error in ordering file: %s: %s" % (filename, e))
|
|
2121
|
+
except Exception:
|
|
2047
2122
|
raise DataError("Error parsing ordering file '%s'" % filename)
|
|
2048
2123
|
|
|
2049
2124
|
|
|
2125
|
+
def _check_ordering(ordering_file, suite_names): # type: (List[ExecutionItem], List[ExecutionItem]) -> None
|
|
2126
|
+
list_of_suite_names = [s.name for s in suite_names]
|
|
2127
|
+
number_of_tests_or_suites = 0
|
|
2128
|
+
if ordering_file:
|
|
2129
|
+
for item in ordering_file:
|
|
2130
|
+
if item.type in ['suite', 'test']:
|
|
2131
|
+
if not any((s == item.name or s.endswith("." + item.name)) for s in list_of_suite_names):
|
|
2132
|
+
# If test name is too long, it gets name ' Invalid', so skip that
|
|
2133
|
+
if item.name != ' Invalid':
|
|
2134
|
+
raise DataError("%s item '%s' in --ordering file does not match suite or test names in .pabotsuitenames file.\nPlease verify content of --ordering file." % (item.type.title(), item.name))
|
|
2135
|
+
number_of_tests_or_suites += 1
|
|
2136
|
+
if number_of_tests_or_suites > len(list_of_suite_names):
|
|
2137
|
+
raise DataError('Ordering file contains more tests and/or suites than exists. Check that there is no duplicates etc. in ordering file and that to .pabotsuitenames.')
|
|
2138
|
+
|
|
2139
|
+
|
|
2050
2140
|
def _group_suites(outs_dir, datasources, options, pabot_args):
|
|
2051
2141
|
suite_names = solve_suite_names(outs_dir, datasources, options, pabot_args)
|
|
2052
2142
|
_verify_depends(suite_names)
|
|
2053
2143
|
ordering_arg = _parse_ordering(pabot_args.get("ordering")) if (pabot_args.get("ordering")) is not None else None
|
|
2054
|
-
|
|
2144
|
+
if ordering_arg:
|
|
2145
|
+
_verify_depends(ordering_arg)
|
|
2146
|
+
_check_ordering(ordering_arg, suite_names)
|
|
2147
|
+
ordering_arg_with_sleep = _set_sleep_times(ordering_arg)
|
|
2148
|
+
ordered_suites = _preserve_order(suite_names, ordering_arg_with_sleep)
|
|
2055
2149
|
shard_suites = solve_shard_suites(ordered_suites, pabot_args)
|
|
2056
2150
|
grouped_suites = (
|
|
2057
2151
|
_chunked_suite_names(shard_suites, pabot_args["processes"])
|
|
@@ -2062,6 +2156,29 @@ def _group_suites(outs_dir, datasources, options, pabot_args):
|
|
|
2062
2156
|
return grouped_by_depend
|
|
2063
2157
|
|
|
2064
2158
|
|
|
2159
|
+
def _set_sleep_times(ordering_arg):
|
|
2160
|
+
# type: (List[ExecutionItem]) -> List[ExecutionItem]
|
|
2161
|
+
set_sleep_value = 0
|
|
2162
|
+
in_group = False
|
|
2163
|
+
output = copy.deepcopy(ordering_arg)
|
|
2164
|
+
if output is not None:
|
|
2165
|
+
if len(output) >= 2:
|
|
2166
|
+
for i in range(len(output) - 1):
|
|
2167
|
+
if isinstance(output[i], SleepItem):
|
|
2168
|
+
set_sleep_value = output[i].get_sleep()
|
|
2169
|
+
else:
|
|
2170
|
+
set_sleep_value = 0
|
|
2171
|
+
if isinstance(output[i], GroupStartItem):
|
|
2172
|
+
in_group = True
|
|
2173
|
+
if isinstance(output[i], GroupEndItem):
|
|
2174
|
+
in_group = False
|
|
2175
|
+
if isinstance(output[i + 1], GroupStartItem) and set_sleep_value > 0:
|
|
2176
|
+
output[i + 1].set_sleep(set_sleep_value)
|
|
2177
|
+
if isinstance(output[i + 1], RunnableItem) and set_sleep_value > 0 and not in_group:
|
|
2178
|
+
output[i + 1].set_sleep(set_sleep_value)
|
|
2179
|
+
return output
|
|
2180
|
+
|
|
2181
|
+
|
|
2065
2182
|
def _chunked_suite_names(suite_names, processes):
|
|
2066
2183
|
q, r = divmod(len(suite_names), processes)
|
|
2067
2184
|
result = []
|
|
@@ -2086,7 +2203,7 @@ def _verify_depends(suite_names):
|
|
|
2086
2203
|
suites_with_found_dependencies = list(
|
|
2087
2204
|
filter(
|
|
2088
2205
|
lambda suite: any(
|
|
2089
|
-
runnable_suite.name
|
|
2206
|
+
runnable_suite.name in suite.depends
|
|
2090
2207
|
for runnable_suite in runnable_suites
|
|
2091
2208
|
),
|
|
2092
2209
|
suites_with_depends,
|
|
@@ -2097,63 +2214,31 @@ def _verify_depends(suite_names):
|
|
|
2097
2214
|
"Invalid test configuration: Some test suites have dependencies (#DEPENDS) that cannot be found."
|
|
2098
2215
|
)
|
|
2099
2216
|
suites_with_circular_dependencies = list(
|
|
2100
|
-
filter(lambda suite: suite.
|
|
2217
|
+
filter(lambda suite: suite.name in suite.depends, suites_with_depends)
|
|
2101
2218
|
)
|
|
2102
2219
|
if suites_with_circular_dependencies:
|
|
2103
2220
|
raise DataError(
|
|
2104
2221
|
"Invalid test configuration: Test suites cannot depend on themselves."
|
|
2105
2222
|
)
|
|
2106
|
-
grouped_suites = list(
|
|
2107
|
-
filter(lambda suite: isinstance(suite, GroupItem), suite_names)
|
|
2108
|
-
)
|
|
2109
|
-
if grouped_suites and suites_with_depends:
|
|
2110
|
-
raise DataError(
|
|
2111
|
-
"Invalid test configuration: Cannot use both #DEPENDS and grouped suites."
|
|
2112
|
-
)
|
|
2113
2223
|
|
|
2114
2224
|
|
|
2115
2225
|
def _group_by_depend(suite_names):
|
|
2226
|
+
# type: (List[ExecutionItem]) -> List[List[ExecutionItem]]
|
|
2116
2227
|
group_items = list(filter(lambda suite: isinstance(suite, GroupItem), suite_names))
|
|
2117
2228
|
runnable_suites = list(
|
|
2118
2229
|
filter(lambda suite: isinstance(suite, RunnableItem), suite_names)
|
|
2119
2230
|
)
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
dependency_tree = [independent_tests]
|
|
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)
|
|
2145
|
-
flattened_dependency_tree = sum(dependency_tree, [])
|
|
2146
|
-
if len(flattened_dependency_tree) != len(runnable_suites):
|
|
2147
|
-
raise DataError(
|
|
2148
|
-
"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions."
|
|
2149
|
-
)
|
|
2231
|
+
dependency_tree = create_dependency_tree(runnable_suites)
|
|
2232
|
+
# Since groups cannot depend on others, they are placed at the beginning.
|
|
2233
|
+
dependency_tree[0][0:0] = group_items
|
|
2150
2234
|
return dependency_tree
|
|
2151
2235
|
|
|
2152
2236
|
|
|
2153
2237
|
def _all_grouped_suites_by_depend(grouped_suites):
|
|
2238
|
+
# type: (List[List[ExecutionItem]]) -> List[List[ExecutionItem]]
|
|
2154
2239
|
grouped_by_depend = []
|
|
2155
|
-
for group_suite in grouped_suites:
|
|
2156
|
-
grouped_by_depend
|
|
2240
|
+
for group_suite in grouped_suites: # These groups are divided by #WAIT
|
|
2241
|
+
grouped_by_depend.extend(_group_by_depend(group_suite))
|
|
2157
2242
|
return grouped_by_depend
|
|
2158
2243
|
|
|
2159
2244
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-pabot
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: robotframework>=3.2
|
|
|
22
22
|
Requires-Dist: robotframework-stacktrace>=0.4.1
|
|
23
23
|
Requires-Dist: natsort>=8.2.0
|
|
24
24
|
Dynamic: download-url
|
|
25
|
+
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
# Pabot
|
|
27
28
|
|
|
@@ -88,9 +89,10 @@ pabot [--verbose|--testlevelsplit|--command .. --end-command|
|
|
|
88
89
|
--processtimeout num|
|
|
89
90
|
--shard i/n|
|
|
90
91
|
--artifacts extensions|--artifactsinsubfolders|
|
|
91
|
-
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file
|
|
92
|
-
--chunk
|
|
93
|
-
--pabotprerunmodifier modifier
|
|
92
|
+
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file|
|
|
93
|
+
--chunk|
|
|
94
|
+
--pabotprerunmodifier modifier|
|
|
95
|
+
--no-rebot|
|
|
94
96
|
--help|--version]
|
|
95
97
|
[robot options] [path ...]
|
|
96
98
|
|
|
@@ -154,7 +156,7 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
|
|
|
154
156
|
Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
|
|
155
157
|
pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
|
|
156
158
|
|
|
157
|
-
--argumentfile
|
|
159
|
+
--argumentfile[INTEGER] [FILEPATH]
|
|
158
160
|
Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
|
|
159
161
|
|
|
160
162
|
For example:
|
|
@@ -178,11 +180,16 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
|
|
|
178
180
|
pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
|
|
179
181
|
example, to modify the list of tests to be performed.
|
|
180
182
|
|
|
181
|
-
|
|
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.
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
--version
|
|
192
|
+
Print version information.
|
|
186
193
|
|
|
187
194
|
Example usages:
|
|
188
195
|
|
|
@@ -259,14 +266,18 @@ After this come the suite names.
|
|
|
259
266
|
|
|
260
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.
|
|
261
268
|
|
|
269
|
+
Note: The `--ordering` file is intended only for defining the execution order of suites and tests. The actual selection of what to run must still be done using options like `--test`, `--suite`, `--include`, or `--exclude`.
|
|
270
|
+
|
|
262
271
|
There different possibilities to influence the execution:
|
|
263
272
|
|
|
264
273
|
* The order of suites can be changed.
|
|
265
274
|
* If a directory (or a directory structure) should be executed sequentially, add the directory suite name to a row as a ```--suite``` option.
|
|
266
275
|
* 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
|
|
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.
|
|
276
|
+
* You can add a line with text to force executor to wait until all previous suites have been executed.
|
|
277
|
+
* You can group suites and tests together to same executor process by adding line `{` before the group and `}` after. Note that `#WAIT` cannot be used inside a group.
|
|
278
|
+
* You can introduce dependencies using the word `#DEPENDS` after a test declaration. This keyword 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. Note that each `#WAIT` splits suites into separate execution blocks, and it's not possible to define dependencies for suites or tests that are inside another `#WAIT` block or inside another `{}` brackets.
|
|
279
|
+
* Note: Within a group `{}`, neither execution order nor the `#DEPENDS` keyword currently works. This is due to limitations in Robot Framework, which is invoked within Pabot subprocesses. These limitations may be addressed in a future release of Robot Framework. For now, tests or suites within a group will be executed in the order Robot Framework discovers them — typically in alphabetical order.
|
|
280
|
+
* An example could be:
|
|
270
281
|
|
|
271
282
|
```
|
|
272
283
|
--test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
|
|
@@ -274,8 +285,10 @@ There different possibilities to influence the execution:
|
|
|
274
285
|
--test robotTest.2 Lists.Test with Keywords and a list
|
|
275
286
|
#WAIT
|
|
276
287
|
--test robotTest.2 Lists.Test with a Keyword that accepts multiple arguments
|
|
288
|
+
{
|
|
277
289
|
--test robotTest.2 Lists.Test with some Collections keywords
|
|
278
290
|
--test robotTest.2 Lists.Test to access list entries
|
|
291
|
+
}
|
|
279
292
|
--test robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
280
293
|
--test robotTest.3 Dictionary.Dictionaries for named arguments #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
281
294
|
--test robotTest.1 Scalar.Test Case With Variables #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
@@ -284,6 +297,51 @@ There different possibilities to influence the execution:
|
|
|
284
297
|
--test robotTest.1 Scalar.Test With Arguments and Return Values
|
|
285
298
|
--test robotTest.3 Dictionary.Test with Dictionaries as Arguments
|
|
286
299
|
--test robotTest.3 Dictionary.Test with FOR loops and Dictionaries #DEPENDS robotTest.1 Scalar.Test Case with Return Values
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
* By using the command `#SLEEP X`, where `X` is an integer in the range [0-3600] (in seconds), you can
|
|
303
|
+
define a startup delay for each subprocess. `#SLEEP` affects the next line unless the next line starts a
|
|
304
|
+
group with `{`, in which case the delay applies to the entire group. If the next line begins with `--test`
|
|
305
|
+
or `--suite`, the delay is applied to that specific item. Any other occurrences of `#SLEEP` are ignored.
|
|
306
|
+
Note that `#SLEEP` has no effect within a group, i.e., inside a subprocess.
|
|
307
|
+
|
|
308
|
+
The following example clarifies the behavior:
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
pabot --processes 2 --ordering order.txt data_1
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
where order.txt is:
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
#SLEEP 1
|
|
318
|
+
{
|
|
319
|
+
#SLEEP 2
|
|
320
|
+
--suite Data 1.suite A
|
|
321
|
+
#SLEEP 3
|
|
322
|
+
--suite Data 1.suite B
|
|
323
|
+
#SLEEP 4
|
|
324
|
+
}
|
|
325
|
+
#SLEEP 5
|
|
326
|
+
#SLEEP 6
|
|
327
|
+
--suite Data 1.suite C
|
|
328
|
+
#SLEEP 7
|
|
329
|
+
--suite Data 1.suite D
|
|
330
|
+
#SLEEP 8
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
prints something like this:
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
2025-02-15 19:15:00.408321 [0] [ID:1] SLEEPING 6 SECONDS BEFORE STARTING Data 1.suite C
|
|
337
|
+
2025-02-15 19:15:00.408321 [1] [ID:0] SLEEPING 1 SECONDS BEFORE STARTING Group_Data 1.suite A_Data 1.suite B
|
|
338
|
+
2025-02-15 19:15:01.409389 [PID:52008] [1] [ID:0] EXECUTING Group_Data 1.suite A_Data 1.suite B
|
|
339
|
+
2025-02-15 19:15:06.409024 [PID:1528] [0] [ID:1] EXECUTING Data 1.suite C
|
|
340
|
+
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
|
|
341
|
+
2025-02-15 19:15:09.259067 [1] [ID:2] SLEEPING 7 SECONDS BEFORE STARTING Data 1.suite D
|
|
342
|
+
2025-02-15 19:15:09.647342 [PID:1528] [0] [ID:1] PASSED Data 1.suite C in 3.2 seconds
|
|
343
|
+
2025-02-15 19:15:16.260432 [PID:48156] [1] [ID:2] EXECUTING Data 1.suite D
|
|
344
|
+
2025-02-15 19:15:18.696420 [PID:48156] [1] [ID:2] PASSED Data 1.suite D in 2.4 seconds
|
|
287
345
|
```
|
|
288
346
|
|
|
289
347
|
### Programmatic use
|
|
@@ -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=uuwf2W7FsicbiL8y-0LayoZ4Ztsqa2N2yJAgcz2EUoE,200
|
|
3
|
+
pabot/arguments.py,sha256=m38y8mXKJ5BHlxSrsEI0gXlkzR5hv88G9i-FL_BouQ4,9168
|
|
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=HCd54LsIEZJjnL0TZC_tuac2DSVL4JHes6veJlpCE94,12058
|
|
7
|
+
pabot/pabot.py,sha256=GtoxhW3NTIE7It8IXtl-m6t0x0GkuT9kx2YkQs4OWhE,74910
|
|
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.3.0.dist-info/licenses/LICENSE.txt,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
18
|
+
robotframework_pabot-4.3.0.dist-info/METADATA,sha256=coN7rNN_R3aY76vawuow6gGITRFi1Jc3nOeobID27gw,16410
|
|
19
|
+
robotframework_pabot-4.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
robotframework_pabot-4.3.0.dist-info/entry_points.txt,sha256=JpAIFADTeFOQWdwmn56KpAil8V3-41ZC5ICXCYm3Ng0,43
|
|
21
|
+
robotframework_pabot-4.3.0.dist-info/top_level.txt,sha256=t3OwfEAsSxyxrhjy_GCJYHKbV_X6AIsgeLhYeHvObG4,6
|
|
22
|
+
robotframework_pabot-4.3.0.dist-info/RECORD,,
|
{robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{robotframework_pabot-4.1.1.dist-info → robotframework_pabot-4.3.0.dist-info/licenses}/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|