robotframework-pabot 5.2.0b1__tar.gz → 5.2.0rc2__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 (55) hide show
  1. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/PKG-INFO +30 -7
  2. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/README.md +29 -6
  3. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/ProcessManager.py +67 -28
  4. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/__init__.py +1 -1
  5. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/arguments.py +19 -1
  6. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/pabot.py +495 -223
  7. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/result_merger.py +13 -9
  8. robotframework_pabot-5.2.0rc2/src/pabot/writer.py +268 -0
  9. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/PKG-INFO +30 -7
  10. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/SOURCES.txt +2 -3
  11. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_missing_subprocess_output.py +22 -4
  12. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabot.py +6 -6
  13. robotframework_pabot-5.2.0rc2/tests/test_writer_comprehensive.py +723 -0
  14. robotframework_pabot-5.2.0b1/src/pabot/skip_listener.py +0 -7
  15. robotframework_pabot-5.2.0b1/src/pabot/timeout_listener.py +0 -5
  16. robotframework_pabot-5.2.0b1/src/pabot/writer.py +0 -110
  17. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/MANIFEST.in +0 -0
  18. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/pyproject.toml +0 -0
  19. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/setup.cfg +0 -0
  20. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/setup.py +0 -0
  21. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/SharedLibrary.py +0 -0
  22. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/clientwrapper.py +0 -0
  23. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/coordinatorwrapper.py +0 -0
  24. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/execution_items.py +0 -0
  25. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/pabotlib.py +0 -0
  26. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/py3/__init__.py +0 -0
  27. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/py3/client.py +0 -0
  28. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/py3/coordinator.py +0 -0
  29. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/py3/messages.py +0 -0
  30. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/py3/worker.py +0 -0
  31. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/robotremoteserver.py +0 -0
  32. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/pabot/workerwrapper.py +0 -0
  33. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/dependency_links.txt +0 -0
  34. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/entry_points.txt +0 -0
  35. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/requires.txt +0 -0
  36. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/src/robotframework_pabot.egg-info/top_level.txt +0 -0
  37. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_arguments_output.py +0 -0
  38. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_basic_arguments.py +0 -0
  39. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_depends.py +0 -0
  40. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_dynamic_ordering.py +0 -0
  41. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_functional.py +0 -0
  42. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_ordering.py +0 -0
  43. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabot_process_handling.py +0 -0
  44. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabot_timeout.py +0 -0
  45. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabotlib.py +0 -0
  46. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabotprerunmodifier.py +0 -0
  47. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_pabotsuitenames_io.py +0 -0
  48. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_prerunmodifier.py +0 -0
  49. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_resultmerger.py +0 -0
  50. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_robotremoteserver.py +0 -0
  51. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_run_empty_suite.py +0 -0
  52. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_stacktrace.py +0 -0
  53. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_suite_structure.py +0 -0
  54. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_testlevelsplit_include.py +0 -0
  55. {robotframework_pabot-5.2.0b1 → robotframework_pabot-5.2.0rc2}/tests/test_testlevelsplit_output_task_order.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-pabot
3
- Version: 5.2.0b1
3
+ Version: 5.2.0rc2
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
@@ -118,10 +118,11 @@ pabot [--verbose|--testlevelsplit|--command .. --end-command|
118
118
  --shard i/n|
119
119
  --artifacts extensions|--artifactsinsubfolders|
120
120
  --resourcefile file|--argumentfile[num] file|--suitesfrom file
121
- --ordering <FILENAME> [static|dynamic] [skip|run_all]|
121
+ --ordering file [static|dynamic] [skip|run_all]|
122
122
  --chunk|
123
123
  --pabotprerunmodifier modifier|
124
124
  --no-rebot|
125
+ --pabotconsole [verbose|dotted|quiet|none]|
125
126
  --help|--version]
126
127
  [robot options] [path ...]
