robotframework-parallelrunner 0.2.1__py3-none-any.whl → 0.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.
@@ -10,13 +10,14 @@ from .version import __version__
10
10
 
11
11
  class ParallelTaskError(RuntimeError):
12
12
  """
13
- Raised when at least one parallel task failed and there is no meaningful
14
- return value to hand back for it - by ``get_result_values``, and by
15
- ``run_parallel_scenarios`` when called with ``return_values_only=True``.
13
+ Raised when at least one parallel task failed: by ``run_parallel_scenarios``
14
+ (unless called with ``fail_on_error=False``), and by ``get_result_values``,
15
+ which has no meaningful return value to hand back for a failed task.
16
16
 
17
- Silently substituting `None` for a failed task's value would let a test
18
- keep going with bad data instead of failing for the right reason, so
19
- this is raised instead.
17
+ Returning normally after a failed call would let a test keep going (and
18
+ pass) with bad or missing data instead of failing for the right reason,
19
+ so this is raised instead - after every task has finished and all logs
20
+ have been replayed.
20
21
 
21
22
  Attributes:
22
23
  failures: the subset of result dicts with ``status == "FAIL"``,
@@ -91,9 +92,24 @@ class ParallelRunner:
91
92
  | result | The return value of the call (only when ``status`` is ``PASS``). |
92
93
  | error | The error message (only when ``status`` is ``FAIL``). |
93
94
 
94
- A failing call does *not* fail the keyword; check ``status`` yourself,
95
- or use ``return_values_only=True`` / `Get Result Values`, which fail
96
- if any call failed.
95
+ = Failures =
96
+
97
+ If any call raises, `Run Parallel Scenarios` still waits for every other
98
+ call, replays all logs, and then *fails* with a ``ParallelTaskError``
99
+ that lists each failed item and its error, e.g.
100
+ ``2 of 5 parallel task(s) failed: item='4': ...; item='5': ...``.
101
+ One failed call is never hidden behind a passing keyword.
102
+
103
+ To inspect failures yourself instead, pass ``fail_on_error=False``: the
104
+ keyword then always returns the result list, failed calls get
105
+ ``status`` ``FAIL`` (their error is also logged at ``ERROR`` level), and
106
+ it is up to you to assert on them:
107
+
108
+ | ${results}= | Run Parallel Scenarios | keyword=Verify Agent Data | library=MyLibrary | for_loop_iterable=${agents} | fail_on_error=False |
109
+ | Should Be Equal | ${results}[1][status] | PASS |
110
+
111
+ ``return_values_only=True`` and `Get Result Values` always fail if any
112
+ call failed, because a failed call has no return value.
97
113
 
98
114
  = Custom logger mappers =
99
115
 
@@ -146,6 +162,7 @@ class ParallelRunner:
146
162
  thread_log_level: str = "INFO",
147
163
  logger_mapper: Optional[Any] = None,
148
164
  return_values_only: bool = False,
165
+ fail_on_error: bool = True,
149
166
  **kwargs
150
167
  ) -> Union[List[Dict[str, Any]], Tuple[Any, ...]]:
151
168
  """Runs ``keyword`` from ``library`` concurrently, once per item or N times.
@@ -178,17 +195,23 @@ class ParallelRunner:
178
195
  return value (in call order) instead of the result dictionaries.
179
196
  Fails with ``ParallelTaskError`` if any call failed. Same as calling
180
197
  `Get Result Values` on the default return value.
198
+ - ``fail_on_error``: If true (default), fail with ``ParallelTaskError``
199
+ when any call failed, after all calls have finished and their logs
200
+ have been replayed. If false, return the results anyway and leave
201
+ checking ``status`` to the caller. See `Failures`.
181
202
  - ``**kwargs``: Any other named argument is passed to every call.
182
203
 
183
204
  Returns a list of result dictionaries (see `Result format`), in call
184
205
  order: entry ``i`` belongs to ``for_loop_iterable[i]`` / repeat
185
- index ``i``, regardless of which thread finished first.
206
+ index ``i``, regardless of which thread finished first. With the
207
+ default ``fail_on_error=True`` it only returns when every call passed.
186
208
 
187
209
  Examples:
188
210
  | ${agents}= | Create List | 1 | 2 | 3 |
189
211
  | ${results}= | Run Parallel Scenarios | keyword=Verify Agent Data | library=MyLibrary | for_loop_iterable=${agents} |
190
212
  | ${results}= | Run Parallel Scenarios | keyword=Check Health | library=MyLibrary | repeat=8 | agent_id=1 |
191
213
  | ${id1} ${id2} ${id3}= | Run Parallel Scenarios | keyword=Seed Record | library=MyLibrary | repeat=3 | return_values_only=True |
