Summary
pytest-run-parallel's _handle_collected_item assumes every collected item is a pytest.Function (or otherwise has an item.obj callable with a __globals__ mapping). Plugins that contribute non-Function items, such as sybil's pytest integration (Sybil(...).pytest()), fail collection with INTERNALERROR before any test runs, even when those items are not intended to be parallelized.
This makes pytest-run-parallel incompatible with any project that uses sybil to run examples extracted from documentation as pytest tests.
Versions:
pytest-run-parallel==0.8.2
pytest==9.0.3
sybil==10.0.1
- CPython 3.13.9
Reproduction
project/
conftest.py
docs/example.rst
conftest.py:
from sybil import Sybil
from sybil.parsers.rest import CodeBlockParser
pytest_collect_file = Sybil(
parsers=[CodeBlockParser("python", lambda example: None)],
patterns=["*.rst"],
).pytest()
docs/example.rst:
Example
=======
.. code-block:: python
x = 1
Run:
pytest --parallel-threads=8
Result:
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File ".../pytest_run_parallel/plugin.py", line 254, in pytest_collection_finish
INTERNALERROR> self._handle_collected_item(item)
INTERNALERROR> File ".../pytest_run_parallel/plugin.py", line 302, in _handle_collected_item
INTERNALERROR> original_globals = item.obj.__globals__
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> AttributeError: 'NoneType' object has no attribute '__globals__'
============================ no tests ran in 0.01s =============================
Without --parallel-threads, the same project collects and runs cleanly.
Root cause
pytest_run_parallel/plugin.py:302 (v0.8.2):
if n_workers > 1 or n_iterations > 1:
original_globals = item.obj.__globals__
item.obj = wrap_function_parallel(item.obj, n_workers, n_iterations)
for name in original_globals:
if name not in item.obj.__globals__:
item.obj.__globals__[name] = original_globals[name]
The plugin reaches into item.obj.__globals__ for every collected item once n_workers > 1 (or n_iterations > 1). For pytest.Function items, item.obj is the underlying Python function and __globals__ is its module globals. For sybil's SybilItem (and likely any non-Function collector that subclasses pytest.Item directly), item.obj is None, so the attribute access raises during the pytest_collection_finish hook and aborts the whole run.
Summary
pytest-run-parallel's_handle_collected_itemassumes every collected item is apytest.Function(or otherwise has anitem.objcallable with a__globals__mapping). Plugins that contribute non-Functionitems, such as sybil's pytest integration (Sybil(...).pytest()), fail collection withINTERNALERRORbefore any test runs, even when those items are not intended to be parallelized.This makes
pytest-run-parallelincompatible with any project that uses sybil to run examples extracted from documentation as pytest tests.Versions:
pytest-run-parallel==0.8.2pytest==9.0.3sybil==10.0.1Reproduction
conftest.py:docs/example.rst:Run:
Result:
Without
--parallel-threads, the same project collects and runs cleanly.Root cause
pytest_run_parallel/plugin.py:302(v0.8.2):The plugin reaches into
item.obj.__globals__for every collected item oncen_workers > 1(orn_iterations > 1). Forpytest.Functionitems,item.objis the underlying Python function and__globals__is its module globals. For sybil'sSybilItem(and likely any non-Functioncollector that subclassespytest.Itemdirectly),item.objisNone, so the attribute access raises during thepytest_collection_finishhook and aborts the whole run.