127
128
  ```
@@ -193,7 +194,7 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
193
194
 
194
195
  **--resourcefile [FILEPATH]**
195
196
  Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
196
- pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
197
+ pabotlib option. Resource file syntax is same as Windows ini files where a section is a shared set of variables.
197
198
 
198
199
  **--argumentfile[INTEGER] [FILEPATH]**
199
200
  Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
@@ -206,10 +207,10 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
206
207
  Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
207
208
  before shorter ones.
208
209
 
209
- **--ordering [FILE PATH] [MODE] [FAILURE POLICY]**
210
+ **--ordering [FILEPATH] [MODE] [FAILURE POLICY]**
210
211
  Optionally give execution order from a file. See README.md section: [Controlling execution order, mode and level of parallelism](#controlling-execution-order-mode-and-level-of-parallelism)
211
- - MODE (optional): [static (default)|dynamic]
212
- - FAILURE POLICY (optional, only in dynamic mode): [skip|run_all (default)]
212
+ - MODE (optional): [ static (default) | dynamic ]
213
+ - FAILURE POLICY (optional, only in dynamic mode): [ skip | run_all (default) ]
213
214
 
214
215
  **--chunk**
215
216
  Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
@@ -226,6 +227,28 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
226
227
  for scenarios where Rebot should be run later due to large log files, ensuring better memory and resource availability.
227
228
  Subprocess results are stored in the pabot_results folder.
228
229
 
230
+ **--pabotconsole [MODE]**
231
+ The --pabotconsole option controls how much output is printed to the console.
232
+ Note that all Pabot’s own messages are always logged to pabot_manager.log, regardless of the selected console mode.
233
+
234
+ The available options are:
235
+ - verbose (default):
236
+ Prints all messages to the console, corresponding closely to what is written to the log file.
237
+ - dotted:
238
+ Prints important messages, warnings, and errors to the console, along with execution progress using the following notation:
239
+
240
+ - PASS = .
241
+ - FAIL = F
242
+ - SKIP = s
243
+
244
+ Note that each Robot Framework process is represented by a single character.
245
+ Depending on the execution parameters, individual tests may not have their own status character;
246
+ instead, the status may represent an entire suite or a group of tests.
247
+ - quiet:
248
+ Similar to dotted, but suppresses execution progress output.
249
+ - none:
250
+ Produces no console output at all.
251
+
229
252
  **--help**
230
253
  Print usage instructions.
231
254
 
@@ -491,7 +514,7 @@ pabot_results/
491
514
  │ ├── robot_stdout.out
492
515
  │ ├── robot_stderr.out
493
516
  │ └── artifacts...
494
- pabot_manager.log # Pabot's own main log. Basically same than prints in console
517
+ └── pabot_manager.log # Pabot's own main log.
495
518
  ```
496
519
 
497
520
  Each `PABOTQUEUEINDEX` folder contains as default:
@@ -95,10 +95,11 @@ pabot [--verbose|--testlevelsplit|--command .. --end-command|
95
95
  --shard i/n|
96
96
  --artifacts extensions|--artifactsinsubfolders|
97
97
  --resourcefile file|--argumentfile[num] file|--suitesfrom file
98
- --ordering <FILENAME> [static|dynamic] [skip|run_all]|
98
+ --ordering file [static|dynamic] [skip|run_all]|
99
99
  --chunk|
100
100
  --pabotprerunmodifier modifier|
101
101
  --no-rebot|
102
+ --pabotconsole [verbose|dotted|quiet|none]|
102
103
  --help|--version]
103
104
  [robot options] [path ...]