214
+ | ${results}= | Run Parallel Scenarios | keyword=Verify Agent Data | library=MyLibrary | for_loop_iterable=${agents} | fail_on_error=False |
192
215
  """
193
216
  # Determine items to process. NOTE: `repeat or 1` would be wrong here -
194
217
  # 0 is falsy in Python, so an explicit repeat=0 would silently fall
@@ -228,6 +251,8 @@ class ParallelRunner:
228
251
 
229
252
  if return_values_only:
230
253
  return self.get_result_values(results)
254
+ if fail_on_error:
255
+ self._raise_if_any_failed(results)
231
256
  return results
232
257
 
233
258
  def get_result_values(self, results: List[Dict[str, Any]]) -> Tuple[Any, ...]:
@@ -238,7 +263,9 @@ class ParallelRunner:
238
263
 
239
264
  Passing ``return_values_only=True`` to `Run Parallel Scenarios` does
240
265
  the same in one step; use this keyword when you want to inspect the
241
- full results first (e.g. assert on ``status`` or ``logs``).
266
+ full results first (e.g. assert on ``status`` or ``logs``). Such a
267
+ result list may only contain failed calls if it came from a run with
268
+ ``fail_on_error=False``.
242
269
 
243
270
  Fails with ``ParallelTaskError`` if any entry has status ``FAIL`` -
244
271
  a failed call has no return value to put in its slot.
@@ -247,10 +274,13 @@ class ParallelRunner:
247
274
  | ${results}= | Run Parallel Scenarios | keyword=Seed Record | library=MyLibrary | repeat=3 |
248
275
  | ${id1} ${id2} ${id3}= | Get Result Values | ${results} |
249
276
  """
277
+ self._raise_if_any_failed(results)
278
+ return tuple(entry["result"] for entry in results)
279
+
280
+ def _raise_if_any_failed(self, results: List[Dict[str, Any]]) -> None:
250
281
  failures = [entry for entry in results if entry.get("status") == "FAIL"]
251
282
  if failures:
252
283
  raise ParallelTaskError(failures, len(results))
253
- return tuple(entry["result"] for entry in results)
254
284
 
255
285
  def _resolve_mapper(self, mapper: Optional[Any], source: str = "logger_mapper") -> Optional[Any]:
256
286
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-parallelrunner
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Run a keyword in parallel inside a single Robot Framework test case, with one clean, ordered log.html.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -118,6 +118,10 @@ threading, buffering, and replay logic stay out of your `.robot` files.
118
118
  8 workers (see [Using It Together With pabot](#using-it-together-with-pabot)).
119
119
  - **Integrity.** Your `log.html` remains the single source of truth — no
120
120
  broken XML tags from concurrent writes.
121
+ - **No hidden failures.** If any call fails, the keyword fails too (after all
122
+ calls finish and their logs are replayed), with a message listing every
123
+ failed item. Pass `fail_on_error=False` to get a `PASS`/`FAIL` status per
124
+ item and check them yourself.
121
125
  - **Flexibility.** Pass `repeat=10` to stress-test a single endpoint, or
122
126
  `for_loop_iterable=${items}` for batch validation of a whole list.
123
127
  - **Ergonomics.** Results come back in call order (not completion order), so
@@ -179,7 +183,7 @@ pabot --processes 4 --pythonpath . atest/
179
183
  pabot --processes 4 --testlevelsplit --pythonpath . atest/
180
184
  ```
181
185
 
182
- Both commands pass the full acceptance suite of this repository (41 tests;
186
+ Both commands pass the full acceptance suite of this repository (52 tests;
183
187
  checked with pabot 5.2.2).
184
188
 
185
189
  How much time each one saves, from the offline
@@ -1,9 +1,9 @@
1
1
  ParallelRunner/__init__.py,sha256=ifsoO1rXf4kL3i75NokrD_KHxnlvE3IuH69Fb-ByfNg,641
2
- ParallelRunner/parallel_library.py,sha256=wOBvPLPLC0GSc9el7O_bcVHfl5NuGKaiWGXdZKTH6do,17380
2
+ ParallelRunner/parallel_library.py,sha256=qagok_v9Kw_OpsXy5yRb2hvwBpwadfgwhfC5bL1boVE,19123
3
3
  ParallelRunner/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ParallelRunner/version.py,sha256=W8E8ymdTLqfKl_1qBIOrjGpO8kSbqPw30aYZpqYUNMk,632
5
5
  parallelrunner.py,sha256=2oyKOVeKlOaZUZ3f2i_l5xCAYFKVm11bSQZOtzSSJO8,1132
6
- robotframework_parallelrunner-0.2.1.dist-info/METADATA,sha256=9VmBqhZ3dMGu9_onf6S8afWTGbr-130YVHWuDHWZJ0M,14053
7
- robotframework_parallelrunner-0.2.1.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
8
- robotframework_parallelrunner-0.2.1.dist-info/licenses/LICENSE,sha256=xxQh3uvlk5jC1i0eoH5Nyo9cDz2Fzu0DqOzAZw8XxFs,1072
9
- robotframework_parallelrunner-0.2.1.dist-info/RECORD,,
6
+ robotframework_parallelrunner-0.3.0.dist-info/METADATA,sha256=6Ph0tfBiO_I6mZ4BP606SDGfJYwCUzOICu5LHMoq1kY,14313
7
+ robotframework_parallelrunner-0.3.0.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
8
+ robotframework_parallelrunner-0.3.0.dist-info/licenses/LICENSE,sha256=xxQh3uvlk5jC1i0eoH5Nyo9cDz2Fzu0DqOzAZw8XxFs,1072
9
+ robotframework_parallelrunner-0.3.0.dist-info/RECORD,,