robotframework-pabot 3.1.0__tar.gz → 4.0.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.
Files changed (40) hide show
  1. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/PKG-INFO +1 -1
  2. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/README.md +9 -5
  3. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/__init__.py +1 -1
  4. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/arguments.py +87 -95
  5. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/pabot.py +4 -4
  6. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/PKG-INFO +1 -1
  7. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_depends.py +4 -4
  8. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_pabot.py +61 -0
  9. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/LICENSE.txt +0 -0
  10. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/MANIFEST.in +0 -0
  11. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/pyproject.toml +0 -0
  12. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/setup.cfg +0 -0
  13. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/setup.py +0 -0
  14. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/SharedLibrary.py +0 -0
  15. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/clientwrapper.py +0 -0
  16. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/coordinatorwrapper.py +0 -0
  17. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/execution_items.py +0 -0
  18. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/pabotlib.py +0 -0
  19. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/py3/__init__.py +0 -0
  20. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/py3/client.py +0 -0
  21. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/py3/coordinator.py +0 -0
  22. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/py3/messages.py +0 -0
  23. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/py3/worker.py +0 -0
  24. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/result_merger.py +0 -0
  25. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/robotremoteserver.py +0 -0
  26. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/pabot/workerwrapper.py +0 -0
  27. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/SOURCES.txt +0 -0
  28. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
  29. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
  30. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/requires.txt +0 -0
  31. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
  32. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_arguments_output.py +0 -0
  33. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_functional.py +0 -0
  34. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_ordering.py +0 -0
  35. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_pabotlib.py +0 -0
  36. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_pabotsuitenames_io.py +0 -0
  37. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_resultmerger.py +0 -0
  38. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_stacktrace.py +0 -0
  39. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_testlevelsplit_include.py +0 -0
  40. {robotframework_pabot-3.1.0 → robotframework_pabot-4.0.0}/tests/test_testlevelsplit_output_task_order.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-pabot
3
- Version: 3.1.0
3
+ Version: 4.0.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
@@ -55,14 +55,14 @@ There are several ways you can help in improving this tool:
55
55
  ## Command-line options
56
56
 
57
57
  pabot [--verbose|--testlevelsplit|--command .. --end-command|
58
- --processes num|--pabotlib|--pabotlibhost host|--pabotlibport port|
58
+ --processes num|--no-pabotlib|--pabotlibhost host|--pabotlibport port|
59
59
  --processtimeout num|
60
60
  --shard i/n|
61
61
  --artifacts extensions|--artifactsinsubfolders|
62
62
  --resourcefile file|--argumentfile[num] file|--suitesfrom file]
63
63
  [robot options] [path ...]
64
64
 
65
- Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following options (these must be before RF options):
65
+ Supports all [Robot Framework command line options](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#all-command-line-options) and also following pabot options:
66
66
 
67
67
  --verbose
68
68
  more output from the parallel execution
@@ -83,8 +83,11 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
83
83
  Special option "all" will use as many processes as there are
84
84
  executable suites or tests.
85
85
 
86
- --pabotlib
87
- Start PabotLib remote server. This enables locking and resource distribution between parallel test executions.
86
+ PabotLib remote server is started by default to enable locking and resource distribution
87
+ between parallel test executions.
88
+
89
+ --no-pabotlib
90
+ Disable the PabotLib remote server if you don't need locking or resource distribution features.
88
91
 
89
92
  --pabotlibhost [HOSTNAME]
90
93
  Host name of the PabotLib remote server (default is 127.0.0.1)
@@ -142,8 +145,9 @@ Example usages:
142
145
  pabot --command java -jar robotframework.jar --end-command --include SMOKE tests
143
146
  pabot --processes 10 tests
144
147
  pabot --pabotlibhost 192.168.1.123 --pabotlibport 8271 --processes 10 tests
145
- pabot --pabotlib --pabotlibhost 192.168.1.111 --pabotlibport 8272 --processes 10 tests
146
148
  pabot --artifacts png,mp4,txt --artifactsinsubfolders directory_to_tests
149
+ # To disable PabotLib:
150
+ pabot --no-pabotlib tests
147
151
 
148
152
  ### PabotLib
149
153
 
@@ -1,4 +1,4 @@
1
1
  from __future__ import absolute_import
2
2
 
3
3
  from .pabotlib import PabotLib
4
- __version__ = "3.1.0"
4
+ __version__ = "4.0.0"
@@ -80,7 +80,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
80
80
  "verbose": False,
81
81
  "help": False,
82
82
  "testlevelsplit": False,
83
- "pabotlib": False,
83
+ "pabotlib": True,
84
84
  "pabotlibhost": "127.0.0.1",
85
85
  "pabotlibport": 8270,
86
86
  "processes": _processes_count(),
@@ -91,108 +91,100 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
91
91
  "shardcount": 1,
92
92
  "chunk": False,
93
93
  }