104
105
  ```
@@ -170,7 +171,7 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
170
171
 
171
172
  **--resourcefile [FILEPATH]**
172
173
  Indicator for a file that can contain shared variables for distributing resources. This needs to be used together with
173
- pabotlib option. Resource file syntax is same as Windows ini files. Where a section is a shared set of variables.
174
+ pabotlib option. Resource file syntax is same as Windows ini files where a section is a shared set of variables.
174
175
 
175
176
  **--argumentfile[INTEGER] [FILEPATH]**
176
177
  Run same suites with multiple [argumentfile](http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#argument-files) options.
@@ -183,10 +184,10 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
183
184
  Optionally read suites from output.xml file. Failed suites will run first and longer running ones will be executed
184
185
  before shorter ones.
185
186
 
186
- **--ordering [FILE PATH] [MODE] [FAILURE POLICY]**
187
+ **--ordering [FILEPATH] [MODE] [FAILURE POLICY]**
187
188
  Optionally give execution order from a file. See README.md section: [Controlling execution order, mode and level of parallelism](#controlling-execution-order-mode-and-level-of-parallelism)
188
- - MODE (optional): [static (default)|dynamic]
189
- - FAILURE POLICY (optional, only in dynamic mode): [skip|run_all (default)]
189
+ - MODE (optional): [ static (default) | dynamic ]
190
+ - FAILURE POLICY (optional, only in dynamic mode): [ skip | run_all (default) ]
190
191
 
191
192
  **--chunk**
192
193
  Optionally chunk tests to PROCESSES number of robot runs. This can save time because all the suites will share the same
@@ -203,6 +204,28 @@ Supports all [Robot Framework command line options](https://robotframework.org/r
203
204
  for scenarios where Rebot should be run later due to large log files, ensuring better memory and resource availability.
204
205
  Subprocess results are stored in the pabot_results folder.
205
206
 
207
+ **--pabotconsole [MODE]**
208
+ The --pabotconsole option controls how much output is printed to the console.
209
+ Note that all Pabot’s own messages are always logged to pabot_manager.log, regardless of the selected console mode.
210
+
211
+ The available options are:
212
+ - verbose (default):
213
+ Prints all messages to the console, corresponding closely to what is written to the log file.
214
+ - dotted:
215
+ Prints important messages, warnings, and errors to the console, along with execution progress using the following notation:
216
+
217
+ - PASS = .
218
+ - FAIL = F
219
+ - SKIP = s
220
+
221
+ Note that each Robot Framework process is represented by a single character.
222
+ Depending on the execution parameters, individual tests may not have their own status character;
223
+ instead, the status may represent an entire suite or a group of tests.
224
+ - quiet:
225
+ Similar to dotted, but suppresses execution progress output.
226
+ - none:
227
+ Produces no console output at all.
228
+
206
229
  **--help**
207
230
  Print usage instructions.
208
231
 
@@ -468,7 +491,7 @@ pabot_results/
468
491
  │ ├── robot_stdout.out
469
492
  │ ├── robot_stderr.out
470
493
  │ └── artifacts...
471
- pabot_manager.log # Pabot's own main log. Basically same than prints in console
494
+ └── pabot_manager.log # Pabot's own main log.
472
495
  ```
473
496
 
474
497
  Each `PABOTQUEUEINDEX` folder contains as default:
@@ -1,12 +1,12 @@
1
1
  import os
2
2
  import sys
3
3
  import time
4
- import signal
5
4
  import threading
6
5
  import subprocess
7
6
  import datetime
8
7
  import queue
9
8
  import locale
9
+ import signal
10
10
 
11
11
  try:
12
12
  import psutil
@@ -28,26 +28,14 @@ class ProcessManager:
28
28
  self.processes = []
29
29
  self.lock = threading.Lock()
30
30
  self.writer = get_writer()
31
+ self.interrupted = False
32
+ # Note: Signal handling is done in pabot.py's main_program() to ensure
33
+ # PabotLib is shut down gracefully before process termination
34
+ # This ProcessManager will check the interrupted flag set by pabot.py's keyboard_interrupt()
31
35
 
