robotframework-pabot 4.1.1__tar.gz → 4.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robotframework_pabot-4.1.1/src/robotframework_pabot.egg-info → robotframework_pabot-4.3.0}/PKG-INFO +71 -13
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/README.md +68 -11
- robotframework_pabot-4.3.0/pyproject.toml +3 -0
- robotframework_pabot-4.3.0/setup.py +14 -0
- robotframework_pabot-4.3.0/src/pabot/__init__.py +10 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/arguments.py +72 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/execution_items.py +81 -1
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/pabot.py +154 -69
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0/src/robotframework_pabot.egg-info}/PKG-INFO +71 -13
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/robotframework_pabot.egg-info/SOURCES.txt +3 -0
- robotframework_pabot-4.3.0/tests/test_arguments_output.py +260 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_depends.py +75 -2
- robotframework_pabot-4.3.0/tests/test_missing_subprocess_output.py +134 -0
- robotframework_pabot-4.3.0/tests/test_ordering.py +444 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_pabot.py +12 -3
- robotframework_pabot-4.3.0/tests/test_run_empty_suite.py +74 -0
- robotframework_pabot-4.3.0/tests/test_suite_structure.py +232 -0
- robotframework_pabot-4.1.1/pyproject.toml +0 -3
- robotframework_pabot-4.1.1/setup.py +0 -5
- robotframework_pabot-4.1.1/src/pabot/__init__.py +0 -5
- robotframework_pabot-4.1.1/tests/test_arguments_output.py +0 -67
- robotframework_pabot-4.1.1/tests/test_ordering.py +0 -194
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/LICENSE.txt +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/MANIFEST.in +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/setup.cfg +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/SharedLibrary.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/clientwrapper.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/coordinatorwrapper.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/pabotlib.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/py3/__init__.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/py3/client.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/py3/coordinator.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/py3/messages.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/py3/worker.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/result_merger.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/robotremoteserver.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/pabot/workerwrapper.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/robotframework_pabot.egg-info/requires.txt +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_basic_arguments.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_functional.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_pabotlib.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_pabotprerunmodifier.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_pabotsuitenames_io.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_prerunmodifier.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_resultmerger.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_stacktrace.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_testlevelsplit_include.py +0 -0
- {robotframework_pabot-4.1.1 → robotframework_pabot-4.3.0}/tests/test_testlevelsplit_output_task_order.py +0 -0
{robotframework_pabot-4.1.1/src/robotframework_pabot.egg-info → robotframework_pabot-4.3.0}/PKG-INFO
RENAMED
|
@@ -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
|
|
@@ -63,9 +63,10 @@ pabot [--verbose|--testlevelsplit|--command .. --end-command|
|
|
|
63
63
|
--processtimeout num|
|
|
64
64
|
--shard i/n|
|
|
65
65
|
--artifacts extensions|--artifactsinsubfolders|
|
|
66
|
-
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file
|
|
67
|
-
--chunk
|
|
68
|
-
--pabotprerunmodifier modifier
|
|
66
|
+
--resourcefile file|--argumentfile[num] file|--suitesfrom file|--ordering file|
|
|
67
|
+
--chunk|
|
|
68
|
+
--pabotprerunmodifier modifier|
|
|
69
|
+
--no-rebot|
|
|
69
70
|
--help|--version]
|
|
70
71
|
[robot options] [path ...]
|
|
71
72
|
|
|
@@ -129,7 +130,7 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
|
|
|
129
130
|
Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
|
|
130
131
|
pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
|
|
131
132
|
|
|
132
|
-
--argumentfile
|
|
133
|
+
--argumentfile[INTEGER] [FILEPATH]
|
|
133
134
|
Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
|
|
134
135
|
|
|
135
136
|
For example:
|
|
@@ -153,11 +154,16 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
|
|
|
153
154
|
pabot subprocesses. Depending on the intended use, this may be desirable as well as more efficient. Can be used, for
|
|
154
155
|
example, to modify the list of tests to be performed.
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
--no-rebot
|
|
158
|
+
If specified, the tests will execute as usual, but Rebot will not be called to merge the logs. This option is designed
|
|
159
|
+
for scenarios where Rebot should be run later due to large log files, ensuring better memory and resource availability.
|
|
160
|
+
Subprocess results are stored in the pabot_results folder.
|
|
161
|
+
|
|
162
|
+
--help
|
|
163
|
+
Print usage instructions.
|
|
158
164
|
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
--version
|
|
166
|
+
Print version information.
|
|
161
167
|
|
|
162
168
|
Example usages:
|
|
163
169
|
|
|
@@ -234,14 +240,18 @@ After this come the suite names.
|
|
|
234
240
|
|
|
235
241
|
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.
|
|
236
242
|
|
|
243
|
+
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`.
|
|
244
|
+
|
|
237
245
|
There different possibilities to influence the execution:
|
|
238
246
|
|
|
239
247
|
* The order of suites can be changed.
|
|
240
248
|
* If a directory (or a directory structure) should be executed sequentially, add the directory suite name to a row as a ```--suite``` option.
|
|
241
249
|
* 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.
|
|
242
|
-
* You can add a line with text
|
|
243
|
-
* You can group suites and tests together to same executor process by adding line `{` before the group and `}`after.
|
|
244
|
-
* You can introduce dependencies using the word `#DEPENDS` after a test declaration.
|
|
250
|
+
* You can add a line with text to force executor to wait until all previous suites have been executed.
|
|
251
|
+
* 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.
|
|
252
|
+
* 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.
|
|
253
|
+
* 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.
|
|
254
|
+
* An example could be:
|
|
245
255
|
|
|
246
256
|
```
|
|
247
257
|
--test robotTest.1 Scalar.Test With Environment Variables #DEPENDS robotTest.1 Scalar.Test with BuiltIn Variables of Robot Framework
|
|
@@ -249,8 +259,10 @@ There different possibilities to influence the execution:
|
|
|
249
259
|
--test robotTest.2 Lists.Test with Keywords and a list
|
|
250
260
|
#WAIT
|
|
251
261
|
--test robotTest.2 Lists.Test with a Keyword that accepts multiple arguments
|
|
262
|
+
{
|
|
252
263
|
--test robotTest.2 Lists.Test with some Collections keywords
|
|
253
264
|
--test robotTest.2 Lists.Test to access list entries
|
|
265
|
+
}
|
|
254
266
|
--test robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
255
267
|
--test robotTest.3 Dictionary.Dictionaries for named arguments #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
256
268
|
--test robotTest.1 Scalar.Test Case With Variables #DEPENDS robotTest.3 Dictionary.Test that accesses Dictionaries
|
|
@@ -259,6 +271,51 @@ There different possibilities to influence the execution:
|
|
|
259
271
|
--test robotTest.1 Scalar.Test With Arguments and Return Values
|
|
260
272
|
--test robotTest.3 Dictionary.Test with Dictionaries as Arguments
|
|
261
273
|
--test robotTest.3 Dictionary.Test with FOR loops and Dictionaries #DEPENDS robotTest.1 Scalar.Test Case with Return Values
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
* By using the command `#SLEEP X`, where `X` is an integer in the range [0-3600] (in seconds), you can
|
|
277
|
+
define a startup delay for each subprocess. `#SLEEP` affects the next line unless the next line starts a
|
|
278
|
+
group with `{`, in which case the delay applies to the entire group. If the next line begins with `--test`
|
|
279
|
+
or `--suite`, the delay is applied to that specific item. Any other occurrences of `#SLEEP` are ignored.
|
|
280
|
+
Note that `#SLEEP` has no effect within a group, i.e., inside a subprocess.
|
|
281
|
+
|
|
282
|
+
The following example clarifies the behavior:
|
|
283
|
+
|
|
284
|
+
```sh
|
|
285
|
+
pabot --processes 2 --ordering order.txt data_1
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
where order.txt is:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
#SLEEP 1
|
|
292
|
+
{
|
|
293
|
+
#SLEEP 2
|
|
294
|
+
--suite Data 1.suite A
|
|
295
|
+
#SLEEP 3
|
|
296
|
+
--suite Data 1.suite B
|
|
297
|
+
#SLEEP 4
|
|
298
|
+
}
|
|
299
|
+
#SLEEP 5
|
|
300
|
+
#SLEEP 6
|
|
301
|
+
--suite Data 1.suite C
|
|
302
|
+
#SLEEP 7
|
|
303
|
+
--suite Data 1.suite D
|
|
304
|
+
#SLEEP 8
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
prints something like this:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
2025-02-15 19:15:00.408321 [0] [ID:1] SLEEPING 6 SECONDS BEFORE STARTING Data 1.suite C
|
|
311
|
+
2025-02-15 19:15:00.408321 [1] [ID:0] SLEEPING 1 SECONDS BEFORE STARTING Group_Data 1.suite A_Data 1.suite B
|
|
312
|
+
2025-02-15 19:15:01.409389 [PID:52008] [1] [ID:0] EXECUTING Group_Data 1.suite A_Data 1.suite B
|
|
313
|
+
2025-02-15 19:15:06.409024 [PID:1528] [0] [ID:1] EXECUTING Data 1.suite C
|
|
314
|
+
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
|
|
315
|
+
2025-02-15 19:15:09.259067 [1] [ID:2] SLEEPING 7 SECONDS BEFORE STARTING Data 1.suite D
|
|
316
|
+
2025-02-15 19:15:09.647342 [PID:1528] [0] [ID:1] PASSED Data 1.suite C in 3.2 seconds
|
|
317
|
+
2025-02-15 19:15:16.260432 [PID:48156] [1] [ID:2] EXECUTING Data 1.suite D
|
|
318
|
+
2025-02-15 19:15:18.696420 [PID:48156] [1] [ID:2] PASSED Data 1.suite D in 2.4 seconds
|
|
262
319
|
```
|
|
263
320
|
|
|
264
321
|
### Programmatic use
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Add src to path so that version can be imported
|
|
8
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
|
9
|
+
from pabot import __version__
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
name="robotframework-pabot",
|
|
13
|
+
version=__version__,
|
|
14
|
+
)
|
|
@@ -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 == "{":
|
|
@@ -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
|
|