94
+ # Explicitly define argument types for validation
95
+ flag_args = {
96
+ "verbose", "help", "testlevelsplit", "pabotlib",
97
+ "artifactsinsubfolders", "chunk"
98
+ }
99
+ value_args = {
100
+ "hive": str,
101
+ "processes": lambda x: int(x) if x != 'all' else None,
102
+ "resourcefile": str,
103
+ "pabotlibhost": str,
104
+ "pabotlibport": int,
105
+ "processtimeout": int,
106
+ "ordering": _parse_ordering,
107
+ "suitesfrom": str,
108
+ "artifacts": lambda x: x.split(","),
109
+ "shard": _parse_shard
110
+ }
111
+
94
112
  argumentfiles = []
95
- while args and (
96
- args[0]
97
- in [
98
- "--" + param
99
- for param in [
100
- "hive",
101
- "command",
102
- "processes",
103
- "verbose",
104
- "resourcefile",
105
- "testlevelsplit",
106
- "pabotlib",
107
- "pabotlibhost",
108
- "pabotlibport",
109
- "processtimeout",
110
- "ordering",
111
- "suitesfrom",
112
- "artifacts",
113
- "artifactsinsubfolders",
114
- "help",
115
- "shard",
116
- "chunk",
117
- ]
118
- ]
119
- or ARGSMATCHER.match(args[0])
120
- ):
121
- if args[0] == "--hive":
122
- pabot_args["hive"] = args[1]
123
- args = args[2:]
124
- continue
125
- if args[0] == "--command":
126
- end_index = args.index("--end-command")
127
- pabot_args["command"] = args[1:end_index]
128
- args = args[end_index + 1 :]
129
- continue
130
- if args[0] == "--processes":
131
- pabot_args["processes"] = int(args[1]) if args[1] != 'all' else None
132
- args = args[2:]
133
- continue
134
- if args[0] == "--verbose":
135
- pabot_args["verbose"] = True
136
- args = args[1:]
137
- continue
138
- if args[0] == "--chunk":
139
- pabot_args["chunk"] = True
140
- args = args[1:]
141
- continue
142
- if args[0] == "--resourcefile":
143
- pabot_args["resourcefile"] = args[1]
144
- args = args[2:]
145
- continue
146
- if args[0] == "--pabotlib":
147
- pabot_args["pabotlib"] = True
148
- args = args[1:]
149
- continue
150
- if args[0] == "--ordering":
151
- pabot_args["ordering"] = _parse_ordering(args[1])
152
- args = args[2:]
113
+ remaining_args = []
114
+ i = 0
115
+
116
+ # Track conflicting options during parsing
117
+ saw_pabotlib_flag = False
118
+ saw_no_pabotlib = False
119
+
120
+ while i < len(args):
121
+ arg = args[i]
122
+ if not arg.startswith('--'):
123
+ remaining_args.append(arg)
124
+ i += 1
153
125
  continue
154
- if args[0] == "--testlevelsplit":
155
- pabot_args["testlevelsplit"] = True
126
+
127
+ arg_name = arg[2:] # Strip '--'
128
+
129
+ if arg_name == "no-pabotlib":
130
+ saw_no_pabotlib = True
131
+ pabot_args["pabotlib"] = False # Just set the main flag
156
132
  args = args[1:]
157
133
  continue
158
- if args[0] == "--pabotlibhost":
159
- pabot_args["pabotlibhost"] = args[1]
160
- args = args[2:]
161
- continue
162
- if args[0] == "--pabotlibport":
163
- pabot_args["pabotlibport"] = int(args[1])
164
- args = args[2:]
165
- continue
166
- if args[0] == "--processtimeout":
167
- pabot_args["processtimeout"] = int(args[1])
168
- args = args[2:]
169
- continue
170
- if args[0] == "--suitesfrom":
171
- pabot_args["suitesfrom"] = args[1]
172
- args = args[2:]
173
- continue
174
- if args[0] == "--artifacts":
175
- pabot_args["artifacts"] = args[1].split(",")
176
- args = args[2:]
177
- continue
178
- if args[0] == "--artifactsinsubfolders":
179
- pabot_args["artifactsinsubfolders"] = True
134
+ if arg_name == "pabotlib":
135
+ saw_pabotlib_flag = True
180
136
  args = args[1:]
181
137
  continue