32
- # Install SIGINT only in main thread
33
- try:
34
- if threading.current_thread() is threading.main_thread():
35
- signal.signal(signal.SIGINT, self._handle_sigint)
36
- else:
37
- self.writer.write(
38
- "[ProcessManager] (test mode) signal handlers disabled (not in main thread)"
39
- )
40
- except Exception as e:
41
- self.writer.write(f"[WARN] Could not register signal handler: {e}")
42
-
43
- # -------------------------------
44
- # SIGNAL HANDLING
45
- # -------------------------------
46
-
47
- def _handle_sigint(self, signum, frame):
48
- self.writer.write("[ProcessManager] Ctrl+C detected — terminating all subprocesses", color=Color.RED)
49
- self.terminate_all()
50
- sys.exit(130)
36
+ def set_interrupted(self):
37
+ """Called by pabot.py when CTRL+C is received."""
38
+ self.interrupted = True
51
39
 
52
40
  # -------------------------------
53
41
  # OUTPUT STREAM READERS
@@ -213,7 +201,7 @@ class ProcessManager:
213
201
 
214
202
  self.writer.write(
215
203
  f"[ProcessManager] Terminating process tree PID={process.pid}",
216
- color=Color.YELLOW
204
+ level='debug'
217
205
  )
218
206
 
219
207
  # PRIMARY: psutil (best reliability)
@@ -304,11 +292,13 @@ class ProcessManager:
304
292
  if verbose:
305
293
  self.writer.write(
306
294
  f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] "
307
- f"EXECUTING PARALLEL {item_name}:\n{' '.join(cmd)}"
295
+ f"EXECUTING PARALLEL {item_name}:\n{' '.join(cmd)}",
296
+ level='debug'
308
297
  )
309
298
  else:
310
299
  self.writer.write(
311
- f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] EXECUTING {item_name}"
300
+ f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] EXECUTING {item_name}",
301
+ level='debug'
312
302
  )
313
303
 
314
304
  # Start logging thread
@@ -327,28 +317,76 @@ class ProcessManager:
327
317
  while rc is None:
328
318
  rc = process.poll()
329
319
 
320
+ # INTERRUPT CHECK - terminate process gracefully when CTRL+C is pressed
321
+ if self.interrupted:
322
+ ts = datetime.datetime.now()
323
+ self.writer.write(
324
+ f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] "
325
+ f"Process {item_name} interrupted by user (Ctrl+C)",
326
+ color=Color.YELLOW, level='warning'
327
+ )
328
+ self._terminate_tree(process)
329
+ rc = -1
330
+
331
+ # Dryrun process to mark all tests as failed due to user interrupt
332
+ this_dir = os.path.dirname(os.path.abspath(__file__))
333
+ listener_path = os.path.join(this_dir, "listener", "interrupt_listener.py")
334
+ dry_run_env = env.copy() if env else os.environ.copy()
335
+ before, after = split_on_first(cmd, "-A")
336
+ dryrun_cmd = before + ["--dryrun", '--listener', listener_path, '-A'] + after
337
+
338
+ self.writer.write(
339
+ f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] "
340
+ f"Starting dry run to mark test as failed due to user interrupt: {' '.join(dryrun_cmd)}",
341
+ level='debug'
342
+ )
343
+ try:
344
+ subprocess.run(
345
+ dryrun_cmd,
346
+ env=dry_run_env,
347
+ stdout=subprocess.PIPE,
348
+ stderr=subprocess.PIPE,
349
+ timeout=3,
350
+ text=True,
351
+ )
352
+ except subprocess.TimeoutExpired as e:
353
+ self.writer.write(f"Dry-run timed out after 3s: {e}", level='debug')
354
+ break
355
+
330
356
  # TIMEOUT CHECK
331
357
  if timeout and (time.time() - start > timeout):
332
358
  ts = datetime.datetime.now()
333
359
  self.writer.write(
334
360
  f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] "
335
- f"Process {item_name} killed due to exceeding the maximum timeout of {timeout} seconds"
361
+ f"Process {item_name} killed due to exceeding the maximum timeout of {timeout} seconds",
362
+ color=Color.YELLOW, level='warning'
336
363
  )
337
364
  self._terminate_tree(process)
338
365
  rc = -1
339
366
 
340
367
  # Dryrun process to mark all tests as failed due to timeout
341
368
  this_dir = os.path.dirname(os.path.abspath(__file__))