182
- if args[0] == "--shard":
183
- pabot_args["shardindex"], pabot_args["shardcount"] = _parse_shard(args[1])
184
- args = args[2:]
185
- continue
186
- match = ARGSMATCHER.match(args[0])
138
+
139
+ # Special case for command
140
+ if arg_name == "command":
141
+ try:
142
+ end_index = args.index("--end-command", i)
143
+ pabot_args["command"] = args[i+1:end_index]
144
+ i = end_index + 1
145
+ continue
146
+ except ValueError:
147
+ raise DataError("--command requires matching --end-command")
148
+
149
+ # Handle flag arguments
150
+ if arg_name in flag_args:
151
+ pabot_args[arg_name] = True
152
+ i += 1
153
+ continue
154
+
155
+ # Handle value arguments
156
+ if arg_name in value_args:
157
+ if i + 1 >= len(args):
158
+ raise DataError(f"--{arg_name} requires a value")
159
+ try:
160
+ value = value_args[arg_name](args[i + 1])
161
+ if arg_name == "shard":
162
+ pabot_args["shardindex"], pabot_args["shardcount"] = value
163
+ else:
164
+ pabot_args[arg_name] = value
165
+ i += 2
166
+ continue
167
+ except (ValueError, TypeError) as e:
168
+ raise DataError(f"Invalid value for --{arg_name}: {args[i + 1]}")
169
+
170
+ # Handle argument files
171
+ match = ARGSMATCHER.match(arg)
187
172
  if match:
188
- argumentfiles += [(match.group(1), args[1])]
189
- args = args[2:]
190
- continue
191
- if args and args[0] == "--help":
192
- pabot_args["help"] = True
193
- args = args[1:]
173
+ if i + 1 >= len(args):
174
+ raise DataError(f"{arg} requires a value")
175
+ argumentfiles.append((match.group(1), args[i + 1]))
176
+ i += 2
177
+ continue
178
+
179
+ # If we get here, it's a non-pabot argument
180
+ remaining_args.append(arg)
181
+ i += 1
182
+
183
+ if saw_pabotlib_flag and saw_no_pabotlib:
184
+ raise DataError("Cannot use both --pabotlib and --no-pabotlib options together")
185
+
194
186
  pabot_args["argumentfiles"] = argumentfiles
195
- return args, pabot_args
187
+ return remaining_args, pabot_args
196
188
 
197
189
 
198
190
  def _parse_ordering(filename): # type: (str) -> List[ExecutionItem]
@@ -2019,17 +2019,17 @@ def _verify_depends(suite_names):
2019
2019
  )
2020
2020
  )
2021
2021
  if suites_with_depends != suites_with_found_dependencies:
2022
- raise Exception("There are unmet dependencies using #DEPENDS")
2022
+ raise DataError("Invalid test configuration: Some test suites have dependencies (#DEPENDS) that cannot be found.")
2023
2023
  suites_with_circular_dependencies = list(
2024
2024
  filter(lambda suite: suite.depends == suite.name, suites_with_depends)
2025
2025
  )
2026
2026
  if suites_with_circular_dependencies:
2027
- raise Exception("There are suites with circular dependencies using #DEPENDS")
2027
+ raise DataError("Invalid test configuration: Test suites cannot depend on themselves.")
2028
2028
  grouped_suites = list(
2029
2029
  filter(lambda suite: isinstance(suite, GroupItem), suite_names)
2030
2030
  )
2031
2031
  if grouped_suites and suites_with_depends:
2032
- raise Exception("#DEPENDS and grouped suites are incompatible")
2032
+ raise DataError("Invalid test configuration: Cannot use both #DEPENDS and grouped suites.")
2033
2033
 
2034
2034
 
2035
2035
  def _group_by_depend(suite_names):
@@ -2057,7 +2057,7 @@ def _group_by_depend(suite_names):
2057
2057
  dependency_tree += [dependent_on_last_stage]
2058
2058
  flattened_dependency_tree = sum(dependency_tree, [])
2059
2059
  if len(flattened_dependency_tree) != len(runnable_suites):
2060
- raise Exception("There are circular or unmet dependencies using #DEPENDS")
2060
+ raise DataError("Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.")
2061
2061
  return dependency_tree
2062
2062
 
2063
2063
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-pabot
3
- Version: 3.1.0
3
+ Version: 4.0.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
@@ -96,7 +96,7 @@ class DependsTest(unittest.TestCase):
96
96
  --test Test.The Test S1Test 08
97
97
  """,
98
98
  )
99
- self.assertIn(b"circular or unmet dependencies", stderr)
99
+ self.assertIn(b"Invalid test configuration: Circular or unmet dependencies detected between test suites", stdout)
100
100
 
101
101
  def test_unmet_dependency(self):
102
102
  stdout, stderr = self._run_tests_with(
@@ -107,7 +107,7 @@ class DependsTest(unittest.TestCase):
107
107
  --test Test.The Test S1Test 08
108
108
  """,
109
109
  )