342
- listener_path = os.path.join(this_dir, "timeout_listener.py")
369
+ listener_path = os.path.join(this_dir, "listener", "timeout_listener.py")
343
370
  dry_run_env = env.copy() if env else os.environ.copy()
344
371
  before, after = split_on_first(cmd, "-A")
345
372
  dryrun_cmd = before + ["--dryrun", '--listener', listener_path, '-A'] + after
346
373
 
347
374
  self.writer.write(
348
375
  f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] "
349
- f"Starting dry run to mark tests as failed due to timeout: {' '.join(dryrun_cmd)}"
376
+ f"Starting dry run to mark test as failed due to timeout: {' '.join(dryrun_cmd)}",
377
+ level='debug'
350
378
  )
351
- subprocess.run(dryrun_cmd, env=dry_run_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
379
+ try:
380
+ subprocess.run(
381
+ dryrun_cmd,
382
+ env=dry_run_env,
383
+ stdout=subprocess.PIPE,
384
+ stderr=subprocess.PIPE,
385
+ timeout=3,
386
+ text=True,
387
+ )
388
+ except subprocess.TimeoutExpired as e:
389
+ self.writer.write(f"Dry-run timed out after 3s: {e}", level='debug')
352
390
 
353
391
  break
354
392
 
@@ -357,7 +395,8 @@ class ProcessManager:
357
395
  ts = datetime.datetime.now()
358
396
  self.writer.write(
359
397
  f"{ts} [PID:{process.pid}] [{pool_id}] [ID:{item_index}] still running "
360
- f"{item_name} after {(counter * 0.1):.1f}s"
398
+ f"{item_name} after {(counter * 0.1):.1f}s",
399
+ level='debug'
361
400
  )
362
401
  ping_interval += 50
363
402
  next_ping += ping_interval
@@ -7,4 +7,4 @@ try:
7
7
  except ImportError:
8
8
  pass
9
9
 
10
- __version__ = "5.2.0b1"
10
+ __version__ = "5.2.0rc2"
@@ -174,6 +174,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
174
174
  "shardcount": 1,
175
175
  "chunk": False,
176
176
  "no-rebot": False,
177
+ "pabotconsole": "verbose",
177
178
  }
178
179
 
179
180
  # Arguments that are flags (boolean)
@@ -200,6 +201,7 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
200
201
  "suitesfrom": str,
201
202
  "artifacts": _parse_artifacts,
202
203
  "shard": _parse_shard,
204
+ "pabotconsole": str,
203
205
  }
204
206
 
205
207
  argumentfiles = []
@@ -234,7 +236,12 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
234
236
  if arg_name == "command":
235
237
  try:
236
238
  end_index = args.index("--end-command", i)
237
- pabot_args["command"] = args[i + 1 : end_index]
239
+ pabot_args["use_user_command"] = True
240
+ cmd_lines = args[i + 1 : end_index]
241
+ cmd = []
242
+ for line in cmd_lines:
243
+ cmd.extend(line.split())
244
+ pabot_args["command"] = cmd
238
245
  i = end_index + 1
239
246
  continue
240
247
  except ValueError:
@@ -284,6 +291,17 @@ def _parse_pabot_args(args): # type: (List[str]) -> Tuple[List[str], Dict[str,
284
291
  # move index past ordering args only
285
292
  i += 2 + i_mode_offset + i_failure_offset
286
293
  continue
294
+ elif arg_name == "pabotconsole":
295
+ console_type = args[i + 1]
296
+ valid_types = ("verbose", "dotted", "quiet", "none")
297
+ if console_type not in valid_types:
298
+ raise DataError(
299
+ f"Invalid value for --pabotconsole: {console_type}. "
300
+ f"Valid values are: {', '.join(valid_types)}"
301
+ )
302
+ pabot_args["pabotconsole"] = console_type
303
+ i += 2
304
+ continue
287
305
  else:
288
306
  value = value_args[arg_name](args[i + 1])
289
307
  if arg_name == "shard":