110
- self.assertIn(b"circular or unmet dependencies", stderr)
110
+ self.assertIn(b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.", stdout)
111
111
 
112
112
  def test_same_reference(self):
113
113
  stdout, stderr = self._run_tests_with(
@@ -118,7 +118,7 @@ class DependsTest(unittest.TestCase):
118
118
  --test Test.The Test S1Test 08
119
119
  """,
120
120
  )
121
- self.assertIn(b"circular or unmet dependencies", stderr)
121
+ self.assertIn(b"Invalid test configuration: Circular or unmet dependencies detected between test suites. Please check your #DEPENDS definitions.", stdout)
122
122
 
123
123
  def test_wait(self):
124
124
  stdout, stderr = self._run_tests_with(
@@ -130,4 +130,4 @@ class DependsTest(unittest.TestCase):
130
130
  --test Test.The Test S1Test 08
131
131
  """,
132
132
  )
133
- self.assertIn(b"circular or unmet dependencies", stderr)
133
+ self.assertIn(b"Invalid test configuration: Circular or unmet dependencies detected between test suites", stdout)
@@ -1329,7 +1329,68 @@ class PabotTests(unittest.TestCase):
1329
1329
  self.assertNotIn('schemaversion="4"', content)
1330
1330
  finally:
1331
1331
  shutil.rmtree(dtemp)
1332
+
1333
+ def test_parse_args_mixed_order(self):
1334
+ options, datasources, pabot_args, options_for_subprocesses = arguments.parse_args([
1335
+ "--exitonfailure",
1336
+ "--processes", "12",
1337
+ "--outputdir", "mydir",
1338
+ "--verbose",
1339
+ "--pabotlib",
1340
+ "suite"
1341
+ ])
1342
+ self.assertEqual(pabot_args["processes"], 12)
1343
+ self.assertEqual(pabot_args["verbose"], True)
1344
+ self.assertEqual(pabot_args["pabotlib"], True)
1345
+ self.assertEqual(options["outputdir"], "mydir")
1346
+ self.assertEqual(options["exitonfailure"], True)
1347
+ self.assertEqual(datasources, ["suite"])
1332
1348
 
1349
+ def test_parse_args_error_handling(self):
1350
+ with self.assertRaises(DataError) as cm:
1351
+ arguments.parse_args(["--processes"])
1352
+ self.assertIn("requires a value", str(cm.exception))
1353
+
1354
+ with self.assertRaises(DataError) as cm:
1355
+ arguments.parse_args(["--processes", "invalid"])
1356
+ self.assertIn("Invalid value for --processes", str(cm.exception))
1357
+
1358
+ with self.assertRaises(DataError) as cm:
1359
+ arguments.parse_args(["--command", "echo", "hello"])
1360
+ self.assertIn("requires matching --end-command", str(cm.exception))
1361
+
1362
+ def test_parse_args_command_with_pabot_args(self):
1363
+ options, datasources, pabot_args, _ = arguments.parse_args([
1364
+ "--command",
1365
+ "script.sh",
1366
+ "--processes",
1367
+ "5",
1368
+ "--end-command",
1369
+ "--verbose",
1370
+ "suite"
1371
+ ])
1372
+ self.assertEqual(pabot_args["command"], ["script.sh", "--processes", "5"])
1373
+ self.assertEqual(pabot_args["verbose"], True)
1374
+
1375
+ def test_pabotlib_defaults_to_enabled(self):
1376
+ options, _, pabot_args, _ = arguments.parse_args(["suite"])
1377
+ self.assertTrue(pabot_args["pabotlib"])
1378
+ self.assertFalse("no_pabotlib" in pabot_args) # Ensure internal flag not leaked
1379
+
1380
+ def test_no_pabotlib_disables_pabotlib(self):
1381
+ options, _, pabot_args, _ = arguments.parse_args(["--no-pabotlib", "suite"])
1382
+ self.assertFalse(pabot_args["pabotlib"])
1383
+ self.assertFalse("no_pabotlib" in pabot_args) # Ensure internal flag not leaked
1384
+
1385
+ def test_pabotlib_option_shows_warning(self):
1386
+ options, _, pabot_args, _ = arguments.parse_args(["--pabotlib", "suite"])
1387
+ self.assertTrue(pabot_args["pabotlib"])
1388
+ self.assertFalse("no_pabotlib" in pabot_args) # Ensure internal flag not leaked
1389
+
1390
+ def test_conflicting_pabotlib_options_raise_error(self):
1391
+ with self.assertRaises(DataError) as context:
1392
+ arguments.parse_args(["--pabotlib", "--no-pabotlib", "suite"])
1393
+ self.assertIn("Cannot use both --pabotlib and --no-pabotlib", str(context.exception))
1333
1394
 
1334
1395
  if __name__ == "__main__":
1335
1396
  unittest.main()